blob: e287538da448f1594b3d1b891d789ca28f7d7780 [file] [log] [blame]
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -07001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
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;
24import com.google.common.collect.Iterators;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070025import com.google.common.collect.Lists;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080026import org.onlab.packet.IpAddress;
Jonathan Hart9eb45bb2016-02-12 10:59:11 -080027import org.onlab.packet.IpPrefix;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080028import org.onlab.packet.MacAddress;
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;
32import java.util.List;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080033import java.util.Set;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070034import java.util.function.Function;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070035
36import static com.google.common.base.Preconditions.checkNotNull;
Charles Chan023a8982016-02-04 11:00:41 -080037import static com.google.common.base.Preconditions.checkState;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070038
39/**
40 * Base abstraction of a configuration facade for a specific subject. Derived
Thomas Vachuska96d55b12015-05-11 08:52:03 -070041 * classes should keep all state in the specified JSON tree as that is the
42 * only state that will be distributed or persisted; this class is merely
43 * a facade for interacting with a particular facet of configuration on a
44 * given subject.
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070045 *
46 * @param <S> type of subject
47 */
Thomas Vachuskae2b7e7e2015-05-20 11:11:31 -070048@Beta
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070049public abstract class Config<S> {
50
Jonathan Hart9eb45bb2016-02-12 10:59:11 -080051 private static final String TRUE_LITERAL = "true";
52 private static final String FALSE_LITERAL = "false";
53
Thomas Vachuska96d55b12015-05-11 08:52:03 -070054 protected S subject;
55 protected String key;
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070056
57 protected JsonNode node;
58 protected ObjectNode object;
59 protected ArrayNode array;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070060 protected ObjectMapper mapper;
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070061
Thomas Vachuska96d55b12015-05-11 08:52:03 -070062 protected ConfigApplyDelegate delegate;
63
64 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080065 * Indicator of whether a configuration JSON field is required.
66 */
67 public enum FieldPresence {
68 /**
69 * Signifies that config field is an optional one.
70 */
71 OPTIONAL,
72
73 /**
74 * Signifies that config field is mandatory.
75 */
76 MANDATORY
77 }
78
79 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -070080 * Initializes the configuration behaviour with necessary context.
81 *
82 * @param subject configuration subject
83 * @param key configuration key
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070084 * @param node JSON node where configuration data is stored
Thomas Vachuska96d55b12015-05-11 08:52:03 -070085 * @param mapper JSON object mapper
Charles Chan023a8982016-02-04 11:00:41 -080086 * @param delegate delegate context, or null for detached configs.
Thomas Vachuska96d55b12015-05-11 08:52:03 -070087 */
Charles Chan023a8982016-02-04 11:00:41 -080088 public final void init(S subject, String key, JsonNode node, ObjectMapper mapper,
Thomas Vachuska96d55b12015-05-11 08:52:03 -070089 ConfigApplyDelegate delegate) {
Charles Chan023a8982016-02-04 11:00:41 -080090 this.subject = checkNotNull(subject, "Subject cannot be null");
Thomas Vachuska96d55b12015-05-11 08:52:03 -070091 this.key = key;
Charles Chan023a8982016-02-04 11:00:41 -080092 this.node = checkNotNull(node, "Node cannot be null");
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070093 this.object = node instanceof ObjectNode ? (ObjectNode) node : null;
94 this.array = node instanceof ArrayNode ? (ArrayNode) node : null;
Charles Chan023a8982016-02-04 11:00:41 -080095 this.mapper = checkNotNull(mapper, "Mapper cannot be null");
96 this.delegate = delegate;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070097 }
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070098
99 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800100 * Indicates whether or not the backing JSON node contains valid data.
101 * <p>
102 * Default implementation returns true.
103 * Subclasses are expected to override this with their own validation.
Thomas Vachuska36008462016-01-07 15:38:20 -0800104 * Implementations are free to throw a RuntimeException if data is invalid.
105 * * </p>
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800106 *
107 * @return true if the data is valid; false otherwise
Thomas Vachuska36008462016-01-07 15:38:20 -0800108 * @throws RuntimeException if configuration is invalid or completely foobar
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800109 */
110 public boolean isValid() {
Thomas Vachuska36008462016-01-07 15:38:20 -0800111 // Derivatives should use the provided set of predicates to test
112 // validity of their fields, e.g.:
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800113 // isString(path)
114 // isBoolean(path)
115 // isNumber(path, [min, max])
116 // isDecimal(path, [min, max])
117 // isMacAddress(path)
118 // isIpAddress(path)
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800119 // isIpPrefix(path)
120 // isConnectPoint(path)
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800121 return true;
122 }
123
124 /**
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700125 * Returns the specific subject to which this configuration pertains.
126 *
127 * @return configuration subject
128 */
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700129 public S subject() {
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700130 return subject;
131 }
132
133 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700134 * Returns the configuration key. This is primarily aimed for use in
135 * composite JSON trees in external representations and has no bearing on
136 * the internal behaviours.
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700137 *
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700138 * @return configuration key
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700139 */
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700140 public String key() {
141 return key;
142 }
143
144 /**
145 * Returns the JSON node that contains the configuration data.
146 *
147 * @return JSON node backing the configuration
148 */
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700149 public JsonNode node() {
Jonathan Harta8625482015-09-08 16:14:56 -0700150 return node;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700151 }
152
153 /**
154 * Applies any configuration changes made via this configuration.
Charles Chan023a8982016-02-04 11:00:41 -0800155 *
156 * Not effective for detached configs.
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700157 */
158 public void apply() {
Charles Chan023a8982016-02-04 11:00:41 -0800159 checkState(delegate != null, "Cannot apply detached config");
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700160 delegate.onApply(this);
161 }
162
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700163 // Miscellaneous helpers for interacting with JSON
164
165 /**
166 * Gets the specified property as a string.
167 *
168 * @param name property name
169 * @param defaultValue default value if property not set
170 * @return property value or default value
171 */
172 protected String get(String name, String defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700173 return object.path(name).asText(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700174 }
175
176 /**
177 * Sets the specified property as a string or clears it if null value given.
178 *
179 * @param name property name
180 * @param value new value or null to clear the property
181 * @return self
182 */
183 protected Config<S> setOrClear(String name, String value) {
184 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700185 object.put(name, value);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700186 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700187 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700188 }
189 return this;
190 }
191
192 /**
193 * Gets the specified property as a boolean.
194 *
195 * @param name property name
196 * @param defaultValue default value if property not set
197 * @return property value or default value
198 */
199 protected boolean get(String name, boolean defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700200 return object.path(name).asBoolean(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700201 }
202
203 /**
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800204 * Clears the specified property.
205 *
206 * @param name property name
207 * @return self
208 */
209 protected Config<S> clear(String name) {
210 object.remove(name);
211 return this;
212 }
213
214 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700215 * Sets the specified property as a boolean or clears it if null value given.
216 *
217 * @param name property name
218 * @param value new value or null to clear the property
219 * @return self
220 */
221 protected Config<S> setOrClear(String name, Boolean value) {
222 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700223 object.put(name, value.booleanValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700224 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700225 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700226 }
227 return this;
228 }
229
230 /**
Jonathan Hart111b42b2015-07-14 13:28:05 -0700231 * Gets the specified property as an integer.
232 *
233 * @param name property name
234 * @param defaultValue default value if property not set
235 * @return property value or default value
236 */
237 protected int get(String name, int defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700238 return object.path(name).asInt(defaultValue);
Jonathan Hart111b42b2015-07-14 13:28:05 -0700239 }
240
241 /**
242 * Sets the specified property as an integer or clears it if null value given.
243 *
244 * @param name property name
245 * @param value new value or null to clear the property
246 * @return self
247 */
248 protected Config<S> setOrClear(String name, Integer value) {
249 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700250 object.put(name, value.intValue());
Jonathan Hart111b42b2015-07-14 13:28:05 -0700251 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700252 object.remove(name);
Jonathan Hart111b42b2015-07-14 13:28:05 -0700253 }
254 return this;
255 }
256
257 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700258 * Gets the specified property as a long.
259 *
260 * @param name property name
261 * @param defaultValue default value if property not set
262 * @return property value or default value
263 */
264 protected long get(String name, long defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700265 return object.path(name).asLong(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700266 }
267
268 /**
269 * Sets the specified property as a long or clears it if null value given.
270 *
271 * @param name property name
272 * @param value new value or null to clear the property
273 * @return self
274 */
275 protected Config<S> setOrClear(String name, Long value) {
276 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700277 object.put(name, value.longValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700278 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700279 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700280 }
281 return this;
282 }
283
284 /**
285 * Gets the specified property as a double.
286 *
287 * @param name property name
288 * @param defaultValue default value if property not set
289 * @return property value or default value
290 */
291 protected double get(String name, double defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700292 return object.path(name).asDouble(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700293 }
294
295 /**
296 * Sets the specified property as a double or clears it if null value given.
297 *
298 * @param name property name
299 * @param value new value or null to clear the property
300 * @return self
301 */
302 protected Config<S> setOrClear(String name, Double value) {
303 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700304 object.put(name, value.doubleValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700305 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700306 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700307 }
308 return this;
309 }
310
311 /**
312 * Gets the specified property as an enum.
313 *
314 * @param name property name
315 * @param defaultValue default value if property not set
316 * @param enumClass the enum class
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700317 * @param <E> type of enum
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700318 * @return property value or default value
319 */
320 protected <E extends Enum<E>> E get(String name, E defaultValue, Class<E> enumClass) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700321 return Enum.valueOf(enumClass, object.path(name).asText(defaultValue.toString()));
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700322 }
323
324 /**
325 * Sets the specified property as a double or clears it if null value given.
326 *
327 * @param name property name
328 * @param value new value or null to clear the property
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700329 * @param <E> type of enum
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700330 * @return self
331 */
332 protected <E extends Enum> Config<S> setOrClear(String name, E value) {
333 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700334 object.put(name, value.toString());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700335 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700336 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700337 }
338 return this;
339 }
Jonathan Hart111b42b2015-07-14 13:28:05 -0700340
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700341 /**
342 * Gets the specified array property as a list of items.
343 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700344 * @param name property name
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700345 * @param function mapper from string to item
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700346 * @param <T> type of item
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700347 * @return list of items
348 */
349 protected <T> List<T> getList(String name, Function<String, T> function) {
350 List<T> list = Lists.newArrayList();
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700351 ArrayNode arrayNode = (ArrayNode) object.path(name);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700352 arrayNode.forEach(i -> list.add(function.apply(i.asText())));
353 return list;
354 }
355
356 /**
Naoki Shiota399a0b32015-11-15 20:36:13 -0600357 * Gets the specified array property as a list of items.
358 *
359 * @param name property name
360 * @param function mapper from string to item
361 * @param defaultValue default value if property not set
362 * @param <T> type of item
363 * @return list of items
364 */
365 protected <T> List<T> getList(String name, Function<String, T> function, List<T> defaultValue) {
366 List<T> list = Lists.newArrayList();
367 JsonNode jsonNode = object.path(name);
368 if (jsonNode.isMissingNode()) {
369 return defaultValue;
370 }
371 ArrayNode arrayNode = (ArrayNode) jsonNode;
372 arrayNode.forEach(i -> list.add(function.apply(i.asText())));
373 return list;
374 }
375
376 /**
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700377 * Sets the specified property as an array of items in a given collection or
378 * clears it if null is given.
379 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700380 * @param name propertyName
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700381 * @param collection collection of items
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700382 * @param <T> type of items
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700383 * @return self
384 */
385 protected <T> Config<S> setOrClear(String name, Collection<T> collection) {
386 if (collection == null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700387 object.remove(name);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700388 } else {
389 ArrayNode arrayNode = mapper.createArrayNode();
390 collection.forEach(i -> arrayNode.add(i.toString()));
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700391 object.set(name, arrayNode);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700392 }
393 return this;
394 }
395
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800396 /**
397 * Indicates whether only the specified fields are present in the backing JSON.
398 *
399 * @param allowedFields allowed field names
400 * @return true if all allowedFields are present; false otherwise
401 */
402 protected boolean hasOnlyFields(String... allowedFields) {
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800403 return hasOnlyFields(object, allowedFields);
404 }
405
406 /**
407 * Indicates whether only the specified fields are present in a particular
408 * JSON object.
409 *
410 * @param node node whose fields to check
411 * @param allowedFields allowed field names
412 * @return true if all allowedFields are present; false otherwise
413 */
414 protected boolean hasOnlyFields(ObjectNode node, String... allowedFields) {
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800415 Set<String> fields = ImmutableSet.copyOf(allowedFields);
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800416 return !Iterators.any(node.fieldNames(), f -> !fields.contains(f));
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800417 }
418
419 /**
420 * Indicates whether the specified field holds a valid MAC address.
421 *
422 * @param field JSON field name
423 * @param presence specifies if field is optional or mandatory
424 * @return true if valid; false otherwise
425 * @throws IllegalArgumentException if field is present, but not valid MAC
426 */
427 protected boolean isMacAddress(String field, FieldPresence presence) {
428 JsonNode node = object.path(field);
429 return isValid(node, presence, node.isTextual() &&
430 MacAddress.valueOf(node.asText()) != null);
431 }
432
433 /**
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800434 * Indicates whether the specified field of a particular node holds a valid
435 * MAC address.
436 *
437 * @param objectNode JSON node
438 * @param field JSON field name
439 * @param presence specifies if field is optional or mandatory
440 * @return true if valid; false otherwise
441 * @throws IllegalArgumentException if field is present, but not valid MAC
442 */
443 protected boolean isMacAddress(ObjectNode objectNode, String field, FieldPresence presence) {
444 JsonNode node = objectNode.path(field);
445 return isValid(node, presence, node.isTextual() &&
446 MacAddress.valueOf(node.asText()) != null);
447 }
448
449 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800450 * Indicates whether the specified field holds a valid IP address.
451 *
452 * @param field JSON field name
453 * @param presence specifies if field is optional or mandatory
454 * @return true if valid; false otherwise
455 * @throws IllegalArgumentException if field is present, but not valid IP
456 */
457 protected boolean isIpAddress(String field, FieldPresence presence) {
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800458 return isIpAddress(object, field, presence);
459 }
460
461 /**
462 * Indicates whether the specified field of a particular node holds a valid
463 * IP address.
464 *
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800465 * @param objectNode node from whom to access the field
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800466 * @param field JSON field name
467 * @param presence specifies if field is optional or mandatory
468 * @return true if valid; false otherwise
469 * @throws IllegalArgumentException if field is present, but not valid IP
470 */
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800471 protected boolean isIpAddress(ObjectNode objectNode, String field, FieldPresence presence) {
472 JsonNode node = objectNode.path(field);
473 return isValid(node, presence, node.isTextual() &&
474 IpAddress.valueOf(node.asText()) != null);
475 }
476
477 /**
478 * Indicates whether the specified field holds a valid IP prefix.
479 *
480 * @param field JSON field name
481 * @param presence specifies if field is optional or mandatory
482 * @return true if valid; false otherwise
483 * @throws IllegalArgumentException if field is present, but not valid IP
484 * prefix
485 */
486 protected boolean isIpPrefix(String field, FieldPresence presence) {
487 return isIpPrefix(object, field, presence);
488 }
489
490 /**
491 * Indicates whether the specified field of a particular node holds a valid
492 * IP prefix.
493 *
494 * @param objectNode node from whom to access the field
495 * @param field JSON field name
496 * @param presence specifies if field is optional or mandatory
497 * @return true if valid; false otherwise
498 * @throws IllegalArgumentException if field is present, but not valid IP
499 * prefix
500 */
501 protected boolean isIpPrefix(ObjectNode objectNode, String field, FieldPresence presence) {
502 JsonNode node = objectNode.path(field);
503 return isValid(node, presence, node.isTextual() &&
504 IpPrefix.valueOf(node.asText()) != null);
505 }
506
507 /**
508 * Indicates whether the specified field holds a valid connect point string.
509 *
510 * @param field JSON field name
511 * @param presence specifies if field is optional or mandatory
512 * @return true if valid; false otherwise
513 * @throws IllegalArgumentException if field is present, but not valid
514 * connect point string representation
515 */
516 protected boolean isConnectPoint(String field, FieldPresence presence) {
517 return isConnectPoint(object, field, presence);
518 }
519
520 /**
521 * Indicates whether the specified field of a particular node holds a valid
522 * connect point string.
523 *
524 * @param objectNode JSON node
525 * @param field JSON field name
526 * @param presence specifies if field is optional or mandatory
527 * @return true if valid; false otherwise
528 * @throws IllegalArgumentException if field is present, but not valid
529 * connect point string representation
530 */
531 protected boolean isConnectPoint(ObjectNode objectNode, String field, FieldPresence presence) {
532 JsonNode node = objectNode.path(field);
533 return isValid(node, presence, node.isTextual() &&
534 ConnectPoint.deviceConnectPoint(node.asText()) != null);
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800535 }
536
537 /**
538 * Indicates whether the specified field holds a valid string value.
539 *
540 * @param field JSON field name
541 * @param presence specifies if field is optional or mandatory
542 * @param pattern optional regex pattern
543 * @return true if valid; false otherwise
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800544 * @throws IllegalArgumentException if field is present, but not valid string
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800545 */
546 protected boolean isString(String field, FieldPresence presence, String... pattern) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800547 return isString(object, field, presence, pattern);
548 }
549
550 /**
551 * Indicates whether the specified field on a particular node holds a valid
552 * string value.
553 *
554 * @param objectNode JSON node
555 * @param field JSON field name
556 * @param presence specifies if field is optional or mandatory
557 * @param pattern optional regex pattern
558 * @return true if valid; false otherwise
559 * @throws IllegalArgumentException if field is present, but not valid string
560 */
561 protected boolean isString(ObjectNode objectNode, String field,
562 FieldPresence presence, String... pattern) {
563 JsonNode node = objectNode.path(field);
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800564 return isValid(node, presence, node.isTextual() &&
565 (pattern.length > 0 && node.asText().matches(pattern[0]) || pattern.length < 1));
566 }
567
568 /**
569 * Indicates whether the specified field holds a valid number.
570 *
571 * @param field JSON field name
572 * @param presence specifies if field is optional or mandatory
573 * @param minMax optional min/max values
574 * @return true if valid; false otherwise
575 * @throws IllegalArgumentException if field is present, but not valid
576 */
577 protected boolean isNumber(String field, FieldPresence presence, long... minMax) {
578 JsonNode node = object.path(field);
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800579 return isValid(node, presence, node.isNumber() &&
580 (minMax.length > 0 && minMax[0] <= node.asLong() || minMax.length < 1) &&
581 (minMax.length > 1 && minMax[1] > node.asLong() || minMax.length < 2));
582 }
583 /**
584 * Indicates whether the specified field holds a valid number.
585 *
586 * @param field JSON field name
587 * @param presence specifies if field is optional or mandatory
588 * @param minMax optional min/max values
589 * @return true if valid; false otherwise
590 * @throws IllegalArgumentException if field is present, but not valid
591 */
592 protected boolean isNumber(String field, FieldPresence presence, double... minMax) {
593 JsonNode node = object.path(field);
594 return isValid(node, presence, node.isNumber() &&
595 (minMax.length > 0 && minMax[0] <= node.asDouble() || minMax.length < 1) &&
596 (minMax.length > 1 && minMax[1] > node.asDouble() || minMax.length < 2));
597 }
598
599 /**
600 * Indicates whether the specified field holds a valid integer.
601 *
602 * @param field JSON field name
603 * @param presence specifies if field is optional or mandatory
604 * @param minMax optional min/max values
605 * @return true if valid; false otherwise
606 * @throws IllegalArgumentException if field is present, but not valid
607 */
608 protected boolean isIntegralNumber(String field, FieldPresence presence, long... minMax) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800609 return isIntegralNumber(object, field, presence, minMax);
610 }
611
612 /**
613 * Indicates whether the specified field of a particular node holds a valid
614 * integer.
615 *
616 * @param objectNode JSON node
617 * @param field JSON field name
618 * @param presence specifies if field is optional or mandatory
619 * @param minMax optional min/max values
620 * @return true if valid; false otherwise
621 * @throws IllegalArgumentException if field is present, but not valid
622 */
623 protected boolean isIntegralNumber(ObjectNode objectNode, String field,
624 FieldPresence presence, long... minMax) {
625 JsonNode node = objectNode.path(field);
626
627 return isValid(node, presence, n -> {
628 long number = (node.isIntegralNumber()) ? n.asLong() : Long.parseLong(n.asText());
629 return (minMax.length > 0 && minMax[0] <= number || minMax.length < 1) &&
630 (minMax.length > 1 && minMax[1] > number || minMax.length < 2);
631 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800632 }
633
634 /**
635 * Indicates whether the specified field holds a valid decimal number.
636 *
637 * @param field JSON field name
638 * @param presence specifies if field is optional or mandatory
639 * @param minMax optional min/max values
640 * @return true if valid; false otherwise
641 * @throws IllegalArgumentException if field is present, but not valid
642 */
643 protected boolean isDecimal(String field, FieldPresence presence, double... minMax) {
644 JsonNode node = object.path(field);
645 return isValid(node, presence, (node.isDouble() || node.isFloat()) &&
646 (minMax.length > 0 && minMax[0] <= node.asDouble() || minMax.length < 1) &&
647 (minMax.length > 1 && minMax[1] > node.asDouble() || minMax.length < 2));
648 }
649
650 /**
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530651 * Indicates whether the specified field holds a valid boolean value.
652 *
653 * @param field JSON field name
654 * @param presence specifies if field is optional or mandatory
655 * @return true if valid; false otherwise
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530656 */
657 protected boolean isBoolean(String field, FieldPresence presence) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800658 return isBoolean(object, field, presence);
659 }
660
661 /**
662 * Indicates whether the specified field of a particular node holds a valid
663 * boolean value.
664 *
665 * @param objectNode JSON object node
666 * @param field JSON field name
667 * @param presence specifies if field is optional or mandatory
668 * @return true if valid; false otherwise
669 */
670 protected boolean isBoolean(ObjectNode objectNode, String field, FieldPresence presence) {
671 JsonNode node = objectNode.path(field);
672 return isValid(node, presence, node.isBoolean() ||
673 (node.isTextual() && isBooleanString(node.asText())));
674 }
675
676 /**
677 * Indicates whether a string holds a boolean literal value.
678 *
679 * @param str string to test
680 * @return true if the string contains "true" or "false" (case insensitive),
681 * otherwise false
682 */
683 private boolean isBooleanString(String str) {
684 return str.equalsIgnoreCase(TRUE_LITERAL) || str.equalsIgnoreCase(FALSE_LITERAL);
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530685 }
686
687 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800688 * Indicates whether the node is present and of correct value or not
689 * mandatory and absent.
690 *
691 * @param node JSON node
692 * @param presence specifies if field is optional or mandatory
693 * @param correctValue true if the value is correct
694 * @return true if the field is as expected
695 */
696 private boolean isValid(JsonNode node, FieldPresence presence, boolean correctValue) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800697 return isValid(node, presence, n -> correctValue);
698 }
699
700 /**
701 * Indicates whether the node is present and of correct value or not
702 * mandatory and absent.
703 *
704 * @param node JSON node
705 * @param presence specified if field is optional or mandatory
706 * @param validationFunction function which can be used to verify if the
707 * node has the correct value
708 * @return true if the field is as expected
709 */
710 private boolean isValid(JsonNode node, FieldPresence presence,
711 Function<JsonNode, Boolean> validationFunction) {
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800712 boolean isMandatory = presence == FieldPresence.MANDATORY;
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800713 if (isMandatory && validationFunction.apply(node)) {
714 return true;
715 }
716
717 if (!isMandatory && (node.isNull() || node.isMissingNode())) {
718 return true;
719 }
720
721 return validationFunction.apply(node);
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800722 }
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700723}