blob: 1e2ee12b17bf06ab618b16911ee4283724e524d0 [file] [log] [blame]
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -07001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Ray Milkeya4122362015-08-18 15:19:08 -070016package org.onosproject.net.config;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070017
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070018import com.fasterxml.jackson.databind.JsonNode;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070019import com.fasterxml.jackson.databind.ObjectMapper;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070020import com.fasterxml.jackson.databind.node.ArrayNode;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070021import com.fasterxml.jackson.databind.node.ObjectNode;
Thomas Vachuskae2b7e7e2015-05-20 11:11:31 -070022import com.google.common.annotations.Beta;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080023import com.google.common.collect.ImmutableSet;
24import com.google.common.collect.Iterators;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070025import com.google.common.collect.Lists;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080026import org.onlab.packet.IpAddress;
27import org.onlab.packet.MacAddress;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070028
29import java.util.Collection;
30import java.util.List;
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080031import java.util.Set;
Brian O'Connorce2d8b52015-07-29 16:24:13 -070032import java.util.function.Function;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070033
34import static com.google.common.base.Preconditions.checkNotNull;
35
36/**
37 * Base abstraction of a configuration facade for a specific subject. Derived
Thomas Vachuska96d55b12015-05-11 08:52:03 -070038 * classes should keep all state in the specified JSON tree as that is the
39 * only state that will be distributed or persisted; this class is merely
40 * a facade for interacting with a particular facet of configuration on a
41 * given subject.
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070042 *
43 * @param <S> type of subject
44 */
Thomas Vachuskae2b7e7e2015-05-20 11:11:31 -070045@Beta
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070046public abstract class Config<S> {
47
Thomas Vachuska96d55b12015-05-11 08:52:03 -070048 protected S subject;
49 protected String key;
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070050
51 protected JsonNode node;
52 protected ObjectNode object;
53 protected ArrayNode array;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070054 protected ObjectMapper mapper;
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070055
Thomas Vachuska96d55b12015-05-11 08:52:03 -070056 protected ConfigApplyDelegate delegate;
57
58 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080059 * Indicator of whether a configuration JSON field is required.
60 */
61 public enum FieldPresence {
62 /**
63 * Signifies that config field is an optional one.
64 */
65 OPTIONAL,
66
67 /**
68 * Signifies that config field is mandatory.
69 */
70 MANDATORY
71 }
72
73 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -070074 * Initializes the configuration behaviour with necessary context.
75 *
76 * @param subject configuration subject
77 * @param key configuration key
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070078 * @param node JSON node where configuration data is stored
Thomas Vachuska96d55b12015-05-11 08:52:03 -070079 * @param mapper JSON object mapper
80 * @param delegate delegate context
81 */
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070082 public void init(S subject, String key, JsonNode node, ObjectMapper mapper,
Thomas Vachuska96d55b12015-05-11 08:52:03 -070083 ConfigApplyDelegate delegate) {
84 this.subject = checkNotNull(subject);
85 this.key = key;
86 this.node = checkNotNull(node);
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070087 this.object = node instanceof ObjectNode ? (ObjectNode) node : null;
88 this.array = node instanceof ArrayNode ? (ArrayNode) node : null;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070089 this.mapper = checkNotNull(mapper);
90 this.delegate = checkNotNull(delegate);
91 }
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -070092
93 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080094 * Indicates whether or not the backing JSON node contains valid data.
95 * <p>
96 * Default implementation returns true.
97 * Subclasses are expected to override this with their own validation.
Thomas Vachuska36008462016-01-07 15:38:20 -080098 * Implementations are free to throw a RuntimeException if data is invalid.
99 * * </p>
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800100 *
101 * @return true if the data is valid; false otherwise
Thomas Vachuska36008462016-01-07 15:38:20 -0800102 * @throws RuntimeException if configuration is invalid or completely foobar
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800103 */
104 public boolean isValid() {
Thomas Vachuska36008462016-01-07 15:38:20 -0800105 // Derivatives should use the provided set of predicates to test
106 // validity of their fields, e.g.:
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800107 // isString(path)
108 // isBoolean(path)
109 // isNumber(path, [min, max])
110 // isDecimal(path, [min, max])
111 // isMacAddress(path)
112 // isIpAddress(path)
113 return true;
114 }
115
116 /**
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700117 * Returns the specific subject to which this configuration pertains.
118 *
119 * @return configuration subject
120 */
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700121 public S subject() {
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700122 return subject;
123 }
124
125 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700126 * Returns the configuration key. This is primarily aimed for use in
127 * composite JSON trees in external representations and has no bearing on
128 * the internal behaviours.
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700129 *
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700130 * @return configuration key
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700131 */
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700132 public String key() {
133 return key;
134 }
135
136 /**
137 * Returns the JSON node that contains the configuration data.
138 *
139 * @return JSON node backing the configuration
140 */
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700141 public JsonNode node() {
Jonathan Harta8625482015-09-08 16:14:56 -0700142 return node;
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700143 }
144
145 /**
146 * Applies any configuration changes made via this configuration.
147 */
148 public void apply() {
149 delegate.onApply(this);
150 }
151
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700152
153 // Miscellaneous helpers for interacting with JSON
154
155 /**
156 * Gets the specified property as a string.
157 *
158 * @param name property name
159 * @param defaultValue default value if property not set
160 * @return property value or default value
161 */
162 protected String get(String name, String defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700163 return object.path(name).asText(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700164 }
165
166 /**
167 * Sets the specified property as a string or clears it if null value given.
168 *
169 * @param name property name
170 * @param value new value or null to clear the property
171 * @return self
172 */
173 protected Config<S> setOrClear(String name, String value) {
174 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700175 object.put(name, value);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700176 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700177 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700178 }
179 return this;
180 }
181
182 /**
183 * Gets the specified property as a boolean.
184 *
185 * @param name property name
186 * @param defaultValue default value if property not set
187 * @return property value or default value
188 */
189 protected boolean get(String name, boolean defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700190 return object.path(name).asBoolean(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700191 }
192
193 /**
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800194 * Clears the specified property.
195 *
196 * @param name property name
197 * @return self
198 */
199 protected Config<S> clear(String name) {
200 object.remove(name);
201 return this;
202 }
203
204 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700205 * Sets the specified property as a boolean or clears it if null value given.
206 *
207 * @param name property name
208 * @param value new value or null to clear the property
209 * @return self
210 */
211 protected Config<S> setOrClear(String name, Boolean value) {
212 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700213 object.put(name, value.booleanValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700214 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700215 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700216 }
217 return this;
218 }
219
220 /**
Jonathan Hart111b42b2015-07-14 13:28:05 -0700221 * Gets the specified property as an integer.
222 *
223 * @param name property name
224 * @param defaultValue default value if property not set
225 * @return property value or default value
226 */
227 protected int get(String name, int defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700228 return object.path(name).asInt(defaultValue);
Jonathan Hart111b42b2015-07-14 13:28:05 -0700229 }
230
231 /**
232 * Sets the specified property as an integer or clears it if null value given.
233 *
234 * @param name property name
235 * @param value new value or null to clear the property
236 * @return self
237 */
238 protected Config<S> setOrClear(String name, Integer value) {
239 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700240 object.put(name, value.intValue());
Jonathan Hart111b42b2015-07-14 13:28:05 -0700241 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700242 object.remove(name);
Jonathan Hart111b42b2015-07-14 13:28:05 -0700243 }
244 return this;
245 }
246
247 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700248 * Gets the specified property as a long.
249 *
250 * @param name property name
251 * @param defaultValue default value if property not set
252 * @return property value or default value
253 */
254 protected long get(String name, long defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700255 return object.path(name).asLong(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700256 }
257
258 /**
259 * Sets the specified property as a long or clears it if null value given.
260 *
261 * @param name property name
262 * @param value new value or null to clear the property
263 * @return self
264 */
265 protected Config<S> setOrClear(String name, Long value) {
266 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700267 object.put(name, value.longValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700268 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700269 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700270 }
271 return this;
272 }
273
274 /**
275 * Gets the specified property as a double.
276 *
277 * @param name property name
278 * @param defaultValue default value if property not set
279 * @return property value or default value
280 */
281 protected double get(String name, double defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700282 return object.path(name).asDouble(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700283 }
284
285 /**
286 * Sets the specified property as a double or clears it if null value given.
287 *
288 * @param name property name
289 * @param value new value or null to clear the property
290 * @return self
291 */
292 protected Config<S> setOrClear(String name, Double value) {
293 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700294 object.put(name, value.doubleValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700295 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700296 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700297 }
298 return this;
299 }
300
301 /**
302 * Gets the specified property as an enum.
303 *
304 * @param name property name
305 * @param defaultValue default value if property not set
306 * @param enumClass the enum class
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700307 * @param <E> type of enum
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700308 * @return property value or default value
309 */
310 protected <E extends Enum<E>> E get(String name, E defaultValue, Class<E> enumClass) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700311 return Enum.valueOf(enumClass, object.path(name).asText(defaultValue.toString()));
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700312 }
313
314 /**
315 * Sets the specified property as a double or clears it if null value given.
316 *
317 * @param name property name
318 * @param value new value or null to clear the property
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700319 * @param <E> type of enum
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700320 * @return self
321 */
322 protected <E extends Enum> Config<S> setOrClear(String name, E value) {
323 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700324 object.put(name, value.toString());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700325 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700326 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700327 }
328 return this;
329 }
Jonathan Hart111b42b2015-07-14 13:28:05 -0700330
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700331 /**
332 * Gets the specified array property as a list of items.
333 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700334 * @param name property name
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700335 * @param function mapper from string to item
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700336 * @param <T> type of item
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700337 * @return list of items
338 */
339 protected <T> List<T> getList(String name, Function<String, T> function) {
340 List<T> list = Lists.newArrayList();
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700341 ArrayNode arrayNode = (ArrayNode) object.path(name);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700342 arrayNode.forEach(i -> list.add(function.apply(i.asText())));
343 return list;
344 }
345
346 /**
Naoki Shiota399a0b32015-11-15 20:36:13 -0600347 * Gets the specified array property as a list of items.
348 *
349 * @param name property name
350 * @param function mapper from string to item
351 * @param defaultValue default value if property not set
352 * @param <T> type of item
353 * @return list of items
354 */
355 protected <T> List<T> getList(String name, Function<String, T> function, List<T> defaultValue) {
356 List<T> list = Lists.newArrayList();
357 JsonNode jsonNode = object.path(name);
358 if (jsonNode.isMissingNode()) {
359 return defaultValue;
360 }
361 ArrayNode arrayNode = (ArrayNode) jsonNode;
362 arrayNode.forEach(i -> list.add(function.apply(i.asText())));
363 return list;
364 }
365
366 /**
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700367 * Sets the specified property as an array of items in a given collection or
368 * clears it if null is given.
369 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700370 * @param name propertyName
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700371 * @param collection collection of items
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700372 * @param <T> type of items
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700373 * @return self
374 */
375 protected <T> Config<S> setOrClear(String name, Collection<T> collection) {
376 if (collection == null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700377 object.remove(name);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700378 } else {
379 ArrayNode arrayNode = mapper.createArrayNode();
380 collection.forEach(i -> arrayNode.add(i.toString()));
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700381 object.set(name, arrayNode);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700382 }
383 return this;
384 }
385
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800386 /**
387 * Indicates whether only the specified fields are present in the backing JSON.
388 *
389 * @param allowedFields allowed field names
390 * @return true if all allowedFields are present; false otherwise
391 */
392 protected boolean hasOnlyFields(String... allowedFields) {
393 Set<String> fields = ImmutableSet.copyOf(allowedFields);
394 return !Iterators.any(object.fieldNames(), f -> !fields.contains(f));
395 }
396
397 /**
398 * Indicates whether the specified field holds a valid MAC address.
399 *
400 * @param field JSON field name
401 * @param presence specifies if field is optional or mandatory
402 * @return true if valid; false otherwise
403 * @throws IllegalArgumentException if field is present, but not valid MAC
404 */
405 protected boolean isMacAddress(String field, FieldPresence presence) {
406 JsonNode node = object.path(field);
407 return isValid(node, presence, node.isTextual() &&
408 MacAddress.valueOf(node.asText()) != null);
409 }
410
411 /**
412 * Indicates whether the specified field holds a valid IP address.
413 *
414 * @param field JSON field name
415 * @param presence specifies if field is optional or mandatory
416 * @return true if valid; false otherwise
417 * @throws IllegalArgumentException if field is present, but not valid IP
418 */
419 protected boolean isIpAddress(String field, FieldPresence presence) {
420 JsonNode node = object.path(field);
421 return isValid(node, presence, node.isTextual() &&
422 IpAddress.valueOf(node.asText()) != null);
423 }
424
425 /**
426 * Indicates whether the specified field holds a valid string value.
427 *
428 * @param field JSON field name
429 * @param presence specifies if field is optional or mandatory
430 * @param pattern optional regex pattern
431 * @return true if valid; false otherwise
432 * @throws IllegalArgumentException if field is present, but not valid MAC
433 */
434 protected boolean isString(String field, FieldPresence presence, String... pattern) {
435 JsonNode node = object.path(field);
436 return isValid(node, presence, node.isTextual() &&
437 (pattern.length > 0 && node.asText().matches(pattern[0]) || pattern.length < 1));
438 }
439
440 /**
441 * Indicates whether the specified field holds a valid number.
442 *
443 * @param field JSON field name
444 * @param presence specifies if field is optional or mandatory
445 * @param minMax optional min/max values
446 * @return true if valid; false otherwise
447 * @throws IllegalArgumentException if field is present, but not valid
448 */
449 protected boolean isNumber(String field, FieldPresence presence, long... minMax) {
450 JsonNode node = object.path(field);
HIGUCHI Yuta1d7c9cb2016-01-20 18:22:36 -0800451 return isValid(node, presence, node.isNumber() &&
452 (minMax.length > 0 && minMax[0] <= node.asLong() || minMax.length < 1) &&
453 (minMax.length > 1 && minMax[1] > node.asLong() || minMax.length < 2));
454 }
455 /**
456 * Indicates whether the specified field holds a valid number.
457 *
458 * @param field JSON field name
459 * @param presence specifies if field is optional or mandatory
460 * @param minMax optional min/max values
461 * @return true if valid; false otherwise
462 * @throws IllegalArgumentException if field is present, but not valid
463 */
464 protected boolean isNumber(String field, FieldPresence presence, double... minMax) {
465 JsonNode node = object.path(field);
466 return isValid(node, presence, node.isNumber() &&
467 (minMax.length > 0 && minMax[0] <= node.asDouble() || minMax.length < 1) &&
468 (minMax.length > 1 && minMax[1] > node.asDouble() || minMax.length < 2));
469 }
470
471 /**
472 * Indicates whether the specified field holds a valid integer.
473 *
474 * @param field JSON field name
475 * @param presence specifies if field is optional or mandatory
476 * @param minMax optional min/max values
477 * @return true if valid; false otherwise
478 * @throws IllegalArgumentException if field is present, but not valid
479 */
480 protected boolean isIntegralNumber(String field, FieldPresence presence, long... minMax) {
481 JsonNode node = object.path(field);
482 return isValid(node, presence, node.isIntegralNumber() &&
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800483 (minMax.length > 0 && minMax[0] <= node.asLong() || minMax.length < 1) &&
484 (minMax.length > 1 && minMax[1] > node.asLong() || minMax.length < 2));
485 }
486
487 /**
488 * Indicates whether the specified field holds a valid decimal number.
489 *
490 * @param field JSON field name
491 * @param presence specifies if field is optional or mandatory
492 * @param minMax optional min/max values
493 * @return true if valid; false otherwise
494 * @throws IllegalArgumentException if field is present, but not valid
495 */
496 protected boolean isDecimal(String field, FieldPresence presence, double... minMax) {
497 JsonNode node = object.path(field);
498 return isValid(node, presence, (node.isDouble() || node.isFloat()) &&
499 (minMax.length > 0 && minMax[0] <= node.asDouble() || minMax.length < 1) &&
500 (minMax.length > 1 && minMax[1] > node.asDouble() || minMax.length < 2));
501 }
502
503 /**
Thejaswi NK6f4ae1c2015-12-08 01:15:53 +0530504 * Indicates whether the specified field holds a valid boolean value.
505 *
506 * @param field JSON field name
507 * @param presence specifies if field is optional or mandatory
508 * @return true if valid; false otherwise
509 * @throws IllegalArgumentException if field is present, but not valid
510 */
511 protected boolean isBoolean(String field, FieldPresence presence) {
512 JsonNode node = object.path(field);
513 return isValid(node, presence, node.isBoolean());
514 }
515
516 /**
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800517 * Indicates whether the node is present and of correct value or not
518 * mandatory and absent.
519 *
520 * @param node JSON node
521 * @param presence specifies if field is optional or mandatory
522 * @param correctValue true if the value is correct
523 * @return true if the field is as expected
524 */
525 private boolean isValid(JsonNode node, FieldPresence presence, boolean correctValue) {
526 boolean isMandatory = presence == FieldPresence.MANDATORY;
527 return isMandatory && correctValue || !isMandatory && !node.isNull() || correctValue;
528 }
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700529}