blob: 4aa3d8d29bcc462e27dff2e957fddaed8696d0a8 [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 Vachuska0a400ea2015-09-04 11:25:03 -0700324 return Enum.valueOf(enumClass, object.path(name).asText(defaultValue.toString()));
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700325 }
326
327 /**
328 * Sets the specified property as a double or clears it if null value given.
329 *
330 * @param name property name
331 * @param value new value or null to clear the property
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700332 * @param <E> type of enum
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700333 * @return self
334 */
335 protected <E extends Enum> Config<S> setOrClear(String name, E value) {
336 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700337 object.put(name, value.toString());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700338 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700339 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700340 }
341 return this;
342 }
Jonathan Hart111b42b2015-07-14 13:28:05 -0700343
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700344 /**
345 * Gets the specified array property as a list of items.
346 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700347 * @param name property name
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700348 * @param function mapper from string to item
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700349 * @param <T> type of item
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700350 * @return list of items
351 */
352 protected <T> List<T> getList(String name, Function<String, T> function) {
353 List<T> list = Lists.newArrayList();
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700354 ArrayNode arrayNode = (ArrayNode) object.path(name);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700355 arrayNode.forEach(i -> list.add(function.apply(i.asText())));
356 return list;
357 }
358
359 /**
Naoki Shiota399a0b32015-11-15 20:36:13 -0600360 * Gets the specified array property as a list of items.
361 *
362 * @param name property name
363 * @param function mapper from string to item
364 * @param defaultValue default value if property not set
365 * @param <T> type of item
366 * @return list of items
367 */
368 protected <T> List<T> getList(String name, Function<String, T> function, List<T> defaultValue) {
369 List<T> list = Lists.newArrayList();
370 JsonNode jsonNode = object.path(name);
371 if (jsonNode.isMissingNode()) {
372 return defaultValue;
373 }
374 ArrayNode arrayNode = (ArrayNode) jsonNode;
375 arrayNode.forEach(i -> list.add(function.apply(i.asText())));
376 return list;
377 }
378
379 /**
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700380 * Sets the specified property as an array of items in a given collection or
381 * clears it if null is given.
382 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700383 * @param name propertyName
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700384 * @param collection collection of items
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700385 * @param <T> type of items
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700386 * @return self
387 */
388 protected <T> Config<S> setOrClear(String name, Collection<T> collection) {
389 if (collection == null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700390 object.remove(name);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700391 } else {
392 ArrayNode arrayNode = mapper.createArrayNode();
393 collection.forEach(i -> arrayNode.add(i.toString()));
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700394 object.set(name, arrayNode);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700395 }
396 return this;
397 }
398
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800399 /**
400 * Indicates whether only the specified fields are present in the backing JSON.
401 *
402 * @param allowedFields allowed field names
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700403 * @return true if only allowedFields are present; false otherwise
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800404 */
405 protected boolean hasOnlyFields(String... allowedFields) {
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800406 return hasOnlyFields(object, allowedFields);
407 }
408
409 /**
410 * Indicates whether only the specified fields are present in a particular
411 * JSON object.
412 *
413 * @param node node whose fields to check
414 * @param allowedFields allowed field names
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700415 * @return true if only allowedFields are present; false otherwise
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800416 */
417 protected boolean hasOnlyFields(ObjectNode node, String... allowedFields) {
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800418 Set<String> fields = ImmutableSet.copyOf(allowedFields);
Jonathan Hart54b83e82016-03-26 20:37:20 -0700419 node.fieldNames().forEachRemaining(f -> {
420 if (!fields.contains(f)) {
421 throw new InvalidFieldException(f, "Field is not allowed");
422 }
423 });
424 return true;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800425 }
426
427 /**
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700428 * Indicates whether all specified fields are present in the backing JSON.
429 *
430 * @param mandatoryFields mandatory field names
431 * @return true if all mandatory fields are present; false otherwise
432 */
433 protected boolean hasFields(String... mandatoryFields) {
434 return hasFields(object, mandatoryFields);
435 }
436
437 /**
438 * Indicates whether all specified fields are present in a particular
439 * JSON object.
440 *
441 * @param node node whose fields to check
442 * @param mandatoryFields mandatory field names
443 * @return true if all mandatory fields are present; false otherwise
444 */
445 protected boolean hasFields(ObjectNode node, String... mandatoryFields) {
446 Set<String> fields = ImmutableSet.copyOf(mandatoryFields);
Jonathan Hart54b83e82016-03-26 20:37:20 -0700447 fields.forEach(f -> {
448 if (node.path(f).isMissingNode()) {
449 throw new InvalidFieldException(f, "Mandatory field is not present");
450 }
451 });
452 return true;
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700453 }
454
455 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800456 * Indicates whether the specified field holds a valid MAC address.
457 *
458 * @param field JSON field name
459 * @param presence specifies if field is optional or mandatory
460 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700461 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800462 */
463 protected boolean isMacAddress(String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700464 return isMacAddress(object, field, presence);
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800465 }
466
467 /**
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800468 * Indicates whether the specified field of a particular node holds a valid
469 * MAC address.
470 *
471 * @param objectNode JSON node
472 * @param field JSON field name
473 * @param presence specifies if field is optional or mandatory
474 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700475 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800476 */
477 protected boolean isMacAddress(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700478 return isValid(objectNode, field, presence, n -> {
479 MacAddress.valueOf(n.asText());
480 return true;
481 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800482 }
483
484 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800485 * Indicates whether the specified field holds a valid IP address.
486 *
487 * @param field JSON field name
488 * @param presence specifies if field is optional or mandatory
489 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700490 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800491 */
492 protected boolean isIpAddress(String field, FieldPresence presence) {
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800493 return isIpAddress(object, field, presence);
494 }
495
496 /**
497 * Indicates whether the specified field of a particular node holds a valid
498 * IP address.
499 *
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800500 * @param objectNode node from whom to access the field
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800501 * @param field JSON field name
502 * @param presence specifies if field is optional or mandatory
503 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700504 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800505 */
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800506 protected boolean isIpAddress(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700507 return isValid(objectNode, field, presence, n -> {
508 IpAddress.valueOf(n.asText());
509 return true;
510 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800511 }
512
513 /**
514 * Indicates whether the specified field holds a valid IP prefix.
515 *
516 * @param field JSON field name
517 * @param presence specifies if field is optional or mandatory
518 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700519 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800520 */
521 protected boolean isIpPrefix(String field, FieldPresence presence) {
522 return isIpPrefix(object, field, presence);
523 }
524
525 /**
526 * Indicates whether the specified field of a particular node holds a valid
527 * IP prefix.
528 *
529 * @param objectNode node from whom to access the field
530 * @param field JSON field name
531 * @param presence specifies if field is optional or mandatory
532 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700533 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800534 */
535 protected boolean isIpPrefix(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700536 return isValid(objectNode, field, presence, n -> {
537 IpPrefix.valueOf(n.asText());
538 return true;
539 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800540 }
541
542 /**
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700543 * Indicates whether the specified field holds a valid transport layer port.
544 *
545 * @param field JSON field name
546 * @param presence specifies if field is optional or mandatory
547 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700548 * @throws InvalidFieldException if the field is present but not valid
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700549 */
550 protected boolean isTpPort(String field, FieldPresence presence) {
551 return isTpPort(object, field, presence);
552 }
553
554 /**
555 * Indicates whether the specified field of a particular node holds a valid
556 * transport layer port.
557 *
558 * @param objectNode node from whom to access the field
559 * @param field JSON field name
560 * @param presence specifies if field is optional or mandatory
561 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700562 * @throws InvalidFieldException if the field is present but not valid
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700563 */
564 protected boolean isTpPort(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700565 return isValid(objectNode, field, presence, n -> {
566 TpPort.tpPort(n.asInt());
567 return true;
568 });
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700569 }
570
571 /**
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800572 * Indicates whether the specified field holds a valid connect point string.
573 *
574 * @param field JSON field name
575 * @param presence specifies if field is optional or mandatory
576 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700577 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800578 */
579 protected boolean isConnectPoint(String field, FieldPresence presence) {
580 return isConnectPoint(object, field, presence);
581 }
582
583 /**
584 * Indicates whether the specified field of a particular node holds a valid
585 * connect point string.
586 *
587 * @param objectNode JSON node
588 * @param field JSON field name
589 * @param presence specifies if field is optional or mandatory
590 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700591 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800592 */
593 protected boolean isConnectPoint(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700594 return isValid(objectNode, field, presence, n -> {
595 ConnectPoint.deviceConnectPoint(n.asText());
596 return true;
597 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800598 }
599
600 /**
601 * Indicates whether the specified field holds a valid string value.
602 *
603 * @param field JSON field name
604 * @param presence specifies if field is optional or mandatory
605 * @param pattern optional regex pattern
606 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700607 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800608 */
609 protected boolean isString(String field, FieldPresence presence, String... pattern) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800610 return isString(object, field, presence, pattern);
611 }
612
613 /**
614 * Indicates whether the specified field on a particular node holds a valid
615 * string value.
616 *
617 * @param objectNode JSON node
618 * @param field JSON field name
619 * @param presence specifies if field is optional or mandatory
620 * @param pattern optional regex pattern
621 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700622 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800623 */
624 protected boolean isString(ObjectNode objectNode, String field,
625 FieldPresence presence, String... pattern) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700626 return isValid(objectNode, field, presence, (node) -> {
627 if (!(node.isTextual() &&
628 (pattern.length > 0 && node.asText().matches(pattern[0]) || pattern.length < 1))) {
629 fail("Invalid string value");
630 }
631 return true;
632 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800633 }
634
635 /**
636 * Indicates whether the specified field holds a valid number.
637 *
638 * @param field JSON field name
639 * @param presence specifies if field is optional or mandatory
640 * @param minMax optional min/max values
641 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700642 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800643 */
644 protected boolean isNumber(String field, FieldPresence presence, long... minMax) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700645 return isNumber(object, field, presence, minMax);
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800646 }
Jonathan Hart54b83e82016-03-26 20:37:20 -0700647
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800648 /**
Jonathan Hart54b83e82016-03-26 20:37:20 -0700649 * Indicates whether the specified field of a particular node holds a
650 * valid number.
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800651 *
Jonathan Hart54b83e82016-03-26 20:37:20 -0700652 * @param objectNode JSON object
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800653 * @param field JSON field name
654 * @param presence specifies if field is optional or mandatory
655 * @param minMax optional min/max values
656 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700657 * @throws InvalidFieldException if the field is present but not valid
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800658 */
Jonathan Hart54b83e82016-03-26 20:37:20 -0700659 protected boolean isNumber(ObjectNode objectNode, String field,
660 FieldPresence presence, long... minMax) {
661 return isValid(objectNode, field, presence, n -> {
662 long number = (n.isNumber()) ? n.asLong() : Long.parseLong(n.asText());
663 if (minMax.length > 1) {
664 verifyRange(number, minMax[0], minMax[1]);
665 } else if (minMax.length > 0) {
666 verifyRange(number, minMax[0]);
667 }
668 return true;
669 });
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800670 }
671
672 /**
673 * Indicates whether the specified field holds a valid integer.
674 *
675 * @param field JSON field name
676 * @param presence specifies if field is optional or mandatory
677 * @param minMax optional min/max values
678 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700679 * @throws InvalidFieldException if the field is present but not valid
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800680 */
681 protected boolean isIntegralNumber(String field, FieldPresence presence, long... minMax) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800682 return isIntegralNumber(object, field, presence, minMax);
683 }
684
685 /**
686 * Indicates whether the specified field of a particular node holds a valid
687 * integer.
688 *
689 * @param objectNode JSON node
690 * @param field JSON field name
691 * @param presence specifies if field is optional or mandatory
692 * @param minMax optional min/max values
693 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700694 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800695 */
696 protected boolean isIntegralNumber(ObjectNode objectNode, String field,
697 FieldPresence presence, long... minMax) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700698 return isValid(objectNode, field, presence, n -> {
699 long number = (n.isIntegralNumber()) ? n.asLong() : Long.parseLong(n.asText());
700 if (minMax.length > 1) {
701 verifyRange(number, minMax[0], minMax[1]);
702 } else if (minMax.length > 0) {
703 verifyRange(number, minMax[0]);
704 }
705 return true;
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800706 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800707 }
708
709 /**
710 * Indicates whether the specified field holds a valid decimal number.
711 *
712 * @param field JSON field name
713 * @param presence specifies if field is optional or mandatory
714 * @param minMax optional min/max values
715 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700716 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800717 */
718 protected boolean isDecimal(String field, FieldPresence presence, double... minMax) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700719 return isDecimal(object, field, presence, minMax);
720 }
721
722 /**
723 * Indicates whether the specified field of a particular node holds a valid
724 * decimal number.
725 *
726 * @param objectNode JSON node
727 * @param field JSON field name
728 * @param presence specifies if field is optional or mandatory
729 * @param minMax optional min/max values
730 * @return true if valid; false otherwise
731 * @throws InvalidFieldException if the field is present but not valid
732 */
733 protected boolean isDecimal(ObjectNode objectNode, String field,
734 FieldPresence presence, double... minMax) {
735 return isValid(objectNode, field, presence, n -> {
736 double number = (n.isDouble()) ? n.asDouble() : Double.parseDouble(n.asText());
737 if (minMax.length > 1) {
738 verifyRange(number, minMax[0], minMax[1]);
739 } else if (minMax.length > 0) {
740 verifyRange(number, minMax[0]);
741 }
742 return true;
743 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800744 }
745
746 /**
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530747 * Indicates whether the specified field holds a valid boolean value.
748 *
749 * @param field JSON field name
750 * @param presence specifies if field is optional or mandatory
751 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700752 * @throws InvalidFieldException if the field is present but not valid
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530753 */
754 protected boolean isBoolean(String field, FieldPresence presence) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800755 return isBoolean(object, field, presence);
756 }
757
758 /**
759 * Indicates whether the specified field of a particular node holds a valid
760 * boolean value.
761 *
762 * @param objectNode JSON object node
763 * @param field JSON field name
764 * @param presence specifies if field is optional or mandatory
765 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700766 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800767 */
768 protected boolean isBoolean(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700769 return isValid(objectNode, field, presence, n -> {
770 if (!(n.isBoolean() || (n.isTextual() && isBooleanString(n.asText())))) {
771 fail("Field is not a boolean value");
772 }
773 return true;
774 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800775 }
776
777 /**
778 * Indicates whether a string holds a boolean literal value.
779 *
780 * @param str string to test
781 * @return true if the string contains "true" or "false" (case insensitive),
782 * otherwise false
783 */
784 private boolean isBooleanString(String str) {
785 return str.equalsIgnoreCase(TRUE_LITERAL) || str.equalsIgnoreCase(FALSE_LITERAL);
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530786 }
787
788 /**
Jonathan Hart54b83e82016-03-26 20:37:20 -0700789 * Indicates whether a field in the node is present and of correct value or
790 * not mandatory and absent.
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800791 *
Jonathan Hart54b83e82016-03-26 20:37:20 -0700792 * @param objectNode JSON object node containing field to validate
793 * @param field name of field to validate
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800794 * @param presence specified if field is optional or mandatory
795 * @param validationFunction function which can be used to verify if the
796 * node has the correct value
797 * @return true if the field is as expected
Jonathan Hart54b83e82016-03-26 20:37:20 -0700798 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800799 */
Jonathan Hart54b83e82016-03-26 20:37:20 -0700800 private boolean isValid(ObjectNode objectNode, String field, FieldPresence presence,
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800801 Function<JsonNode, Boolean> validationFunction) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700802 JsonNode node = objectNode.path(field);
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800803 boolean isMandatory = presence == FieldPresence.MANDATORY;
Jonathan Hart54b83e82016-03-26 20:37:20 -0700804 if (isMandatory && node.isMissingNode()) {
805 throw new InvalidFieldException(field, "Mandatory field not present");
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800806 }
807
808 if (!isMandatory && (node.isNull() || node.isMissingNode())) {
809 return true;
810 }
811
Jonathan Hart54b83e82016-03-26 20:37:20 -0700812 try {
813 if (validationFunction.apply(node)) {
814 return true;
815 } else {
816 throw new InvalidFieldException(field, "Validation error");
817 }
818 } catch (IllegalArgumentException e) {
819 throw new InvalidFieldException(field, e);
820 }
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800821 }
Jonathan Hart54b83e82016-03-26 20:37:20 -0700822
823 private static void fail(String message) {
824 throw new IllegalArgumentException(message);
825 }
826
827 private static <N extends Comparable> void verifyRange(N num, N min) {
828 if (num.compareTo(min) < 0) {
829 fail("Field must be greater than " + min);
830 }
831 }
832
833 private static <N extends Comparable> void verifyRange(N num, N min, N max) {
834 verifyRange(num, min);
835
836 if (num.compareTo(max) > 0) {
837 fail("Field must be less than " + max);
838 }
839 }
840
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700841}