blob: 86129a6e24bbbf5c48969a11bb579c9c19ed559a [file] [log] [blame]
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Ray Milkeya4122362015-08-18 15:19:08 -070016package org.onosproject.net.config;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070017
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070018import com.fasterxml.jackson.databind.JsonNode;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070019import com.fasterxml.jackson.databind.ObjectMapper;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070020import com.fasterxml.jackson.databind.node.ArrayNode;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070021import com.fasterxml.jackson.databind.node.ObjectNode;
Thomas Vachuskae2b7e7e2015-05-20 11:11:31 -070022import com.google.common.annotations.Beta;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080023import com.google.common.collect.ImmutableSet;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070024import com.google.common.collect.Lists;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080025import org.onlab.packet.IpAddress;
Jonathan Hart9eb45bb2016-02-12 10:59:11 -080026import org.onlab.packet.IpPrefix;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080027import org.onlab.packet.MacAddress;
Hyunsun Moon61b73e92016-05-10 18:05:57 -070028import org.onlab.packet.TpPort;
Jonathan Hart9eb45bb2016-02-12 10:59:11 -080029import org.onosproject.net.ConnectPoint;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070030
31import java.util.Collection;
Simon Hunt4f3a4072016-10-17 17:52:11 -070032import java.util.Iterator;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070033import java.util.List;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080034import java.util.Set;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070035import java.util.function.Function;
Yuta HIGUCHIa255bb42016-11-03 16:04:08 -070036import java.util.stream.Collectors;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070037
38import static com.google.common.base.Preconditions.checkNotNull;
Charles Chan023a8982016-02-04 11:00:41 -080039import static com.google.common.base.Preconditions.checkState;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070040
41/**
42 * Base abstraction of a configuration facade for a specific subject. Derived
Thomas Vachuska96d55b12015-05-11 08:52:03 -070043 * classes should keep all state in the specified JSON tree as that is the
44 * only state that will be distributed or persisted; this class is merely
45 * a facade for interacting with a particular facet of configuration on a
46 * given subject.
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070047 *
48 * @param <S> type of subject
49 */
Thomas Vachuskae2b7e7e2015-05-20 11:11:31 -070050@Beta
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070051public abstract class Config<S> {
52
Jonathan Hart9eb45bb2016-02-12 10:59:11 -080053 private static final String TRUE_LITERAL = "true";
54 private static final String FALSE_LITERAL = "false";
Jordan Halterman83949a12017-06-21 10:35:38 -070055 private static final String EMPTY_STRING = "";
Jonathan Hart9eb45bb2016-02-12 10:59:11 -080056
Thomas Vachuska96d55b12015-05-11 08:52:03 -070057 protected S subject;
58 protected String key;
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070059
60 protected JsonNode node;
61 protected ObjectNode object;
62 protected ArrayNode array;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070063 protected ObjectMapper mapper;
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070064
Thomas Vachuska96d55b12015-05-11 08:52:03 -070065 protected ConfigApplyDelegate delegate;
66
67 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080068 * Indicator of whether a configuration JSON field is required.
69 */
70 public enum FieldPresence {
71 /**
72 * Signifies that config field is an optional one.
73 */
74 OPTIONAL,
75
76 /**
77 * Signifies that config field is mandatory.
78 */
79 MANDATORY
80 }
81
82 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -070083 * Initializes the configuration behaviour with necessary context.
84 *
85 * @param subject configuration subject
86 * @param key configuration key
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070087 * @param node JSON node where configuration data is stored
Thomas Vachuska96d55b12015-05-11 08:52:03 -070088 * @param mapper JSON object mapper
Charles Chan023a8982016-02-04 11:00:41 -080089 * @param delegate delegate context, or null for detached configs.
Thomas Vachuska96d55b12015-05-11 08:52:03 -070090 */
Charles Chan023a8982016-02-04 11:00:41 -080091 public final void init(S subject, String key, JsonNode node, ObjectMapper mapper,
Simon Hunt4f3a4072016-10-17 17:52:11 -070092 ConfigApplyDelegate delegate) {
Charles Chan023a8982016-02-04 11:00:41 -080093 this.subject = checkNotNull(subject, "Subject cannot be null");
Thomas Vachuska96d55b12015-05-11 08:52:03 -070094 this.key = key;
Charles Chan023a8982016-02-04 11:00:41 -080095 this.node = checkNotNull(node, "Node cannot be null");
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070096 this.object = node instanceof ObjectNode ? (ObjectNode) node : null;
97 this.array = node instanceof ArrayNode ? (ArrayNode) node : null;
Charles Chan023a8982016-02-04 11:00:41 -080098 this.mapper = checkNotNull(mapper, "Mapper cannot be null");
99 this.delegate = delegate;
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700100 }
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700101
102 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800103 * Indicates whether or not the backing JSON node contains valid data.
104 * <p>
105 * Default implementation returns true.
106 * Subclasses are expected to override this with their own validation.
Thomas Vachuska36008462016-01-07 15:38:20 -0800107 * Implementations are free to throw a RuntimeException if data is invalid.
Jonathan Hart54b83e82016-03-26 20:37:20 -0700108 * </p>
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800109 *
110 * @return true if the data is valid; false otherwise
Thomas Vachuska36008462016-01-07 15:38:20 -0800111 * @throws RuntimeException if configuration is invalid or completely foobar
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800112 */
113 public boolean isValid() {
Thomas Vachuska36008462016-01-07 15:38:20 -0800114 // Derivatives should use the provided set of predicates to test
115 // validity of their fields, e.g.:
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800116 // isString(path)
117 // isBoolean(path)
118 // isNumber(path, [min, max])
Jonathan Hart54b83e82016-03-26 20:37:20 -0700119 // isIntegralNumber(path, [min, max])
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800120 // isDecimal(path, [min, max])
121 // isMacAddress(path)
122 // isIpAddress(path)
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800123 // isIpPrefix(path)
124 // isConnectPoint(path)
Jonathan Hart54b83e82016-03-26 20:37:20 -0700125 // isTpPort(path)
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800126 return true;
127 }
128
129 /**
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700130 * Returns the specific subject to which this configuration pertains.
131 *
132 * @return configuration subject
133 */
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700134 public S subject() {
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700135 return subject;
136 }
137
138 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700139 * Returns the configuration key. This is primarily aimed for use in
140 * composite JSON trees in external representations and has no bearing on
141 * the internal behaviours.
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700142 *
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700143 * @return configuration key
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700144 */
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700145 public String key() {
146 return key;
147 }
148
149 /**
150 * Returns the JSON node that contains the configuration data.
151 *
152 * @return JSON node backing the configuration
153 */
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700154 public JsonNode node() {
Jonathan Harta8625482015-09-08 16:14:56 -0700155 return node;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700156 }
157
158 /**
159 * Applies any configuration changes made via this configuration.
Jonathan Hart54b83e82016-03-26 20:37:20 -0700160 * <p>
Charles Chan023a8982016-02-04 11:00:41 -0800161 * Not effective for detached configs.
Jonathan Hart54b83e82016-03-26 20:37:20 -0700162 * </p>
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700163 */
164 public void apply() {
Charles Chan023a8982016-02-04 11:00:41 -0800165 checkState(delegate != null, "Cannot apply detached config");
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700166 delegate.onApply(this);
167 }
168
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700169 // Miscellaneous helpers for interacting with JSON
170
171 /**
172 * Gets the specified property as a string.
173 *
174 * @param name property name
175 * @param defaultValue default value if property not set
176 * @return property value or default value
177 */
178 protected String get(String name, String defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700179 return object.path(name).asText(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700180 }
181
182 /**
183 * Sets the specified property as a string or clears it if null value given.
184 *
185 * @param name property name
186 * @param value new value or null to clear the property
187 * @return self
188 */
189 protected Config<S> setOrClear(String name, String value) {
190 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700191 object.put(name, value);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700192 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700193 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700194 }
195 return this;
196 }
197
198 /**
199 * Gets the specified property as a boolean.
200 *
201 * @param name property name
202 * @param defaultValue default value if property not set
203 * @return property value or default value
204 */
205 protected boolean get(String name, boolean defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700206 return object.path(name).asBoolean(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700207 }
208
209 /**
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800210 * Clears the specified property.
211 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700212 * @param name property name
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800213 * @return self
214 */
215 protected Config<S> clear(String name) {
216 object.remove(name);
217 return this;
218 }
219
220 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700221 * Sets the specified property as a boolean or clears it if null value given.
222 *
223 * @param name property name
224 * @param value new value or null to clear the property
225 * @return self
226 */
227 protected Config<S> setOrClear(String name, Boolean value) {
228 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700229 object.put(name, value.booleanValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700230 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700231 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700232 }
233 return this;
234 }
235
236 /**
Jonathan Hart111b42b2015-07-14 13:28:05 -0700237 * Gets the specified property as an integer.
238 *
239 * @param name property name
240 * @param defaultValue default value if property not set
241 * @return property value or default value
242 */
243 protected int get(String name, int defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700244 return object.path(name).asInt(defaultValue);
Jonathan Hart111b42b2015-07-14 13:28:05 -0700245 }
246
247 /**
248 * Sets the specified property as an integer or clears it if null value given.
249 *
250 * @param name property name
251 * @param value new value or null to clear the property
252 * @return self
253 */
254 protected Config<S> setOrClear(String name, Integer value) {
255 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700256 object.put(name, value.intValue());
Jonathan Hart111b42b2015-07-14 13:28:05 -0700257 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700258 object.remove(name);
Jonathan Hart111b42b2015-07-14 13:28:05 -0700259 }
260 return this;
261 }
262
263 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700264 * Gets the specified property as a long.
265 *
266 * @param name property name
267 * @param defaultValue default value if property not set
268 * @return property value or default value
269 */
270 protected long get(String name, long defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700271 return object.path(name).asLong(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700272 }
273
274 /**
275 * Sets the specified property as a long or clears it if null value given.
276 *
277 * @param name property name
278 * @param value new value or null to clear the property
279 * @return self
280 */
281 protected Config<S> setOrClear(String name, Long value) {
282 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700283 object.put(name, value.longValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700284 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700285 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700286 }
287 return this;
288 }
289
290 /**
291 * Gets the specified property as a double.
292 *
293 * @param name property name
294 * @param defaultValue default value if property not set
295 * @return property value or default value
296 */
297 protected double get(String name, double defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700298 return object.path(name).asDouble(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700299 }
300
301 /**
302 * Sets the specified property as a double or clears it if null value given.
303 *
304 * @param name property name
305 * @param value new value or null to clear the property
306 * @return self
307 */
308 protected Config<S> setOrClear(String name, Double value) {
309 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700310 object.put(name, value.doubleValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700311 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700312 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700313 }
314 return this;
315 }
316
317 /**
318 * Gets the specified property as an enum.
319 *
320 * @param name property name
321 * @param defaultValue default value if property not set
322 * @param enumClass the enum class
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700323 * @param <E> type of enum
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700324 * @return property value or default value
325 */
326 protected <E extends Enum<E>> E get(String name, E defaultValue, Class<E> enumClass) {
Thomas Vachuska2d6c5992016-07-01 12:42:26 +0200327 if (defaultValue != null) {
Charles Chand5c3b932016-09-22 17:49:52 -0700328 return Enum.valueOf(enumClass, object.path(name).asText(defaultValue.toString()));
Thomas Vachuska2d6c5992016-07-01 12:42:26 +0200329 }
330
331 JsonNode node = object.get(name);
332 return node == null ? null : Enum.valueOf(enumClass, node.asText());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700333 }
334
335 /**
336 * Sets the specified property as a double or clears it if null value given.
337 *
338 * @param name property name
339 * @param value new value or null to clear the property
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700340 * @param <E> type of enum
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700341 * @return self
342 */
343 protected <E extends Enum> Config<S> setOrClear(String name, E value) {
344 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700345 object.put(name, value.toString());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700346 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700347 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700348 }
349 return this;
350 }
Jonathan Hart111b42b2015-07-14 13:28:05 -0700351
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700352 /**
353 * Gets the specified array property as a list of items.
354 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700355 * @param name property name
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700356 * @param function mapper from string to item
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700357 * @param <T> type of item
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700358 * @return list of items
359 */
360 protected <T> List<T> getList(String name, Function<String, T> function) {
361 List<T> list = Lists.newArrayList();
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700362 ArrayNode arrayNode = (ArrayNode) object.path(name);
Yuta HIGUCHIa255bb42016-11-03 16:04:08 -0700363 arrayNode.forEach(i -> list.add(function.apply(asString(i))));
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700364 return list;
365 }
366
367 /**
Yuta HIGUCHIa255bb42016-11-03 16:04:08 -0700368 * Converts JSON node to a String.
369 * <p>
370 * If the {@code node} was a text node, text is returned as-is,
371 * all other node type will be converted to String by toString().
372 *
373 * @param node JSON node to convert
374 * @return String representation
375 */
376 private static String asString(JsonNode node) {
377 if (node.isTextual()) {
378 return node.asText();
379 } else {
380 return node.toString();
381 }
382 }
383
384 /**
Naoki Shiota399a0b32015-11-15 20:36:13 -0600385 * Gets the specified array property as a list of items.
386 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700387 * @param name property name
388 * @param function mapper from string to item
Naoki Shiota399a0b32015-11-15 20:36:13 -0600389 * @param defaultValue default value if property not set
Simon Hunt4f3a4072016-10-17 17:52:11 -0700390 * @param <T> type of item
Naoki Shiota399a0b32015-11-15 20:36:13 -0600391 * @return list of items
392 */
393 protected <T> List<T> getList(String name, Function<String, T> function, List<T> defaultValue) {
394 List<T> list = Lists.newArrayList();
395 JsonNode jsonNode = object.path(name);
396 if (jsonNode.isMissingNode()) {
397 return defaultValue;
398 }
399 ArrayNode arrayNode = (ArrayNode) jsonNode;
Yuta HIGUCHIa255bb42016-11-03 16:04:08 -0700400 arrayNode.forEach(i -> list.add(function.apply(asString(i))));
Naoki Shiota399a0b32015-11-15 20:36:13 -0600401 return list;
402 }
403
404 /**
Yuta HIGUCHIa255bb42016-11-03 16:04:08 -0700405 * Sets the specified property as an array of items in a given collection
406 * transformed into a String with supplied {@code function}.
407 *
408 * @param name propertyName
409 * @param function to transform item to a String
410 * @param value list of items
411 * @param <T> type of items
412 * @return self
413 */
414 protected <T> Config<S> setList(String name,
415 Function<? super T, String> function,
416 List<T> value) {
417 Collection<String> mapped = value.stream()
418 .map(function)
419 .collect(Collectors.toList());
420 return setOrClear(name, mapped);
421 }
422
423 /**
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700424 * Sets the specified property as an array of items in a given collection or
425 * clears it if null is given.
426 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700427 * @param name propertyName
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700428 * @param collection collection of items
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700429 * @param <T> type of items
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700430 * @return self
431 */
432 protected <T> Config<S> setOrClear(String name, Collection<T> collection) {
433 if (collection == null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700434 object.remove(name);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700435 } else {
436 ArrayNode arrayNode = mapper.createArrayNode();
437 collection.forEach(i -> arrayNode.add(i.toString()));
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700438 object.set(name, arrayNode);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700439 }
440 return this;
441 }
442
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800443 /**
Jordan Halterman83949a12017-06-21 10:35:38 -0700444 * Indicates whether the specified field is of a valid length.
445 *
446 * @param field the field to validate
447 * @param maxLength the maximum allowed length of the field
448 * @return true if the field lenth is less than the required length
449 */
450 protected boolean isValidLength(String field, int maxLength) {
451 if (object.path(field).asText(EMPTY_STRING).length() > maxLength) {
452 throw new InvalidFieldException(field, "exceeds maximum length " + maxLength);
453 }
454 return true;
455 }
456
457 /**
Simon Hunt4f3a4072016-10-17 17:52:11 -0700458 * Returns true if this config contains a field with the given name.
459 *
460 * @param name the field name
461 * @return true if field is present, false otherwise
462 */
463 protected boolean hasField(String name) {
464 return hasField(object, name);
465 }
466
467 /**
468 * Returns true if the given node contains a field with the given name.
469 *
470 * @param node the node to examine
471 * @param name the name to look for
472 * @return true if the node has a field with the given name, false otherwise
473 */
474 protected boolean hasField(ObjectNode node, String name) {
475 Iterator<String> fnames = node.fieldNames();
476 while (fnames.hasNext()) {
477 if (fnames.next().equals(name)) {
478 return true;
479 }
480 }
481 return false;
482 }
483
484 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800485 * Indicates whether only the specified fields are present in the backing JSON.
486 *
487 * @param allowedFields allowed field names
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700488 * @return true if only allowedFields are present; false otherwise
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800489 */
490 protected boolean hasOnlyFields(String... allowedFields) {
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800491 return hasOnlyFields(object, allowedFields);
492 }
493
494 /**
495 * Indicates whether only the specified fields are present in a particular
496 * JSON object.
497 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700498 * @param node node whose fields to check
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800499 * @param allowedFields allowed field names
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700500 * @return true if only allowedFields are present; false otherwise
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800501 */
502 protected boolean hasOnlyFields(ObjectNode node, String... allowedFields) {
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800503 Set<String> fields = ImmutableSet.copyOf(allowedFields);
Jonathan Hart54b83e82016-03-26 20:37:20 -0700504 node.fieldNames().forEachRemaining(f -> {
505 if (!fields.contains(f)) {
506 throw new InvalidFieldException(f, "Field is not allowed");
507 }
508 });
509 return true;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800510 }
511
512 /**
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700513 * Indicates whether all specified fields are present in the backing JSON.
514 *
515 * @param mandatoryFields mandatory field names
516 * @return true if all mandatory fields are present; false otherwise
517 */
518 protected boolean hasFields(String... mandatoryFields) {
519 return hasFields(object, mandatoryFields);
520 }
521
522 /**
523 * Indicates whether all specified fields are present in a particular
524 * JSON object.
525 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700526 * @param node node whose fields to check
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700527 * @param mandatoryFields mandatory field names
528 * @return true if all mandatory fields are present; false otherwise
529 */
530 protected boolean hasFields(ObjectNode node, String... mandatoryFields) {
531 Set<String> fields = ImmutableSet.copyOf(mandatoryFields);
Jonathan Hart54b83e82016-03-26 20:37:20 -0700532 fields.forEach(f -> {
533 if (node.path(f).isMissingNode()) {
534 throw new InvalidFieldException(f, "Mandatory field is not present");
535 }
536 });
537 return true;
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700538 }
539
540 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800541 * Indicates whether the specified field holds a valid MAC address.
542 *
543 * @param field JSON field name
544 * @param presence specifies if field is optional or mandatory
545 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700546 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800547 */
548 protected boolean isMacAddress(String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700549 return isMacAddress(object, field, presence);
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800550 }
551
552 /**
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800553 * Indicates whether the specified field of a particular node holds a valid
554 * MAC address.
555 *
556 * @param objectNode JSON node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700557 * @param field JSON field name
558 * @param presence specifies if field is optional or mandatory
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800559 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700560 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800561 */
562 protected boolean isMacAddress(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700563 return isValid(objectNode, field, presence, n -> {
564 MacAddress.valueOf(n.asText());
565 return true;
566 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800567 }
568
569 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800570 * Indicates whether the specified field holds a valid IP address.
571 *
572 * @param field JSON field name
573 * @param presence specifies if field is optional or mandatory
574 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700575 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800576 */
577 protected boolean isIpAddress(String field, FieldPresence presence) {
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800578 return isIpAddress(object, field, presence);
579 }
580
581 /**
582 * Indicates whether the specified field of a particular node holds a valid
583 * IP address.
584 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700585 * @param objectNode node from whom to access the field
586 * @param field JSON field name
587 * @param presence specifies if field is optional or mandatory
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800588 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700589 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800590 */
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800591 protected boolean isIpAddress(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700592 return isValid(objectNode, field, presence, n -> {
593 IpAddress.valueOf(n.asText());
594 return true;
595 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800596 }
597
598 /**
599 * Indicates whether the specified field holds a valid IP prefix.
600 *
601 * @param field JSON field name
602 * @param presence specifies if field is optional or mandatory
603 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700604 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800605 */
606 protected boolean isIpPrefix(String field, FieldPresence presence) {
607 return isIpPrefix(object, field, presence);
608 }
609
610 /**
611 * Indicates whether the specified field of a particular node holds a valid
612 * IP prefix.
613 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700614 * @param objectNode node from whom to access the field
615 * @param field JSON field name
616 * @param presence specifies if field is optional or mandatory
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800617 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700618 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800619 */
620 protected boolean isIpPrefix(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700621 return isValid(objectNode, field, presence, n -> {
622 IpPrefix.valueOf(n.asText());
623 return true;
624 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800625 }
626
627 /**
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700628 * Indicates whether the specified field holds a valid transport layer port.
629 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700630 * @param field JSON field name
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700631 * @param presence specifies if field is optional or mandatory
632 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700633 * @throws InvalidFieldException if the field is present but not valid
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700634 */
635 protected boolean isTpPort(String field, FieldPresence presence) {
636 return isTpPort(object, field, presence);
637 }
638
639 /**
640 * Indicates whether the specified field of a particular node holds a valid
641 * transport layer port.
642 *
643 * @param objectNode node from whom to access the field
Simon Hunt4f3a4072016-10-17 17:52:11 -0700644 * @param field JSON field name
645 * @param presence specifies if field is optional or mandatory
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700646 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700647 * @throws InvalidFieldException if the field is present but not valid
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700648 */
649 protected boolean isTpPort(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700650 return isValid(objectNode, field, presence, n -> {
651 TpPort.tpPort(n.asInt());
652 return true;
653 });
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700654 }
655
656 /**
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800657 * Indicates whether the specified field holds a valid connect point string.
658 *
659 * @param field JSON field name
660 * @param presence specifies if field is optional or mandatory
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
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800663 */
664 protected boolean isConnectPoint(String field, FieldPresence presence) {
665 return isConnectPoint(object, field, presence);
666 }
667
668 /**
669 * Indicates whether the specified field of a particular node holds a valid
670 * connect point string.
671 *
672 * @param objectNode JSON node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700673 * @param field JSON field name
674 * @param presence specifies if field is optional or mandatory
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800675 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700676 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800677 */
678 protected boolean isConnectPoint(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700679 return isValid(objectNode, field, presence, n -> {
680 ConnectPoint.deviceConnectPoint(n.asText());
681 return true;
682 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800683 }
684
685 /**
686 * Indicates whether the specified field holds a valid string value.
687 *
688 * @param field JSON field name
689 * @param presence specifies if field is optional or mandatory
690 * @param pattern optional regex pattern
691 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700692 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800693 */
694 protected boolean isString(String field, FieldPresence presence, String... pattern) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800695 return isString(object, field, presence, pattern);
696 }
697
698 /**
699 * Indicates whether the specified field on a particular node holds a valid
700 * string value.
701 *
702 * @param objectNode JSON node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700703 * @param field JSON field name
704 * @param presence specifies if field is optional or mandatory
705 * @param pattern optional regex pattern
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800706 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700707 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800708 */
709 protected boolean isString(ObjectNode objectNode, String field,
710 FieldPresence presence, String... pattern) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700711 return isValid(objectNode, field, presence, (node) -> {
712 if (!(node.isTextual() &&
713 (pattern.length > 0 && node.asText().matches(pattern[0]) || pattern.length < 1))) {
714 fail("Invalid string value");
715 }
716 return true;
717 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800718 }
719
720 /**
721 * Indicates whether the specified field holds a valid number.
722 *
723 * @param field JSON field name
724 * @param presence specifies if field is optional or mandatory
725 * @param minMax optional min/max values
726 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700727 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800728 */
729 protected boolean isNumber(String field, FieldPresence presence, long... minMax) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700730 return isNumber(object, field, presence, minMax);
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800731 }
Jonathan Hart54b83e82016-03-26 20:37:20 -0700732
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800733 /**
Jonathan Hart54b83e82016-03-26 20:37:20 -0700734 * Indicates whether the specified field of a particular node holds a
735 * valid number.
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800736 *
Jonathan Hart54b83e82016-03-26 20:37:20 -0700737 * @param objectNode JSON object
Simon Hunt4f3a4072016-10-17 17:52:11 -0700738 * @param field JSON field name
739 * @param presence specifies if field is optional or mandatory
740 * @param minMax optional min/max values
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800741 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700742 * @throws InvalidFieldException if the field is present but not valid
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800743 */
Jonathan Hart54b83e82016-03-26 20:37:20 -0700744 protected boolean isNumber(ObjectNode objectNode, String field,
745 FieldPresence presence, long... minMax) {
746 return isValid(objectNode, field, presence, n -> {
747 long number = (n.isNumber()) ? n.asLong() : Long.parseLong(n.asText());
748 if (minMax.length > 1) {
749 verifyRange(number, minMax[0], minMax[1]);
750 } else if (minMax.length > 0) {
751 verifyRange(number, minMax[0]);
752 }
753 return true;
754 });
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800755 }
756
757 /**
758 * Indicates whether the specified field holds a valid integer.
759 *
760 * @param field JSON field name
761 * @param presence specifies if field is optional or mandatory
762 * @param minMax optional min/max values
763 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700764 * @throws InvalidFieldException if the field is present but not valid
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800765 */
766 protected boolean isIntegralNumber(String field, FieldPresence presence, long... minMax) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800767 return isIntegralNumber(object, field, presence, minMax);
768 }
769
770 /**
771 * Indicates whether the specified field of a particular node holds a valid
772 * integer.
773 *
774 * @param objectNode JSON node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700775 * @param field JSON field name
776 * @param presence specifies if field is optional or mandatory
777 * @param minMax optional min/max values
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800778 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700779 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800780 */
781 protected boolean isIntegralNumber(ObjectNode objectNode, String field,
782 FieldPresence presence, long... minMax) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700783 return isValid(objectNode, field, presence, n -> {
784 long number = (n.isIntegralNumber()) ? n.asLong() : Long.parseLong(n.asText());
785 if (minMax.length > 1) {
786 verifyRange(number, minMax[0], minMax[1]);
787 } else if (minMax.length > 0) {
788 verifyRange(number, minMax[0]);
789 }
790 return true;
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800791 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800792 }
793
794 /**
795 * Indicates whether the specified field holds a valid decimal number.
796 *
797 * @param field JSON field name
798 * @param presence specifies if field is optional or mandatory
799 * @param minMax optional min/max values
800 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700801 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800802 */
803 protected boolean isDecimal(String field, FieldPresence presence, double... minMax) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700804 return isDecimal(object, field, presence, minMax);
805 }
806
807 /**
808 * Indicates whether the specified field of a particular node holds a valid
809 * decimal number.
810 *
811 * @param objectNode JSON node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700812 * @param field JSON field name
813 * @param presence specifies if field is optional or mandatory
814 * @param minMax optional min/max values
Jonathan Hart54b83e82016-03-26 20:37:20 -0700815 * @return true if valid; false otherwise
816 * @throws InvalidFieldException if the field is present but not valid
817 */
818 protected boolean isDecimal(ObjectNode objectNode, String field,
819 FieldPresence presence, double... minMax) {
820 return isValid(objectNode, field, presence, n -> {
821 double number = (n.isDouble()) ? n.asDouble() : Double.parseDouble(n.asText());
822 if (minMax.length > 1) {
823 verifyRange(number, minMax[0], minMax[1]);
824 } else if (minMax.length > 0) {
825 verifyRange(number, minMax[0]);
826 }
827 return true;
828 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800829 }
830
831 /**
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530832 * Indicates whether the specified field holds a valid boolean value.
833 *
834 * @param field JSON field name
835 * @param presence specifies if field is optional or mandatory
836 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700837 * @throws InvalidFieldException if the field is present but not valid
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530838 */
839 protected boolean isBoolean(String field, FieldPresence presence) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800840 return isBoolean(object, field, presence);
841 }
842
843 /**
844 * Indicates whether the specified field of a particular node holds a valid
845 * boolean value.
846 *
847 * @param objectNode JSON object node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700848 * @param field JSON field name
849 * @param presence specifies if field is optional or mandatory
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800850 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700851 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800852 */
853 protected boolean isBoolean(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700854 return isValid(objectNode, field, presence, n -> {
855 if (!(n.isBoolean() || (n.isTextual() && isBooleanString(n.asText())))) {
856 fail("Field is not a boolean value");
857 }
858 return true;
859 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800860 }
861
862 /**
863 * Indicates whether a string holds a boolean literal value.
864 *
865 * @param str string to test
866 * @return true if the string contains "true" or "false" (case insensitive),
867 * otherwise false
868 */
869 private boolean isBooleanString(String str) {
870 return str.equalsIgnoreCase(TRUE_LITERAL) || str.equalsIgnoreCase(FALSE_LITERAL);
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530871 }
872
873 /**
Jonathan Hart54b83e82016-03-26 20:37:20 -0700874 * Indicates whether a field in the node is present and of correct value or
875 * not mandatory and absent.
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800876 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700877 * @param objectNode JSON object node containing field to validate
878 * @param field name of field to validate
879 * @param presence specified if field is optional or mandatory
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800880 * @param validationFunction function which can be used to verify if the
881 * node has the correct value
882 * @return true if the field is as expected
Jonathan Hart54b83e82016-03-26 20:37:20 -0700883 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800884 */
Jonathan Hart54b83e82016-03-26 20:37:20 -0700885 private boolean isValid(ObjectNode objectNode, String field, FieldPresence presence,
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800886 Function<JsonNode, Boolean> validationFunction) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700887 JsonNode node = objectNode.path(field);
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800888 boolean isMandatory = presence == FieldPresence.MANDATORY;
Jonathan Hart54b83e82016-03-26 20:37:20 -0700889 if (isMandatory && node.isMissingNode()) {
890 throw new InvalidFieldException(field, "Mandatory field not present");
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800891 }
892
893 if (!isMandatory && (node.isNull() || node.isMissingNode())) {
894 return true;
895 }
896
Jonathan Hart54b83e82016-03-26 20:37:20 -0700897 try {
898 if (validationFunction.apply(node)) {
899 return true;
900 } else {
901 throw new InvalidFieldException(field, "Validation error");
902 }
903 } catch (IllegalArgumentException e) {
904 throw new InvalidFieldException(field, e);
905 }
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800906 }
Jonathan Hart54b83e82016-03-26 20:37:20 -0700907
908 private static void fail(String message) {
909 throw new IllegalArgumentException(message);
910 }
911
912 private static <N extends Comparable> void verifyRange(N num, N min) {
913 if (num.compareTo(min) < 0) {
914 fail("Field must be greater than " + min);
915 }
916 }
917
918 private static <N extends Comparable> void verifyRange(N num, N min, N max) {
919 verifyRange(num, min);
920
921 if (num.compareTo(max) > 0) {
922 fail("Field must be less than " + max);
923 }
924 }
925
Yuta HIGUCHI7438f5a2017-02-15 22:09:46 -0800926 @Override
927 public String toString() {
928 return String.valueOf(node);
929 }
930
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700931}