blob: 1b57cdbca42ba237a52f7970b3dbba7f055faca8 [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;
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.
Jonathan Hart54b83e82016-03-26 20:37:20 -0700105 * </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])
Jonathan Hart54b83e82016-03-26 20:37:20 -0700116 // isIntegralNumber(path, [min, max])
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800117 // isDecimal(path, [min, max])
118 // isMacAddress(path)
119 // isIpAddress(path)
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800120 // isIpPrefix(path)
121 // isConnectPoint(path)
Jonathan Hart54b83e82016-03-26 20:37:20 -0700122 // isTpPort(path)
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800123 return true;
124 }
125
126 /**
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700127 * Returns the specific subject to which this configuration pertains.
128 *
129 * @return configuration subject
130 */
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700131 public S subject() {
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700132 return subject;
133 }
134
135 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700136 * Returns the configuration key. This is primarily aimed for use in
137 * composite JSON trees in external representations and has no bearing on
138 * the internal behaviours.
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700139 *
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700140 * @return configuration key
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700141 */
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700142 public String key() {
143 return key;
144 }
145
146 /**
147 * Returns the JSON node that contains the configuration data.
148 *
149 * @return JSON node backing the configuration
150 */
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700151 public JsonNode node() {
Jonathan Harta8625482015-09-08 16:14:56 -0700152 return node;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700153 }
154
155 /**
156 * Applies any configuration changes made via this configuration.
Jonathan Hart54b83e82016-03-26 20:37:20 -0700157 * <p>
Charles Chan023a8982016-02-04 11:00:41 -0800158 * Not effective for detached configs.
Jonathan Hart54b83e82016-03-26 20:37:20 -0700159 * </p>
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700160 */
161 public void apply() {
Charles Chan023a8982016-02-04 11:00:41 -0800162 checkState(delegate != null, "Cannot apply detached config");
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700163 delegate.onApply(this);
164 }
165
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700166 // Miscellaneous helpers for interacting with JSON
167
168 /**
169 * Gets the specified property as a string.
170 *
171 * @param name property name
172 * @param defaultValue default value if property not set
173 * @return property value or default value
174 */
175 protected String get(String name, String defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700176 return object.path(name).asText(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700177 }
178
179 /**
180 * Sets the specified property as a string or clears it if null value given.
181 *
182 * @param name property name
183 * @param value new value or null to clear the property
184 * @return self
185 */
186 protected Config<S> setOrClear(String name, String value) {
187 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700188 object.put(name, value);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700189 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700190 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700191 }
192 return this;
193 }
194
195 /**
196 * Gets the specified property as a boolean.
197 *
198 * @param name property name
199 * @param defaultValue default value if property not set
200 * @return property value or default value
201 */
202 protected boolean get(String name, boolean defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700203 return object.path(name).asBoolean(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700204 }
205
206 /**
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800207 * Clears the specified property.
208 *
209 * @param name property name
210 * @return self
211 */
212 protected Config<S> clear(String name) {
213 object.remove(name);
214 return this;
215 }
216
217 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700218 * Sets the specified property as a boolean or clears it if null value given.
219 *
220 * @param name property name
221 * @param value new value or null to clear the property
222 * @return self
223 */
224 protected Config<S> setOrClear(String name, Boolean value) {
225 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700226 object.put(name, value.booleanValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700227 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700228 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700229 }
230 return this;
231 }
232
233 /**
Jonathan Hart111b42b2015-07-14 13:28:05 -0700234 * Gets the specified property as an integer.
235 *
236 * @param name property name
237 * @param defaultValue default value if property not set
238 * @return property value or default value
239 */
240 protected int get(String name, int defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700241 return object.path(name).asInt(defaultValue);
Jonathan Hart111b42b2015-07-14 13:28:05 -0700242 }
243
244 /**
245 * Sets the specified property as an integer or clears it if null value given.
246 *
247 * @param name property name
248 * @param value new value or null to clear the property
249 * @return self
250 */
251 protected Config<S> setOrClear(String name, Integer value) {
252 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700253 object.put(name, value.intValue());
Jonathan Hart111b42b2015-07-14 13:28:05 -0700254 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700255 object.remove(name);
Jonathan Hart111b42b2015-07-14 13:28:05 -0700256 }
257 return this;
258 }
259
260 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700261 * Gets the specified property as a long.
262 *
263 * @param name property name
264 * @param defaultValue default value if property not set
265 * @return property value or default value
266 */
267 protected long get(String name, long defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700268 return object.path(name).asLong(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700269 }
270
271 /**
272 * Sets the specified property as a long or clears it if null value given.
273 *
274 * @param name property name
275 * @param value new value or null to clear the property
276 * @return self
277 */
278 protected Config<S> setOrClear(String name, Long value) {
279 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700280 object.put(name, value.longValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700281 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700282 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700283 }
284 return this;
285 }
286
287 /**
288 * Gets the specified property as a double.
289 *
290 * @param name property name
291 * @param defaultValue default value if property not set
292 * @return property value or default value
293 */
294 protected double get(String name, double defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700295 return object.path(name).asDouble(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700296 }
297
298 /**
299 * Sets the specified property as a double or clears it if null value given.
300 *
301 * @param name property name
302 * @param value new value or null to clear the property
303 * @return self
304 */
305 protected Config<S> setOrClear(String name, Double value) {
306 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700307 object.put(name, value.doubleValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700308 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700309 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700310 }
311 return this;
312 }
313
314 /**
315 * Gets the specified property as an enum.
316 *
317 * @param name property name
318 * @param defaultValue default value if property not set
319 * @param enumClass the enum class
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700320 * @param <E> type of enum
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700321 * @return property value or default value
322 */
323 protected <E extends Enum<E>> E get(String name, E defaultValue, Class<E> enumClass) {
Thomas Vachuska2d6c5992016-07-01 12:42:26 +0200324 if (defaultValue != null) {
Charles Chand5c3b932016-09-22 17:49:52 -0700325 return Enum.valueOf(enumClass, object.path(name).asText(defaultValue.toString()));
Thomas Vachuska2d6c5992016-07-01 12:42:26 +0200326 }
327
328 JsonNode node = object.get(name);
329 return node == null ? null : Enum.valueOf(enumClass, node.asText());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700330 }
331
332 /**
333 * Sets the specified property as a double or clears it if null value given.
334 *
335 * @param name property name
336 * @param value new value or null to clear the property
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700337 * @param <E> type of enum
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700338 * @return self
339 */
340 protected <E extends Enum> Config<S> setOrClear(String name, E value) {
341 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700342 object.put(name, value.toString());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700343 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700344 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700345 }
346 return this;
347 }
Jonathan Hart111b42b2015-07-14 13:28:05 -0700348
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700349 /**
350 * Gets the specified array property as a list of items.
351 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700352 * @param name property name
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700353 * @param function mapper from string to item
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700354 * @param <T> type of item
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700355 * @return list of items
356 */
357 protected <T> List<T> getList(String name, Function<String, T> function) {
358 List<T> list = Lists.newArrayList();
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700359 ArrayNode arrayNode = (ArrayNode) object.path(name);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700360 arrayNode.forEach(i -> list.add(function.apply(i.asText())));
361 return list;
362 }
363
364 /**
Naoki Shiota399a0b32015-11-15 20:36:13 -0600365 * Gets the specified array property as a list of items.
366 *
367 * @param name property name
368 * @param function mapper from string to item
369 * @param defaultValue default value if property not set
370 * @param <T> type of item
371 * @return list of items
372 */
373 protected <T> List<T> getList(String name, Function<String, T> function, List<T> defaultValue) {
374 List<T> list = Lists.newArrayList();
375 JsonNode jsonNode = object.path(name);
376 if (jsonNode.isMissingNode()) {
377 return defaultValue;
378 }
379 ArrayNode arrayNode = (ArrayNode) jsonNode;
380 arrayNode.forEach(i -> list.add(function.apply(i.asText())));
381 return list;
382 }
383
384 /**
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700385 * Sets the specified property as an array of items in a given collection or
386 * clears it if null is given.
387 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700388 * @param name propertyName
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700389 * @param collection collection of items
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700390 * @param <T> type of items
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700391 * @return self
392 */
393 protected <T> Config<S> setOrClear(String name, Collection<T> collection) {
394 if (collection == null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700395 object.remove(name);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700396 } else {
397 ArrayNode arrayNode = mapper.createArrayNode();
398 collection.forEach(i -> arrayNode.add(i.toString()));
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700399 object.set(name, arrayNode);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700400 }
401 return this;
402 }
403
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800404 /**
405 * Indicates whether only the specified fields are present in the backing JSON.
406 *
407 * @param allowedFields allowed field names
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700408 * @return true if only allowedFields are present; false otherwise
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800409 */
410 protected boolean hasOnlyFields(String... allowedFields) {
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800411 return hasOnlyFields(object, allowedFields);
412 }
413
414 /**
415 * Indicates whether only the specified fields are present in a particular
416 * JSON object.
417 *
418 * @param node node whose fields to check
419 * @param allowedFields allowed field names
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700420 * @return true if only allowedFields are present; false otherwise
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800421 */
422 protected boolean hasOnlyFields(ObjectNode node, String... allowedFields) {
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800423 Set<String> fields = ImmutableSet.copyOf(allowedFields);
Jonathan Hart54b83e82016-03-26 20:37:20 -0700424 node.fieldNames().forEachRemaining(f -> {
425 if (!fields.contains(f)) {
426 throw new InvalidFieldException(f, "Field is not allowed");
427 }
428 });
429 return true;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800430 }
431
432 /**
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700433 * Indicates whether all specified fields are present in the backing JSON.
434 *
435 * @param mandatoryFields mandatory field names
436 * @return true if all mandatory fields are present; false otherwise
437 */
438 protected boolean hasFields(String... mandatoryFields) {
439 return hasFields(object, mandatoryFields);
440 }
441
442 /**
443 * Indicates whether all specified fields are present in a particular
444 * JSON object.
445 *
446 * @param node node whose fields to check
447 * @param mandatoryFields mandatory field names
448 * @return true if all mandatory fields are present; false otherwise
449 */
450 protected boolean hasFields(ObjectNode node, String... mandatoryFields) {
451 Set<String> fields = ImmutableSet.copyOf(mandatoryFields);
Jonathan Hart54b83e82016-03-26 20:37:20 -0700452 fields.forEach(f -> {
453 if (node.path(f).isMissingNode()) {
454 throw new InvalidFieldException(f, "Mandatory field is not present");
455 }
456 });
457 return true;
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700458 }
459
460 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800461 * Indicates whether the specified field holds a valid MAC address.
462 *
463 * @param field JSON field name
464 * @param presence specifies if field is optional or mandatory
465 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700466 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800467 */
468 protected boolean isMacAddress(String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700469 return isMacAddress(object, field, presence);
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800470 }
471
472 /**
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800473 * Indicates whether the specified field of a particular node holds a valid
474 * MAC address.
475 *
476 * @param objectNode JSON node
477 * @param field JSON field name
478 * @param presence specifies if field is optional or mandatory
479 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700480 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800481 */
482 protected boolean isMacAddress(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700483 return isValid(objectNode, field, presence, n -> {
484 MacAddress.valueOf(n.asText());
485 return true;
486 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800487 }
488
489 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800490 * Indicates whether the specified field holds a valid IP address.
491 *
492 * @param field JSON field name
493 * @param presence specifies if field is optional or mandatory
494 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700495 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800496 */
497 protected boolean isIpAddress(String field, FieldPresence presence) {
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800498 return isIpAddress(object, field, presence);
499 }
500
501 /**
502 * Indicates whether the specified field of a particular node holds a valid
503 * IP address.
504 *
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800505 * @param objectNode node from whom to access the field
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800506 * @param field JSON field name
507 * @param presence specifies if field is optional or mandatory
508 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700509 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800510 */
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800511 protected boolean isIpAddress(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700512 return isValid(objectNode, field, presence, n -> {
513 IpAddress.valueOf(n.asText());
514 return true;
515 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800516 }
517
518 /**
519 * Indicates whether the specified field holds a valid IP prefix.
520 *
521 * @param field JSON field name
522 * @param presence specifies if field is optional or mandatory
523 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700524 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800525 */
526 protected boolean isIpPrefix(String field, FieldPresence presence) {
527 return isIpPrefix(object, field, presence);
528 }
529
530 /**
531 * Indicates whether the specified field of a particular node holds a valid
532 * IP prefix.
533 *
534 * @param objectNode node from whom to access the field
535 * @param field JSON field name
536 * @param presence specifies if field is optional or mandatory
537 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700538 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800539 */
540 protected boolean isIpPrefix(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700541 return isValid(objectNode, field, presence, n -> {
542 IpPrefix.valueOf(n.asText());
543 return true;
544 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800545 }
546
547 /**
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700548 * Indicates whether the specified field holds a valid transport layer port.
549 *
550 * @param field JSON field name
551 * @param presence specifies if field is optional or mandatory
552 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700553 * @throws InvalidFieldException if the field is present but not valid
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700554 */
555 protected boolean isTpPort(String field, FieldPresence presence) {
556 return isTpPort(object, field, presence);
557 }
558
559 /**
560 * Indicates whether the specified field of a particular node holds a valid
561 * transport layer port.
562 *
563 * @param objectNode node from whom to access the field
564 * @param field JSON field name
565 * @param presence specifies if field is optional or mandatory
566 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700567 * @throws InvalidFieldException if the field is present but not valid
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700568 */
569 protected boolean isTpPort(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700570 return isValid(objectNode, field, presence, n -> {
571 TpPort.tpPort(n.asInt());
572 return true;
573 });
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700574 }
575
576 /**
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800577 * Indicates whether the specified field holds a valid connect point string.
578 *
579 * @param field JSON field name
580 * @param presence specifies if field is optional or mandatory
581 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700582 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800583 */
584 protected boolean isConnectPoint(String field, FieldPresence presence) {
585 return isConnectPoint(object, field, presence);
586 }
587
588 /**
589 * Indicates whether the specified field of a particular node holds a valid
590 * connect point string.
591 *
592 * @param objectNode JSON node
593 * @param field JSON field name
594 * @param presence specifies if field is optional or mandatory
595 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700596 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800597 */
598 protected boolean isConnectPoint(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700599 return isValid(objectNode, field, presence, n -> {
600 ConnectPoint.deviceConnectPoint(n.asText());
601 return true;
602 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800603 }
604
605 /**
606 * Indicates whether the specified field holds a valid string value.
607 *
608 * @param field JSON field name
609 * @param presence specifies if field is optional or mandatory
610 * @param pattern optional regex pattern
611 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700612 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800613 */
614 protected boolean isString(String field, FieldPresence presence, String... pattern) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800615 return isString(object, field, presence, pattern);
616 }
617
618 /**
619 * Indicates whether the specified field on a particular node holds a valid
620 * string value.
621 *
622 * @param objectNode JSON node
623 * @param field JSON field name
624 * @param presence specifies if field is optional or mandatory
625 * @param pattern optional regex pattern
626 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700627 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800628 */
629 protected boolean isString(ObjectNode objectNode, String field,
630 FieldPresence presence, String... pattern) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700631 return isValid(objectNode, field, presence, (node) -> {
632 if (!(node.isTextual() &&
633 (pattern.length > 0 && node.asText().matches(pattern[0]) || pattern.length < 1))) {
634 fail("Invalid string value");
635 }
636 return true;
637 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800638 }
639
640 /**
641 * Indicates whether the specified field holds a valid number.
642 *
643 * @param field JSON field name
644 * @param presence specifies if field is optional or mandatory
645 * @param minMax optional min/max values
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
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800648 */
649 protected boolean isNumber(String field, FieldPresence presence, long... minMax) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700650 return isNumber(object, field, presence, minMax);
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800651 }
Jonathan Hart54b83e82016-03-26 20:37:20 -0700652
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800653 /**
Jonathan Hart54b83e82016-03-26 20:37:20 -0700654 * Indicates whether the specified field of a particular node holds a
655 * valid number.
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800656 *
Jonathan Hart54b83e82016-03-26 20:37:20 -0700657 * @param objectNode JSON object
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800658 * @param field JSON field name
659 * @param presence specifies if field is optional or mandatory
660 * @param minMax optional min/max values
661 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700662 * @throws InvalidFieldException if the field is present but not valid
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800663 */
Jonathan Hart54b83e82016-03-26 20:37:20 -0700664 protected boolean isNumber(ObjectNode objectNode, String field,
665 FieldPresence presence, long... minMax) {
666 return isValid(objectNode, field, presence, n -> {
667 long number = (n.isNumber()) ? n.asLong() : Long.parseLong(n.asText());
668 if (minMax.length > 1) {
669 verifyRange(number, minMax[0], minMax[1]);
670 } else if (minMax.length > 0) {
671 verifyRange(number, minMax[0]);
672 }
673 return true;
674 });
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800675 }
676
677 /**
678 * Indicates whether the specified field holds a valid integer.
679 *
680 * @param field JSON field name
681 * @param presence specifies if field is optional or mandatory
682 * @param minMax optional min/max values
683 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700684 * @throws InvalidFieldException if the field is present but not valid
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800685 */
686 protected boolean isIntegralNumber(String field, FieldPresence presence, long... minMax) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800687 return isIntegralNumber(object, field, presence, minMax);
688 }
689
690 /**
691 * Indicates whether the specified field of a particular node holds a valid
692 * integer.
693 *
694 * @param objectNode JSON node
695 * @param field JSON field name
696 * @param presence specifies if field is optional or mandatory
697 * @param minMax optional min/max values
698 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700699 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800700 */
701 protected boolean isIntegralNumber(ObjectNode objectNode, String field,
702 FieldPresence presence, long... minMax) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700703 return isValid(objectNode, field, presence, n -> {
704 long number = (n.isIntegralNumber()) ? n.asLong() : Long.parseLong(n.asText());
705 if (minMax.length > 1) {
706 verifyRange(number, minMax[0], minMax[1]);
707 } else if (minMax.length > 0) {
708 verifyRange(number, minMax[0]);
709 }
710 return true;
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800711 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800712 }
713
714 /**
715 * Indicates whether the specified field holds a valid decimal number.
716 *
717 * @param field JSON field name
718 * @param presence specifies if field is optional or mandatory
719 * @param minMax optional min/max values
720 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700721 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800722 */
723 protected boolean isDecimal(String field, FieldPresence presence, double... minMax) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700724 return isDecimal(object, field, presence, minMax);
725 }
726
727 /**
728 * Indicates whether the specified field of a particular node holds a valid
729 * decimal number.
730 *
731 * @param objectNode JSON node
732 * @param field JSON field name
733 * @param presence specifies if field is optional or mandatory
734 * @param minMax optional min/max values
735 * @return true if valid; false otherwise
736 * @throws InvalidFieldException if the field is present but not valid
737 */
738 protected boolean isDecimal(ObjectNode objectNode, String field,
739 FieldPresence presence, double... minMax) {
740 return isValid(objectNode, field, presence, n -> {
741 double number = (n.isDouble()) ? n.asDouble() : Double.parseDouble(n.asText());
742 if (minMax.length > 1) {
743 verifyRange(number, minMax[0], minMax[1]);
744 } else if (minMax.length > 0) {
745 verifyRange(number, minMax[0]);
746 }
747 return true;
748 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800749 }
750
751 /**
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530752 * Indicates whether the specified field holds a valid boolean value.
753 *
754 * @param field JSON field name
755 * @param presence specifies if field is optional or mandatory
756 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700757 * @throws InvalidFieldException if the field is present but not valid
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530758 */
759 protected boolean isBoolean(String field, FieldPresence presence) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800760 return isBoolean(object, field, presence);
761 }
762
763 /**
764 * Indicates whether the specified field of a particular node holds a valid
765 * boolean value.
766 *
767 * @param objectNode JSON object node
768 * @param field JSON field name
769 * @param presence specifies if field is optional or mandatory
770 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700771 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800772 */
773 protected boolean isBoolean(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700774 return isValid(objectNode, field, presence, n -> {
775 if (!(n.isBoolean() || (n.isTextual() && isBooleanString(n.asText())))) {
776 fail("Field is not a boolean value");
777 }
778 return true;
779 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800780 }
781
782 /**
783 * Indicates whether a string holds a boolean literal value.
784 *
785 * @param str string to test
786 * @return true if the string contains "true" or "false" (case insensitive),
787 * otherwise false
788 */
789 private boolean isBooleanString(String str) {
790 return str.equalsIgnoreCase(TRUE_LITERAL) || str.equalsIgnoreCase(FALSE_LITERAL);
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530791 }
792
793 /**
Jonathan Hart54b83e82016-03-26 20:37:20 -0700794 * Indicates whether a field in the node is present and of correct value or
795 * not mandatory and absent.
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800796 *
Jonathan Hart54b83e82016-03-26 20:37:20 -0700797 * @param objectNode JSON object node containing field to validate
798 * @param field name of field to validate
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800799 * @param presence specified if field is optional or mandatory
800 * @param validationFunction function which can be used to verify if the
801 * node has the correct value
802 * @return true if the field is as expected
Jonathan Hart54b83e82016-03-26 20:37:20 -0700803 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800804 */
Jonathan Hart54b83e82016-03-26 20:37:20 -0700805 private boolean isValid(ObjectNode objectNode, String field, FieldPresence presence,
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800806 Function<JsonNode, Boolean> validationFunction) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700807 JsonNode node = objectNode.path(field);
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800808 boolean isMandatory = presence == FieldPresence.MANDATORY;
Jonathan Hart54b83e82016-03-26 20:37:20 -0700809 if (isMandatory && node.isMissingNode()) {
810 throw new InvalidFieldException(field, "Mandatory field not present");
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800811 }
812
813 if (!isMandatory && (node.isNull() || node.isMissingNode())) {
814 return true;
815 }
816
Jonathan Hart54b83e82016-03-26 20:37:20 -0700817 try {
818 if (validationFunction.apply(node)) {
819 return true;
820 } else {
821 throw new InvalidFieldException(field, "Validation error");
822 }
823 } catch (IllegalArgumentException e) {
824 throw new InvalidFieldException(field, e);
825 }
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800826 }
Jonathan Hart54b83e82016-03-26 20:37:20 -0700827
828 private static void fail(String message) {
829 throw new IllegalArgumentException(message);
830 }
831
832 private static <N extends Comparable> void verifyRange(N num, N min) {
833 if (num.compareTo(min) < 0) {
834 fail("Field must be greater than " + min);
835 }
836 }
837
838 private static <N extends Comparable> void verifyRange(N num, N min, N max) {
839 verifyRange(num, min);
840
841 if (num.compareTo(max) > 0) {
842 fail("Field must be less than " + max);
843 }
844 }
845
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700846}