blob: a3824c8e48f1b79054167e545d2c8f55e09b377c [file] [log] [blame]
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Ray Milkeya4122362015-08-18 15:19:08 -070016package org.onosproject.net.config;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070017
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070018import com.fasterxml.jackson.databind.JsonNode;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070019import com.fasterxml.jackson.databind.ObjectMapper;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070020import com.fasterxml.jackson.databind.node.ArrayNode;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070021import com.fasterxml.jackson.databind.node.ObjectNode;
Thomas Vachuskae2b7e7e2015-05-20 11:11:31 -070022import com.google.common.annotations.Beta;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080023import com.google.common.collect.ImmutableSet;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070024import com.google.common.collect.Lists;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080025import org.onlab.packet.IpAddress;
Jonathan Hart9eb45bb2016-02-12 10:59:11 -080026import org.onlab.packet.IpPrefix;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080027import org.onlab.packet.MacAddress;
Hyunsun Moon61b73e92016-05-10 18:05:57 -070028import org.onlab.packet.TpPort;
Jonathan Hart9eb45bb2016-02-12 10:59:11 -080029import org.onosproject.net.ConnectPoint;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070030
31import java.util.Collection;
Simon Hunt4f3a4072016-10-17 17:52:11 -070032import java.util.Iterator;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070033import java.util.List;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080034import java.util.Set;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070035import java.util.function.Function;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070036
37import static com.google.common.base.Preconditions.checkNotNull;
Charles Chan023a8982016-02-04 11:00:41 -080038import static com.google.common.base.Preconditions.checkState;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070039
40/**
41 * Base abstraction of a configuration facade for a specific subject. Derived
Thomas Vachuska96d55b12015-05-11 08:52:03 -070042 * classes should keep all state in the specified JSON tree as that is the
43 * only state that will be distributed or persisted; this class is merely
44 * a facade for interacting with a particular facet of configuration on a
45 * given subject.
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070046 *
47 * @param <S> type of subject
48 */
Thomas Vachuskae2b7e7e2015-05-20 11:11:31 -070049@Beta
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070050public abstract class Config<S> {
51
Jonathan Hart9eb45bb2016-02-12 10:59:11 -080052 private static final String TRUE_LITERAL = "true";
53 private static final String FALSE_LITERAL = "false";
54
Thomas Vachuska96d55b12015-05-11 08:52:03 -070055 protected S subject;
56 protected String key;
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070057
58 protected JsonNode node;
59 protected ObjectNode object;
60 protected ArrayNode array;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070061 protected ObjectMapper mapper;
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070062
Thomas Vachuska96d55b12015-05-11 08:52:03 -070063 protected ConfigApplyDelegate delegate;
64
65 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080066 * Indicator of whether a configuration JSON field is required.
67 */
68 public enum FieldPresence {
69 /**
70 * Signifies that config field is an optional one.
71 */
72 OPTIONAL,
73
74 /**
75 * Signifies that config field is mandatory.
76 */
77 MANDATORY
78 }
79
80 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -070081 * Initializes the configuration behaviour with necessary context.
82 *
83 * @param subject configuration subject
84 * @param key configuration key
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070085 * @param node JSON node where configuration data is stored
Thomas Vachuska96d55b12015-05-11 08:52:03 -070086 * @param mapper JSON object mapper
Charles Chan023a8982016-02-04 11:00:41 -080087 * @param delegate delegate context, or null for detached configs.
Thomas Vachuska96d55b12015-05-11 08:52:03 -070088 */
Charles Chan023a8982016-02-04 11:00:41 -080089 public final void init(S subject, String key, JsonNode node, ObjectMapper mapper,
Simon Hunt4f3a4072016-10-17 17:52:11 -070090 ConfigApplyDelegate delegate) {
Charles Chan023a8982016-02-04 11:00:41 -080091 this.subject = checkNotNull(subject, "Subject cannot be null");
Thomas Vachuska96d55b12015-05-11 08:52:03 -070092 this.key = key;
Charles Chan023a8982016-02-04 11:00:41 -080093 this.node = checkNotNull(node, "Node cannot be null");
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070094 this.object = node instanceof ObjectNode ? (ObjectNode) node : null;
95 this.array = node instanceof ArrayNode ? (ArrayNode) node : null;
Charles Chan023a8982016-02-04 11:00:41 -080096 this.mapper = checkNotNull(mapper, "Mapper cannot be null");
97 this.delegate = delegate;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070098 }
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070099
100 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800101 * Indicates whether or not the backing JSON node contains valid data.
102 * <p>
103 * Default implementation returns true.
104 * Subclasses are expected to override this with their own validation.
Thomas Vachuska36008462016-01-07 15:38:20 -0800105 * Implementations are free to throw a RuntimeException if data is invalid.
Jonathan Hart54b83e82016-03-26 20:37:20 -0700106 * </p>
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800107 *
108 * @return true if the data is valid; false otherwise
Thomas Vachuska36008462016-01-07 15:38:20 -0800109 * @throws RuntimeException if configuration is invalid or completely foobar
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800110 */
111 public boolean isValid() {
Thomas Vachuska36008462016-01-07 15:38:20 -0800112 // Derivatives should use the provided set of predicates to test
113 // validity of their fields, e.g.:
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800114 // isString(path)
115 // isBoolean(path)
116 // isNumber(path, [min, max])
Jonathan Hart54b83e82016-03-26 20:37:20 -0700117 // isIntegralNumber(path, [min, max])
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800118 // isDecimal(path, [min, max])
119 // isMacAddress(path)
120 // isIpAddress(path)
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800121 // isIpPrefix(path)
122 // isConnectPoint(path)
Jonathan Hart54b83e82016-03-26 20:37:20 -0700123 // isTpPort(path)
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800124 return true;
125 }
126
127 /**
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700128 * Returns the specific subject to which this configuration pertains.
129 *
130 * @return configuration subject
131 */
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700132 public S subject() {
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700133 return subject;
134 }
135
136 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700137 * Returns the configuration key. This is primarily aimed for use in
138 * composite JSON trees in external representations and has no bearing on
139 * the internal behaviours.
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700140 *
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700141 * @return configuration key
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700142 */
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700143 public String key() {
144 return key;
145 }
146
147 /**
148 * Returns the JSON node that contains the configuration data.
149 *
150 * @return JSON node backing the configuration
151 */
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700152 public JsonNode node() {
Jonathan Harta8625482015-09-08 16:14:56 -0700153 return node;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700154 }
155
156 /**
157 * Applies any configuration changes made via this configuration.
Jonathan Hart54b83e82016-03-26 20:37:20 -0700158 * <p>
Charles Chan023a8982016-02-04 11:00:41 -0800159 * Not effective for detached configs.
Jonathan Hart54b83e82016-03-26 20:37:20 -0700160 * </p>
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700161 */
162 public void apply() {
Charles Chan023a8982016-02-04 11:00:41 -0800163 checkState(delegate != null, "Cannot apply detached config");
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700164 delegate.onApply(this);
165 }
166
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700167 // Miscellaneous helpers for interacting with JSON
168
169 /**
170 * Gets the specified property as a string.
171 *
172 * @param name property name
173 * @param defaultValue default value if property not set
174 * @return property value or default value
175 */
176 protected String get(String name, String defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700177 return object.path(name).asText(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700178 }
179
180 /**
181 * Sets the specified property as a string or clears it if null value given.
182 *
183 * @param name property name
184 * @param value new value or null to clear the property
185 * @return self
186 */
187 protected Config<S> setOrClear(String name, String value) {
188 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700189 object.put(name, value);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700190 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700191 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700192 }
193 return this;
194 }
195
196 /**
197 * Gets the specified property as a boolean.
198 *
199 * @param name property name
200 * @param defaultValue default value if property not set
201 * @return property value or default value
202 */
203 protected boolean get(String name, boolean defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700204 return object.path(name).asBoolean(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700205 }
206
207 /**
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800208 * Clears the specified property.
209 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700210 * @param name property name
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800211 * @return self
212 */
213 protected Config<S> clear(String name) {
214 object.remove(name);
215 return this;
216 }
217
218 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700219 * Sets the specified property as a boolean or clears it if null value given.
220 *
221 * @param name property name
222 * @param value new value or null to clear the property
223 * @return self
224 */
225 protected Config<S> setOrClear(String name, Boolean value) {
226 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700227 object.put(name, value.booleanValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700228 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700229 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700230 }
231 return this;
232 }
233
234 /**
Jonathan Hart111b42b2015-07-14 13:28:05 -0700235 * Gets the specified property as an integer.
236 *
237 * @param name property name
238 * @param defaultValue default value if property not set
239 * @return property value or default value
240 */
241 protected int get(String name, int defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700242 return object.path(name).asInt(defaultValue);
Jonathan Hart111b42b2015-07-14 13:28:05 -0700243 }
244
245 /**
246 * Sets the specified property as an integer or clears it if null value given.
247 *
248 * @param name property name
249 * @param value new value or null to clear the property
250 * @return self
251 */
252 protected Config<S> setOrClear(String name, Integer value) {
253 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700254 object.put(name, value.intValue());
Jonathan Hart111b42b2015-07-14 13:28:05 -0700255 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700256 object.remove(name);
Jonathan Hart111b42b2015-07-14 13:28:05 -0700257 }
258 return this;
259 }
260
261 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700262 * Gets the specified property as a long.
263 *
264 * @param name property name
265 * @param defaultValue default value if property not set
266 * @return property value or default value
267 */
268 protected long get(String name, long defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700269 return object.path(name).asLong(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700270 }
271
272 /**
273 * Sets the specified property as a long or clears it if null value given.
274 *
275 * @param name property name
276 * @param value new value or null to clear the property
277 * @return self
278 */
279 protected Config<S> setOrClear(String name, Long value) {
280 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700281 object.put(name, value.longValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700282 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700283 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700284 }
285 return this;
286 }
287
288 /**
289 * Gets the specified property as a double.
290 *
291 * @param name property name
292 * @param defaultValue default value if property not set
293 * @return property value or default value
294 */
295 protected double get(String name, double defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700296 return object.path(name).asDouble(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700297 }
298
299 /**
300 * Sets the specified property as a double or clears it if null value given.
301 *
302 * @param name property name
303 * @param value new value or null to clear the property
304 * @return self
305 */
306 protected Config<S> setOrClear(String name, Double value) {
307 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700308 object.put(name, value.doubleValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700309 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700310 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700311 }
312 return this;
313 }
314
315 /**
316 * Gets the specified property as an enum.
317 *
318 * @param name property name
319 * @param defaultValue default value if property not set
320 * @param enumClass the enum class
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700321 * @param <E> type of enum
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700322 * @return property value or default value
323 */
324 protected <E extends Enum<E>> E get(String name, E defaultValue, Class<E> enumClass) {
Thomas Vachuska2d6c5992016-07-01 12:42:26 +0200325 if (defaultValue != null) {
Charles Chand5c3b932016-09-22 17:49:52 -0700326 return Enum.valueOf(enumClass, object.path(name).asText(defaultValue.toString()));
Thomas Vachuska2d6c5992016-07-01 12:42:26 +0200327 }
328
329 JsonNode node = object.get(name);
330 return node == null ? null : Enum.valueOf(enumClass, node.asText());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700331 }
332
333 /**
334 * Sets the specified property as a double or clears it if null value given.
335 *
336 * @param name property name
337 * @param value new value or null to clear the property
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700338 * @param <E> type of enum
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700339 * @return self
340 */
341 protected <E extends Enum> Config<S> setOrClear(String name, E value) {
342 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700343 object.put(name, value.toString());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700344 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700345 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700346 }
347 return this;
348 }
Jonathan Hart111b42b2015-07-14 13:28:05 -0700349
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700350 /**
351 * Gets the specified array property as a list of items.
352 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700353 * @param name property name
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700354 * @param function mapper from string to item
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700355 * @param <T> type of item
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700356 * @return list of items
357 */
358 protected <T> List<T> getList(String name, Function<String, T> function) {
359 List<T> list = Lists.newArrayList();
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700360 ArrayNode arrayNode = (ArrayNode) object.path(name);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700361 arrayNode.forEach(i -> list.add(function.apply(i.asText())));
362 return list;
363 }
364
365 /**
Naoki Shiota399a0b32015-11-15 20:36:13 -0600366 * Gets the specified array property as a list of items.
367 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700368 * @param name property name
369 * @param function mapper from string to item
Naoki Shiota399a0b32015-11-15 20:36:13 -0600370 * @param defaultValue default value if property not set
Simon Hunt4f3a4072016-10-17 17:52:11 -0700371 * @param <T> type of item
Naoki Shiota399a0b32015-11-15 20:36:13 -0600372 * @return list of items
373 */
374 protected <T> List<T> getList(String name, Function<String, T> function, List<T> defaultValue) {
375 List<T> list = Lists.newArrayList();
376 JsonNode jsonNode = object.path(name);
377 if (jsonNode.isMissingNode()) {
378 return defaultValue;
379 }
380 ArrayNode arrayNode = (ArrayNode) jsonNode;
381 arrayNode.forEach(i -> list.add(function.apply(i.asText())));
382 return list;
383 }
384
385 /**
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700386 * Sets the specified property as an array of items in a given collection or
387 * clears it if null is given.
388 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700389 * @param name propertyName
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700390 * @param collection collection of items
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700391 * @param <T> type of items
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700392 * @return self
393 */
394 protected <T> Config<S> setOrClear(String name, Collection<T> collection) {
395 if (collection == null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700396 object.remove(name);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700397 } else {
398 ArrayNode arrayNode = mapper.createArrayNode();
399 collection.forEach(i -> arrayNode.add(i.toString()));
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700400 object.set(name, arrayNode);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700401 }
402 return this;
403 }
404
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800405 /**
Simon Hunt4f3a4072016-10-17 17:52:11 -0700406 * Returns true if this config contains a field with the given name.
407 *
408 * @param name the field name
409 * @return true if field is present, false otherwise
410 */
411 protected boolean hasField(String name) {
412 return hasField(object, name);
413 }
414
415 /**
416 * Returns true if the given node contains a field with the given name.
417 *
418 * @param node the node to examine
419 * @param name the name to look for
420 * @return true if the node has a field with the given name, false otherwise
421 */
422 protected boolean hasField(ObjectNode node, String name) {
423 Iterator<String> fnames = node.fieldNames();
424 while (fnames.hasNext()) {
425 if (fnames.next().equals(name)) {
426 return true;
427 }
428 }
429 return false;
430 }
431
432 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800433 * Indicates whether only the specified fields are present in the backing JSON.
434 *
435 * @param allowedFields allowed field names
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700436 * @return true if only allowedFields are present; false otherwise
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800437 */
438 protected boolean hasOnlyFields(String... allowedFields) {
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800439 return hasOnlyFields(object, allowedFields);
440 }
441
442 /**
443 * Indicates whether only the specified fields are present in a particular
444 * JSON object.
445 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700446 * @param node node whose fields to check
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800447 * @param allowedFields allowed field names
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700448 * @return true if only allowedFields are present; false otherwise
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800449 */
450 protected boolean hasOnlyFields(ObjectNode node, String... allowedFields) {
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800451 Set<String> fields = ImmutableSet.copyOf(allowedFields);
Jonathan Hart54b83e82016-03-26 20:37:20 -0700452 node.fieldNames().forEachRemaining(f -> {
453 if (!fields.contains(f)) {
454 throw new InvalidFieldException(f, "Field is not allowed");
455 }
456 });
457 return true;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800458 }
459
460 /**
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700461 * Indicates whether all specified fields are present in the backing JSON.
462 *
463 * @param mandatoryFields mandatory field names
464 * @return true if all mandatory fields are present; false otherwise
465 */
466 protected boolean hasFields(String... mandatoryFields) {
467 return hasFields(object, mandatoryFields);
468 }
469
470 /**
471 * Indicates whether all specified fields are present in a particular
472 * JSON object.
473 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700474 * @param node node whose fields to check
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700475 * @param mandatoryFields mandatory field names
476 * @return true if all mandatory fields are present; false otherwise
477 */
478 protected boolean hasFields(ObjectNode node, String... mandatoryFields) {
479 Set<String> fields = ImmutableSet.copyOf(mandatoryFields);
Jonathan Hart54b83e82016-03-26 20:37:20 -0700480 fields.forEach(f -> {
481 if (node.path(f).isMissingNode()) {
482 throw new InvalidFieldException(f, "Mandatory field is not present");
483 }
484 });
485 return true;
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700486 }
487
488 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800489 * Indicates whether the specified field holds a valid MAC address.
490 *
491 * @param field JSON field name
492 * @param presence specifies if field is optional or mandatory
493 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700494 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800495 */
496 protected boolean isMacAddress(String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700497 return isMacAddress(object, field, presence);
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800498 }
499
500 /**
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800501 * Indicates whether the specified field of a particular node holds a valid
502 * MAC address.
503 *
504 * @param objectNode JSON node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700505 * @param field JSON field name
506 * @param presence specifies if field is optional or mandatory
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800507 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700508 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800509 */
510 protected boolean isMacAddress(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700511 return isValid(objectNode, field, presence, n -> {
512 MacAddress.valueOf(n.asText());
513 return true;
514 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800515 }
516
517 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800518 * Indicates whether the specified field holds a valid IP address.
519 *
520 * @param field JSON field name
521 * @param presence specifies if field is optional or mandatory
522 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700523 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800524 */
525 protected boolean isIpAddress(String field, FieldPresence presence) {
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800526 return isIpAddress(object, field, presence);
527 }
528
529 /**
530 * Indicates whether the specified field of a particular node holds a valid
531 * IP address.
532 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700533 * @param objectNode node from whom to access the field
534 * @param field JSON field name
535 * @param presence specifies if field is optional or mandatory
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800536 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700537 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart3a8896b2016-02-16 13:06:26 -0800538 */
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800539 protected boolean isIpAddress(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700540 return isValid(objectNode, field, presence, n -> {
541 IpAddress.valueOf(n.asText());
542 return true;
543 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800544 }
545
546 /**
547 * Indicates whether the specified field holds a valid IP prefix.
548 *
549 * @param field JSON field name
550 * @param presence specifies if field is optional or mandatory
551 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700552 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800553 */
554 protected boolean isIpPrefix(String field, FieldPresence presence) {
555 return isIpPrefix(object, field, presence);
556 }
557
558 /**
559 * Indicates whether the specified field of a particular node holds a valid
560 * IP prefix.
561 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700562 * @param objectNode node from whom to access the field
563 * @param field JSON field name
564 * @param presence specifies if field is optional or mandatory
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800565 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700566 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800567 */
568 protected boolean isIpPrefix(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700569 return isValid(objectNode, field, presence, n -> {
570 IpPrefix.valueOf(n.asText());
571 return true;
572 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800573 }
574
575 /**
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700576 * Indicates whether the specified field holds a valid transport layer port.
577 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700578 * @param field JSON field name
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700579 * @param presence specifies if field is optional or mandatory
580 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700581 * @throws InvalidFieldException if the field is present but not valid
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700582 */
583 protected boolean isTpPort(String field, FieldPresence presence) {
584 return isTpPort(object, field, presence);
585 }
586
587 /**
588 * Indicates whether the specified field of a particular node holds a valid
589 * transport layer port.
590 *
591 * @param objectNode node from whom to access the field
Simon Hunt4f3a4072016-10-17 17:52:11 -0700592 * @param field JSON field name
593 * @param presence specifies if field is optional or mandatory
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700594 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700595 * @throws InvalidFieldException if the field is present but not valid
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700596 */
597 protected boolean isTpPort(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700598 return isValid(objectNode, field, presence, n -> {
599 TpPort.tpPort(n.asInt());
600 return true;
601 });
Hyunsun Moon61b73e92016-05-10 18:05:57 -0700602 }
603
604 /**
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800605 * Indicates whether the specified field holds a valid connect point string.
606 *
607 * @param field JSON field name
608 * @param presence specifies if field is optional or mandatory
609 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700610 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800611 */
612 protected boolean isConnectPoint(String field, FieldPresence presence) {
613 return isConnectPoint(object, field, presence);
614 }
615
616 /**
617 * Indicates whether the specified field of a particular node holds a valid
618 * connect point string.
619 *
620 * @param objectNode JSON node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700621 * @param field JSON field name
622 * @param presence specifies if field is optional or mandatory
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800623 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700624 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800625 */
626 protected boolean isConnectPoint(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700627 return isValid(objectNode, field, presence, n -> {
628 ConnectPoint.deviceConnectPoint(n.asText());
629 return true;
630 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800631 }
632
633 /**
634 * Indicates whether the specified field holds a valid string value.
635 *
636 * @param field JSON field name
637 * @param presence specifies if field is optional or mandatory
638 * @param pattern optional regex pattern
639 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700640 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800641 */
642 protected boolean isString(String field, FieldPresence presence, String... pattern) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800643 return isString(object, field, presence, pattern);
644 }
645
646 /**
647 * Indicates whether the specified field on a particular node holds a valid
648 * string value.
649 *
650 * @param objectNode JSON node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700651 * @param field JSON field name
652 * @param presence specifies if field is optional or mandatory
653 * @param pattern optional regex pattern
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800654 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700655 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800656 */
657 protected boolean isString(ObjectNode objectNode, String field,
658 FieldPresence presence, String... pattern) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700659 return isValid(objectNode, field, presence, (node) -> {
660 if (!(node.isTextual() &&
661 (pattern.length > 0 && node.asText().matches(pattern[0]) || pattern.length < 1))) {
662 fail("Invalid string value");
663 }
664 return true;
665 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800666 }
667
668 /**
669 * Indicates whether the specified field holds a valid number.
670 *
671 * @param field JSON field name
672 * @param presence specifies if field is optional or mandatory
673 * @param minMax optional min/max values
674 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700675 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800676 */
677 protected boolean isNumber(String field, FieldPresence presence, long... minMax) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700678 return isNumber(object, field, presence, minMax);
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800679 }
Jonathan Hart54b83e82016-03-26 20:37:20 -0700680
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800681 /**
Jonathan Hart54b83e82016-03-26 20:37:20 -0700682 * Indicates whether the specified field of a particular node holds a
683 * valid number.
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800684 *
Jonathan Hart54b83e82016-03-26 20:37:20 -0700685 * @param objectNode JSON object
Simon Hunt4f3a4072016-10-17 17:52:11 -0700686 * @param field JSON field name
687 * @param presence specifies if field is optional or mandatory
688 * @param minMax optional min/max values
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800689 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700690 * @throws InvalidFieldException if the field is present but not valid
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800691 */
Jonathan Hart54b83e82016-03-26 20:37:20 -0700692 protected boolean isNumber(ObjectNode objectNode, String field,
693 FieldPresence presence, long... minMax) {
694 return isValid(objectNode, field, presence, n -> {
695 long number = (n.isNumber()) ? n.asLong() : Long.parseLong(n.asText());
696 if (minMax.length > 1) {
697 verifyRange(number, minMax[0], minMax[1]);
698 } else if (minMax.length > 0) {
699 verifyRange(number, minMax[0]);
700 }
701 return true;
702 });
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800703 }
704
705 /**
706 * Indicates whether the specified field holds a valid integer.
707 *
708 * @param field JSON field name
709 * @param presence specifies if field is optional or mandatory
710 * @param minMax optional min/max values
711 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700712 * @throws InvalidFieldException if the field is present but not valid
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800713 */
714 protected boolean isIntegralNumber(String field, FieldPresence presence, long... minMax) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800715 return isIntegralNumber(object, field, presence, minMax);
716 }
717
718 /**
719 * Indicates whether the specified field of a particular node holds a valid
720 * integer.
721 *
722 * @param objectNode JSON node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700723 * @param field JSON field name
724 * @param presence specifies if field is optional or mandatory
725 * @param minMax optional min/max values
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800726 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700727 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800728 */
729 protected boolean isIntegralNumber(ObjectNode objectNode, String field,
730 FieldPresence presence, long... minMax) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700731 return isValid(objectNode, field, presence, n -> {
732 long number = (n.isIntegralNumber()) ? n.asLong() : Long.parseLong(n.asText());
733 if (minMax.length > 1) {
734 verifyRange(number, minMax[0], minMax[1]);
735 } else if (minMax.length > 0) {
736 verifyRange(number, minMax[0]);
737 }
738 return true;
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800739 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800740 }
741
742 /**
743 * Indicates whether the specified field holds a valid decimal number.
744 *
745 * @param field JSON field name
746 * @param presence specifies if field is optional or mandatory
747 * @param minMax optional min/max values
748 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700749 * @throws InvalidFieldException if the field is present but not valid
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800750 */
751 protected boolean isDecimal(String field, FieldPresence presence, double... minMax) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700752 return isDecimal(object, field, presence, minMax);
753 }
754
755 /**
756 * Indicates whether the specified field of a particular node holds a valid
757 * decimal number.
758 *
759 * @param objectNode JSON node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700760 * @param field JSON field name
761 * @param presence specifies if field is optional or mandatory
762 * @param minMax optional min/max values
Jonathan Hart54b83e82016-03-26 20:37:20 -0700763 * @return true if valid; false otherwise
764 * @throws InvalidFieldException if the field is present but not valid
765 */
766 protected boolean isDecimal(ObjectNode objectNode, String field,
767 FieldPresence presence, double... minMax) {
768 return isValid(objectNode, field, presence, n -> {
769 double number = (n.isDouble()) ? n.asDouble() : Double.parseDouble(n.asText());
770 if (minMax.length > 1) {
771 verifyRange(number, minMax[0], minMax[1]);
772 } else if (minMax.length > 0) {
773 verifyRange(number, minMax[0]);
774 }
775 return true;
776 });
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800777 }
778
779 /**
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530780 * Indicates whether the specified field holds a valid boolean value.
781 *
782 * @param field JSON field name
783 * @param presence specifies if field is optional or mandatory
784 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700785 * @throws InvalidFieldException if the field is present but not valid
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530786 */
787 protected boolean isBoolean(String field, FieldPresence presence) {
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800788 return isBoolean(object, field, presence);
789 }
790
791 /**
792 * Indicates whether the specified field of a particular node holds a valid
793 * boolean value.
794 *
795 * @param objectNode JSON object node
Simon Hunt4f3a4072016-10-17 17:52:11 -0700796 * @param field JSON field name
797 * @param presence specifies if field is optional or mandatory
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800798 * @return true if valid; false otherwise
Jonathan Hart54b83e82016-03-26 20:37:20 -0700799 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800800 */
801 protected boolean isBoolean(ObjectNode objectNode, String field, FieldPresence presence) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700802 return isValid(objectNode, field, presence, n -> {
803 if (!(n.isBoolean() || (n.isTextual() && isBooleanString(n.asText())))) {
804 fail("Field is not a boolean value");
805 }
806 return true;
807 });
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800808 }
809
810 /**
811 * Indicates whether a string holds a boolean literal value.
812 *
813 * @param str string to test
814 * @return true if the string contains "true" or "false" (case insensitive),
815 * otherwise false
816 */
817 private boolean isBooleanString(String str) {
818 return str.equalsIgnoreCase(TRUE_LITERAL) || str.equalsIgnoreCase(FALSE_LITERAL);
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530819 }
820
821 /**
Jonathan Hart54b83e82016-03-26 20:37:20 -0700822 * Indicates whether a field in the node is present and of correct value or
823 * not mandatory and absent.
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800824 *
Simon Hunt4f3a4072016-10-17 17:52:11 -0700825 * @param objectNode JSON object node containing field to validate
826 * @param field name of field to validate
827 * @param presence specified if field is optional or mandatory
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800828 * @param validationFunction function which can be used to verify if the
829 * node has the correct value
830 * @return true if the field is as expected
Jonathan Hart54b83e82016-03-26 20:37:20 -0700831 * @throws InvalidFieldException if the field is present but not valid
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800832 */
Jonathan Hart54b83e82016-03-26 20:37:20 -0700833 private boolean isValid(ObjectNode objectNode, String field, FieldPresence presence,
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800834 Function<JsonNode, Boolean> validationFunction) {
Jonathan Hart54b83e82016-03-26 20:37:20 -0700835 JsonNode node = objectNode.path(field);
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800836 boolean isMandatory = presence == FieldPresence.MANDATORY;
Jonathan Hart54b83e82016-03-26 20:37:20 -0700837 if (isMandatory && node.isMissingNode()) {
838 throw new InvalidFieldException(field, "Mandatory field not present");
Jonathan Hart9eb45bb2016-02-12 10:59:11 -0800839 }
840
841 if (!isMandatory && (node.isNull() || node.isMissingNode())) {
842 return true;
843 }
844
Jonathan Hart54b83e82016-03-26 20:37:20 -0700845 try {
846 if (validationFunction.apply(node)) {
847 return true;
848 } else {
849 throw new InvalidFieldException(field, "Validation error");
850 }
851 } catch (IllegalArgumentException e) {
852 throw new InvalidFieldException(field, e);
853 }
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800854 }
Jonathan Hart54b83e82016-03-26 20:37:20 -0700855
856 private static void fail(String message) {
857 throw new IllegalArgumentException(message);
858 }
859
860 private static <N extends Comparable> void verifyRange(N num, N min) {
861 if (num.compareTo(min) < 0) {
862 fail("Field must be greater than " + min);
863 }
864 }
865
866 private static <N extends Comparable> void verifyRange(N num, N min, N max) {
867 verifyRange(num, min);
868
869 if (num.compareTo(max) > 0) {
870 fail("Field must be less than " + max);
871 }
872 }
873
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700874}