blob: 9cea1b945be5d99a5748676e3d034a81896f747d [file] [log] [blame]
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Ray Milkeya4122362015-08-18 15:19:08 -070016package org.onosproject.net.config;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070017
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070018import com.fasterxml.jackson.databind.JsonNode;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070019import com.fasterxml.jackson.databind.ObjectMapper;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070020import com.fasterxml.jackson.databind.node.ArrayNode;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070021import com.fasterxml.jackson.databind.node.ObjectNode;
Thomas Vachuskae2b7e7e2015-05-20 11:11:31 -070022import com.google.common.annotations.Beta;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080023import com.google.common.collect.ImmutableSet;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070024import com.google.common.collect.Lists;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080025import org.onlab.packet.IpAddress;
Jonathan Hart9eb45bb2016-02-12 10:59:11 -080026import org.onlab.packet.IpPrefix;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080027import org.onlab.packet.MacAddress;
Hyunsun Moon61b73e92016-05-10 18:05:57 -070028import org.onlab.packet.TpPort;
Jonathan Hart9eb45bb2016-02-12 10:59:11 -080029import org.onosproject.net.ConnectPoint;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070030
31import java.util.Collection;
Simon Hunt4f3a4072016-10-17 17:52:11 -070032import java.util.Iterator;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070033import java.util.List;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080034import java.util.Set;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070035import java.util.function.Function;
Yuta HIGUCHIa255bb42016-11-03 16:04:08 -070036import java.util.stream.Collectors;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070037
38import static com.google.common.base.Preconditions.checkNotNull;
Charles Chan023a8982016-02-04 11:00:41 -080039import static com.google.common.base.Preconditions.checkState;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070040
41/**
42 * Base abstraction of a configuration facade for a specific subject. Derived
Thomas Vachuska96d55b12015-05-11 08:52:03 -070043 * classes should keep all state in the specified JSON tree as that is the
44 * only state that will be distributed or persisted; this class is merely
45 * a facade for interacting with a particular facet of configuration on a
46 * given subject.
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070047 *
48 * @param <S> type of subject
49 */
Thomas Vachuskae2b7e7e2015-05-20 11:11:31 -070050@Beta
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070051public abstract class Config<S> {
52
Jonathan Hart9eb45bb2016-02-12 10:59:11 -080053 private static final String TRUE_LITERAL = "true";
54 private static final String FALSE_LITERAL = "false";
55
Thomas Vachuska96d55b12015-05-11 08:52:03 -070056 protected S subject;
57 protected String key;
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070058
59 protected JsonNode node;
60 protected ObjectNode object;
61 protected ArrayNode array;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070062 protected ObjectMapper mapper;
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070063
Thomas Vachuska96d55b12015-05-11 08:52:03 -070064 protected ConfigApplyDelegate delegate;
65
66 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080067 * Indicator of whether a configuration JSON field is required.
68 */
69 public enum FieldPresence {
70 /**
71 * Signifies that config field is an optional one.
72 */
73 OPTIONAL,
74
75 /**
76 * Signifies that config field is mandatory.
77 */
78 MANDATORY
79 }
80
81 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -070082 * Initializes the configuration behaviour with necessary context.
83 *
84 * @param subject configuration subject
85 * @param key configuration key
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070086 * @param node JSON node where configuration data is stored
Thomas Vachuska96d55b12015-05-11 08:52:03 -070087 * @param mapper JSON object mapper
Charles Chan023a8982016-02-04 11:00:41 -080088 * @param delegate delegate context, or null for detached configs.
Thomas Vachuska96d55b12015-05-11 08:52:03 -070089 */
Charles Chan023a8982016-02-04 11:00:41 -080090 public final void init(S subject, String key, JsonNode node, ObjectMapper mapper,
Simon Hunt4f3a4072016-10-17 17:52:11 -070091 ConfigApplyDelegate delegate) {
Charles Chan023a8982016-02-04 11:00:41 -080092 this.subject = checkNotNull(subject, "Subject cannot be null");
Thomas Vachuska96d55b12015-05-11 08:52:03 -070093 this.key = key;
Charles Chan023a8982016-02-04 11:00:41 -080094 this.node = checkNotNull(node, "Node cannot be null");
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070095 this.object = node instanceof ObjectNode ? (ObjectNode) node : null;
96 this.array = node instanceof ArrayNode ? (ArrayNode) node : null;
Charles Chan023a8982016-02-04 11:00:41 -080097 this.mapper = checkNotNull(mapper, "Mapper cannot be null");
98 this.delegate = delegate;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070099 }
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700100
101 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800102 * Indicates whether or not the backing JSON node contains valid data.
103 * <p>
104 * Default implementation returns true.
105 * Subclasses are expected to override this with their own validation.
Thomas Vachuska36008462016-01-07 15:38:20 -0800106 * Implementations are free to throw a RuntimeException if data is invalid.
Jonathan Hart54b83e82016-03-26 20:37:20 -0700107 * </p>
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800108 *
109 * @return true if the data is valid; false otherwise
Thomas Vachuska36008462016-01-07 15:38:20 -0800110 * @throws RuntimeException if configuration is invalid or completely foobar
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800111 */
112 public boolean isValid() {
Thomas Vachuska36008462016-01-07 15:38:20 -0800113 // Derivatives should use the provided set of predicates to test
114 // validity of their fields, e.g.:
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800115 // isString(path)
116 // isBoolean(path)
117 // isNumber(path, [min, max])
Jonathan Hart54b83e82016-03-26 20:37:20 -0700118 // isIntegralNumber(path, [min, max])
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800119 // isDecimal(path, [min, max])
120 // isMacAddress(path)
121 // isIpAddress(path)
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800122 // isIpPrefix(path)
123 // isConnectPoint(path)
Jonathan Hart54b83e82016-03-26 20:37:20 -0700124 // isTpPort(path)
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800125 return true;
126 }
127
128 /**
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700129 * Returns the specific subject to which this configuration pertains.
130 *
131 * @return configuration subject
132 */
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700133 public S subject() {
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700134 return subject;
135 }
136
137 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700138 * Returns the configuration key. This is primarily aimed for use in
139 * composite JSON trees in external representations and has no bearing on
140 * the internal behaviours.
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700141 *
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700142 * @return configuration key
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700143 */
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700144 public String key() {
145 return key;
146 }
147
148 /**
149 * Returns the JSON node that contains the configuration data.
150 *
151 * @return JSON node backing the configuration
152 */
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700153 public JsonNode node() {
Jonathan Harta8625482015-09-08 16:14:56 -0700154 return node;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700155 }
156
157 /**
158 * Applies any configuration changes made via this configuration.
Jonathan Hart54b83e82016-03-26 20:37:20 -0700159 * <p>
Charles Chan023a8982016-02-04 11:00:41 -0800160 * Not effective for detached configs.
Jonathan Hart54b83e82016-03-26 20:37:20 -0700161 * </p>
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700162 */
163 public void apply() {
Charles Chan023a8982016-02-04 11:00:41 -0800164 checkState(delegate != null, "Cannot apply detached config");
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700165 delegate.onApply(this);
166 }
167
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700168 // Miscellaneous helpers for interacting with JSON
169
170 /**
171 * Gets the specified property as a string.
172 *
173 * @param name property name
174 * @param defaultValue default value if property not set
175 * @return property value or default value
176 */
177 protected String get(String name, String defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700178 return object.path(name).asText(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700179 }
180
181 /**
182 * Sets the specified property as a string or clears it if null value given.
183 *
184 * @param name property name
185 * @param value new value or null to clear the property
186 * @return self
187 */
188 protected Config<S> setOrClear(String name, String value) {
189 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700190 object.put(name, value);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700191 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700192 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700193 }
194 return this;
195 }
196
197 /**
198 * Gets the specified property as a boolean.
199 *
200 * @param name property name
201 * @param defaultValue default value if property not set
202 * @return property value or default value
203 */
204 protected boolean get(String name, boolean defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700205 return object.path(name).asBoolean(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700206 }
207
208 /**
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800209 * Clears the specified property.
210 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700211 * @param name property name
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800212 * @return self
213 */
214 protected Config<S> clear(String name) {
215 object.remove(name);
216 return this;
217 }
218
219 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700220 * Sets the specified property as a boolean or clears it if null value given.
221 *
222 * @param name property name
223 * @param value new value or null to clear the property
224 * @return self
225 */
226 protected Config<S> setOrClear(String name, Boolean value) {
227 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700228 object.put(name, value.booleanValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700229 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700230 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700231 }
232 return this;
233 }
234
235 /**
Jonathan Hart111b42b2015-07-14 13:28:05 -0700236 * Gets the specified property as an integer.
237 *
238 * @param name property name
239 * @param defaultValue default value if property not set
240 * @return property value or default value
241 */
242 protected int get(String name, int defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700243 return object.path(name).asInt(defaultValue);
Jonathan Hart111b42b2015-07-14 13:28:05 -0700244 }
245
246 /**
247 * Sets the specified property as an integer or clears it if null value given.
248 *
249 * @param name property name
250 * @param value new value or null to clear the property
251 * @return self
252 */
253 protected Config<S> setOrClear(String name, Integer value) {
254 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700255 object.put(name, value.intValue());
Jonathan Hart111b42b2015-07-14 13:28:05 -0700256 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700257 object.remove(name);
Jonathan Hart111b42b2015-07-14 13:28:05 -0700258 }
259 return this;
260 }
261
262 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700263 * Gets the specified property as a long.
264 *
265 * @param name property name
266 * @param defaultValue default value if property not set
267 * @return property value or default value
268 */
269 protected long get(String name, long defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700270 return object.path(name).asLong(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700271 }
272
273 /**
274 * Sets the specified property as a long or clears it if null value given.
275 *
276 * @param name property name
277 * @param value new value or null to clear the property
278 * @return self
279 */
280 protected Config<S> setOrClear(String name, Long value) {
281 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700282 object.put(name, value.longValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700283 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700284 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700285 }
286 return this;
287 }
288
289 /**
290 * Gets the specified property as a double.
291 *
292 * @param name property name
293 * @param defaultValue default value if property not set
294 * @return property value or default value
295 */
296 protected double get(String name, double defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700297 return object.path(name).asDouble(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700298 }
299
300 /**
301 * Sets the specified property as a double or clears it if null value given.
302 *
303 * @param name property name
304 * @param value new value or null to clear the property
305 * @return self
306 */
307 protected Config<S> setOrClear(String name, Double value) {
308 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700309 object.put(name, value.doubleValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700310 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700311 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700312 }
313 return this;
314 }
315
316 /**
317 * Gets the specified property as an enum.
318 *
319 * @param name property name
320 * @param defaultValue default value if property not set
321 * @param enumClass the enum class
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700322 * @param <E> type of enum
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700323 * @return property value or default value
324 */
325 protected <E extends Enum<E>> E get(String name, E defaultValue, Class<E> enumClass) {
Thomas Vachuska2d6c5992016-07-01 12:42:26 +0200326 if (defaultValue != null) {
Charles Chand5c3b932016-09-22 17:49:52 -0700327 return Enum.valueOf(enumClass, object.path(name).asText(defaultValue.toString()));
Thomas Vachuska2d6c5992016-07-01 12:42:26 +0200328 }
329
330 JsonNode node = object.get(name);
331 return node == null ? null : Enum.valueOf(enumClass, node.asText());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700332 }
333
334 /**
335 * Sets the specified property as a double or clears it if null value given.
336 *
337 * @param name property name
338 * @param value new value or null to clear the property
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700339 * @param <E> type of enum
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700340 * @return self
341 */
342 protected <E extends Enum> Config<S> setOrClear(String name, E value) {
343 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700344 object.put(name, value.toString());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700345 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700346 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700347 }
348 return this;
349 }
Jonathan Hart111b42b2015-07-14 13:28:05 -0700350
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700351 /**
352 * Gets the specified array property as a list of items.
353 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700354 * @param name property name
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700355 * @param function mapper from string to item
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700356 * @param <T> type of item
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700357 * @return list of items
358 */
359 protected <T> List<T> getList(String name, Function<String, T> function) {
360 List<T> list = Lists.newArrayList();
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700361 ArrayNode arrayNode = (ArrayNode) object.path(name);
Yuta HIGUCHIa255bb42016-11-03 16:04:08 -0700362 arrayNode.forEach(i -> list.add(function.apply(asString(i))));
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700363 return list;
364 }
365
366 /**
Yuta HIGUCHIa255bb42016-11-03 16:04:08 -0700367 * Converts JSON node to a String.
368 * <p>
369 * If the {@code node} was a text node, text is returned as-is,
370 * all other node type will be converted to String by toString().
371 *
372 * @param node JSON node to convert
373 * @return String representation
374 */
375 private static String asString(JsonNode node) {
376 if (node.isTextual()) {
377 return node.asText();
378 } else {
379 return node.toString();
380 }
381 }
382
383 /**
Naoki Shiota399a0b32015-11-15 20:36:13 -0600384 * Gets the specified array property as a list of items.
385 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700386 * @param name property name
387 * @param function mapper from string to item
Naoki Shiota399a0b32015-11-15 20:36:13 -0600388 * @param defaultValue default value if property not set
Simon Hunt4f3a4072016-10-17 17:52:11 -0700389 * @param <T> type of item
Naoki Shiota399a0b32015-11-15 20:36:13 -0600390 * @return list of items
391 */
392 protected <T> List<T> getList(String name, Function<String, T> function, List<T> defaultValue) {
393 List<T> list = Lists.newArrayList();
394 JsonNode jsonNode = object.path(name);
395 if (jsonNode.isMissingNode()) {
396 return defaultValue;
397 }
398 ArrayNode arrayNode = (ArrayNode) jsonNode;
Yuta HIGUCHIa255bb42016-11-03 16:04:08 -0700399 arrayNode.forEach(i -> list.add(function.apply(asString(i))));
Naoki Shiota399a0b32015-11-15 20:36:13 -0600400 return list;
401 }
402
403 /**
Yuta HIGUCHIa255bb42016-11-03 16:04:08 -0700404 * Sets the specified property as an array of items in a given collection
405 * transformed into a String with supplied {@code function}.
406 *
407 * @param name propertyName
408 * @param function to transform item to a String
409 * @param value list of items
410 * @param <T> type of items
411 * @return self
412 */
413 protected <T> Config<S> setList(String name,
414 Function<? super T, String> function,
415 List<T> value) {
416 Collection<String> mapped = value.stream()
417 .map(function)
418 .collect(Collectors.toList());
419 return setOrClear(name, mapped);
420 }
421
422 /**
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700423 * Sets the specified property as an array of items in a given collection or
424 * clears it if null is given.
425 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700426 * @param name propertyName
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700427 * @param collection collection of items
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700428 * @param <T> type of items
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700429 * @return self
430 */
431 protected <T> Config<S> setOrClear(String name, Collection<T> collection) {
432 if (collection == null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700433 object.remove(name);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700434 } else {
435 ArrayNode arrayNode = mapper.createArrayNode();
436 collection.forEach(i -> arrayNode.add(i.toString()));
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700437 object.set(name, arrayNode);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700438 }
439 return this;
440 }
441
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800442 /**
Simon Hunt4f3a4072016-10-17 17:52:11 -0700443 * Returns true if this config contains a field with the given name.
444 *
445 * @param name the field name
446 * @return true if field is present, false otherwise
447 */
448 protected boolean hasField(String name) {
449 return hasField(object, name);
450 }
451
452 /**
453 * Returns true if the given node contains a field with the given name.
454 *
455 * @param node the node to examine
456 * @param name the name to look for
457 * @return true if the node has a field with the given name, false otherwise
458 */
459 protected boolean hasField(ObjectNode node, String name) {
460 Iterator<String> fnames = node.fieldNames();
461 while (fnames.hasNext()) {
462 if (fnames.next().equals(name)) {
463 return true;
464 }
465 }
466 return false;
467 }
468
469 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800470 * Indicates whether only the specified fields are present in the backing JSON.
471 *
472 * @param allowedFields allowed field names
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700473 * @return true if only allowedFields are present; false otherwise
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800474 */
475 protected boolean hasOnlyFields(String... allowedFields) {
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800476 return hasOnlyFields(object, allowedFields);
477 }
478
479 /**
480 * Indicates whether only the specified fields are present in a particular
481 * JSON object.
482 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700483 * @param node node whose fields to check
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800484 * @param allowedFields allowed field names
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700485 * @return true if only allowedFields are present; false otherwise
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800486 */
487 protected boolean hasOnlyFields(ObjectNode node, String... allowedFields) {
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800488 Set<String> fields = ImmutableSet.copyOf(allowedFields);
Jonathan Hart54b83e82016-03-26 20:37:20 -0700489 node.fieldNames().forEachRemaining(f -> {
490 if (!fields.contains(f)) {
491 throw new InvalidFieldException(f, "Field is not allowed");
492 }
493 });
494 return true;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800495 }
496
497 /**
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700498 * Indicates whether all specified fields are present in the backing JSON.
499 *
500 * @param mandatoryFields mandatory field names
501 * @return true if all mandatory fields are present; false otherwise
502 */
503 protected boolean hasFields(String... mandatoryFields) {
504 return hasFields(object, mandatoryFields);
505 }
506
507 /**
508 * Indicates whether all specified fields are present in a particular
509 * JSON object.
510 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700511 * @param node node whose fields to check
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700512 * @param mandatoryFields mandatory field names
513 * @return true if all mandatory fields are present; false otherwise
514 */
515 protected boolean hasFields(ObjectNode node, String... mandatoryFields) {
516 Set<String> fields = ImmutableSet.copyOf(mandatoryFields);
Jonathan Hart54b83e82016-03-26 20:37:20 -0700517 fields.forEach(f -> {
518 if (node.path(f).isMissingNode()) {
519 throw new InvalidFieldException(f, "Mandatory field is not present");
520 }
521 });
522 return true;
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700523 }
524
525 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800526 * Indicates whether the specified field holds a valid MAC address.
527 *
528 * @param field JSON field name
529 * @param presence specifies if field is optional or mandatory
530 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700531 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800532 */
533 protected boolean isMacAddress(String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700534 return isMacAddress(object, field, presence);
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800535 }
536
537 /**
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800538 * Indicates whether the specified field of a particular node holds a valid
539 * MAC address.
540 *
541 * @param objectNode JSON node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700542 * @param field JSON field name
543 * @param presence specifies if field is optional or mandatory
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800544 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700545 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800546 */
547 protected boolean isMacAddress(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700548 return isValid(objectNode, field, presence, n -> {
549 MacAddress.valueOf(n.asText());
550 return true;
551 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800552 }
553
554 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800555 * Indicates whether the specified field holds a valid IP address.
556 *
557 * @param field JSON field name
558 * @param presence specifies if field is optional or mandatory
559 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700560 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800561 */
562 protected boolean isIpAddress(String field, FieldPresence presence) {
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800563 return isIpAddress(object, field, presence);
564 }
565
566 /**
567 * Indicates whether the specified field of a particular node holds a valid
568 * IP address.
569 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700570 * @param objectNode node from whom to access the field
571 * @param field JSON field name
572 * @param presence specifies if field is optional or mandatory
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800573 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700574 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800575 */
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800576 protected boolean isIpAddress(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700577 return isValid(objectNode, field, presence, n -> {
578 IpAddress.valueOf(n.asText());
579 return true;
580 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800581 }
582
583 /**
584 * Indicates whether the specified field holds a valid IP prefix.
585 *
586 * @param field JSON field name
587 * @param presence specifies if field is optional or mandatory
588 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700589 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800590 */
591 protected boolean isIpPrefix(String field, FieldPresence presence) {
592 return isIpPrefix(object, field, presence);
593 }
594
595 /**
596 * Indicates whether the specified field of a particular node holds a valid
597 * IP prefix.
598 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700599 * @param objectNode node from whom to access the field
600 * @param field JSON field name
601 * @param presence specifies if field is optional or mandatory
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800602 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700603 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800604 */
605 protected boolean isIpPrefix(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700606 return isValid(objectNode, field, presence, n -> {
607 IpPrefix.valueOf(n.asText());
608 return true;
609 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800610 }
611
612 /**
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700613 * Indicates whether the specified field holds a valid transport layer port.
614 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700615 * @param field JSON field name
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700616 * @param presence specifies if field is optional or mandatory
617 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700618 * @throws InvalidFieldException if the field is present but not valid
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700619 */
620 protected boolean isTpPort(String field, FieldPresence presence) {
621 return isTpPort(object, field, presence);
622 }
623
624 /**
625 * Indicates whether the specified field of a particular node holds a valid
626 * transport layer port.
627 *
628 * @param objectNode node from whom to access the field
Simon Hunt4f3a4072016-10-17 17:52:11 -0700629 * @param field JSON field name
630 * @param presence specifies if field is optional or mandatory
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700631 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700632 * @throws InvalidFieldException if the field is present but not valid
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700633 */
634 protected boolean isTpPort(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700635 return isValid(objectNode, field, presence, n -> {
636 TpPort.tpPort(n.asInt());
637 return true;
638 });
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700639 }
640
641 /**
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800642 * Indicates whether the specified field holds a valid connect point string.
643 *
644 * @param field JSON field name
645 * @param presence specifies if field is optional or mandatory
646 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700647 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800648 */
649 protected boolean isConnectPoint(String field, FieldPresence presence) {
650 return isConnectPoint(object, field, presence);
651 }
652
653 /**
654 * Indicates whether the specified field of a particular node holds a valid
655 * connect point string.
656 *
657 * @param objectNode JSON node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700658 * @param field JSON field name
659 * @param presence specifies if field is optional or mandatory
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800660 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700661 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800662 */
663 protected boolean isConnectPoint(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700664 return isValid(objectNode, field, presence, n -> {
665 ConnectPoint.deviceConnectPoint(n.asText());
666 return true;
667 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800668 }
669
670 /**
671 * Indicates whether the specified field holds a valid string value.
672 *
673 * @param field JSON field name
674 * @param presence specifies if field is optional or mandatory
675 * @param pattern optional regex pattern
676 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700677 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800678 */
679 protected boolean isString(String field, FieldPresence presence, String... pattern) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800680 return isString(object, field, presence, pattern);
681 }
682
683 /**
684 * Indicates whether the specified field on a particular node holds a valid
685 * string value.
686 *
687 * @param objectNode JSON node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700688 * @param field JSON field name
689 * @param presence specifies if field is optional or mandatory
690 * @param pattern optional regex pattern
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800691 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700692 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800693 */
694 protected boolean isString(ObjectNode objectNode, String field,
695 FieldPresence presence, String... pattern) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700696 return isValid(objectNode, field, presence, (node) -> {
697 if (!(node.isTextual() &&
698 (pattern.length > 0 && node.asText().matches(pattern[0]) || pattern.length < 1))) {
699 fail("Invalid string value");
700 }
701 return true;
702 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800703 }
704
705 /**
706 * Indicates whether the specified field holds a valid number.
707 *
708 * @param field JSON field name
709 * @param presence specifies if field is optional or mandatory
710 * @param minMax optional min/max values
711 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700712 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800713 */
714 protected boolean isNumber(String field, FieldPresence presence, long... minMax) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700715 return isNumber(object, field, presence, minMax);
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800716 }
Jonathan Hart54b83e82016-03-26 20:37:20 -0700717
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800718 /**
Jonathan Hart54b83e82016-03-26 20:37:20 -0700719 * Indicates whether the specified field of a particular node holds a
720 * valid number.
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800721 *
Jonathan Hart54b83e82016-03-26 20:37:20 -0700722 * @param objectNode JSON object
Simon Hunt4f3a4072016-10-17 17:52:11 -0700723 * @param field JSON field name
724 * @param presence specifies if field is optional or mandatory
725 * @param minMax optional min/max values
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800726 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700727 * @throws InvalidFieldException if the field is present but not valid
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800728 */
Jonathan Hart54b83e82016-03-26 20:37:20 -0700729 protected boolean isNumber(ObjectNode objectNode, String field,
730 FieldPresence presence, long... minMax) {
731 return isValid(objectNode, field, presence, n -> {
732 long number = (n.isNumber()) ? n.asLong() : Long.parseLong(n.asText());
733 if (minMax.length > 1) {
734 verifyRange(number, minMax[0], minMax[1]);
735 } else if (minMax.length > 0) {
736 verifyRange(number, minMax[0]);
737 }
738 return true;
739 });
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800740 }
741
742 /**
743 * Indicates whether the specified field holds a valid integer.
744 *
745 * @param field JSON field name
746 * @param presence specifies if field is optional or mandatory
747 * @param minMax optional min/max values
748 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700749 * @throws InvalidFieldException if the field is present but not valid
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800750 */
751 protected boolean isIntegralNumber(String field, FieldPresence presence, long... minMax) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800752 return isIntegralNumber(object, field, presence, minMax);
753 }
754
755 /**
756 * Indicates whether the specified field of a particular node holds a valid
757 * integer.
758 *
759 * @param objectNode JSON node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700760 * @param field JSON field name
761 * @param presence specifies if field is optional or mandatory
762 * @param minMax optional min/max values
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800763 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700764 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800765 */
766 protected boolean isIntegralNumber(ObjectNode objectNode, String field,
767 FieldPresence presence, long... minMax) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700768 return isValid(objectNode, field, presence, n -> {
769 long number = (n.isIntegralNumber()) ? n.asLong() : Long.parseLong(n.asText());
770 if (minMax.length > 1) {
771 verifyRange(number, minMax[0], minMax[1]);
772 } else if (minMax.length > 0) {
773 verifyRange(number, minMax[0]);
774 }
775 return true;
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800776 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800777 }
778
779 /**
780 * Indicates whether the specified field holds a valid decimal number.
781 *
782 * @param field JSON field name
783 * @param presence specifies if field is optional or mandatory
784 * @param minMax optional min/max values
785 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700786 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800787 */
788 protected boolean isDecimal(String field, FieldPresence presence, double... minMax) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700789 return isDecimal(object, field, presence, minMax);
790 }
791
792 /**
793 * Indicates whether the specified field of a particular node holds a valid
794 * decimal number.
795 *
796 * @param objectNode JSON node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700797 * @param field JSON field name
798 * @param presence specifies if field is optional or mandatory
799 * @param minMax optional min/max values
Jonathan Hart54b83e82016-03-26 20:37:20 -0700800 * @return true if valid; false otherwise
801 * @throws InvalidFieldException if the field is present but not valid
802 */
803 protected boolean isDecimal(ObjectNode objectNode, String field,
804 FieldPresence presence, double... minMax) {
805 return isValid(objectNode, field, presence, n -> {
806 double number = (n.isDouble()) ? n.asDouble() : Double.parseDouble(n.asText());
807 if (minMax.length > 1) {
808 verifyRange(number, minMax[0], minMax[1]);
809 } else if (minMax.length > 0) {
810 verifyRange(number, minMax[0]);
811 }
812 return true;
813 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800814 }
815
816 /**
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530817 * Indicates whether the specified field holds a valid boolean value.
818 *
819 * @param field JSON field name
820 * @param presence specifies if field is optional or mandatory
821 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700822 * @throws InvalidFieldException if the field is present but not valid
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530823 */
824 protected boolean isBoolean(String field, FieldPresence presence) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800825 return isBoolean(object, field, presence);
826 }
827
828 /**
829 * Indicates whether the specified field of a particular node holds a valid
830 * boolean value.
831 *
832 * @param objectNode JSON object node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700833 * @param field JSON field name
834 * @param presence specifies if field is optional or mandatory
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800835 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700836 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800837 */
838 protected boolean isBoolean(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700839 return isValid(objectNode, field, presence, n -> {
840 if (!(n.isBoolean() || (n.isTextual() && isBooleanString(n.asText())))) {
841 fail("Field is not a boolean value");
842 }
843 return true;
844 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800845 }
846
847 /**
848 * Indicates whether a string holds a boolean literal value.
849 *
850 * @param str string to test
851 * @return true if the string contains "true" or "false" (case insensitive),
852 * otherwise false
853 */
854 private boolean isBooleanString(String str) {
855 return str.equalsIgnoreCase(TRUE_LITERAL) || str.equalsIgnoreCase(FALSE_LITERAL);
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530856 }
857
858 /**
Jonathan Hart54b83e82016-03-26 20:37:20 -0700859 * Indicates whether a field in the node is present and of correct value or
860 * not mandatory and absent.
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800861 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700862 * @param objectNode JSON object node containing field to validate
863 * @param field name of field to validate
864 * @param presence specified if field is optional or mandatory
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800865 * @param validationFunction function which can be used to verify if the
866 * node has the correct value
867 * @return true if the field is as expected
Jonathan Hart54b83e82016-03-26 20:37:20 -0700868 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800869 */
Jonathan Hart54b83e82016-03-26 20:37:20 -0700870 private boolean isValid(ObjectNode objectNode, String field, FieldPresence presence,
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800871 Function<JsonNode, Boolean> validationFunction) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700872 JsonNode node = objectNode.path(field);
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800873 boolean isMandatory = presence == FieldPresence.MANDATORY;
Jonathan Hart54b83e82016-03-26 20:37:20 -0700874 if (isMandatory && node.isMissingNode()) {
875 throw new InvalidFieldException(field, "Mandatory field not present");
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800876 }
877
878 if (!isMandatory && (node.isNull() || node.isMissingNode())) {
879 return true;
880 }
881
Jonathan Hart54b83e82016-03-26 20:37:20 -0700882 try {
883 if (validationFunction.apply(node)) {
884 return true;
885 } else {
886 throw new InvalidFieldException(field, "Validation error");
887 }
888 } catch (IllegalArgumentException e) {
889 throw new InvalidFieldException(field, e);
890 }
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800891 }
Jonathan Hart54b83e82016-03-26 20:37:20 -0700892
893 private static void fail(String message) {
894 throw new IllegalArgumentException(message);
895 }
896
897 private static <N extends Comparable> void verifyRange(N num, N min) {
898 if (num.compareTo(min) < 0) {
899 fail("Field must be greater than " + min);
900 }
901 }
902
903 private static <N extends Comparable> void verifyRange(N num, N min, N max) {
904 verifyRange(num, min);
905
906 if (num.compareTo(max) > 0) {
907 fail("Field must be less than " + max);
908 }
909 }
910
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700911}