blob: 5f2c9f3a40cb6370d1c2670ba32d459270c6cec3 [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.
98 * </p>
99 *
100 * @return true if the data is valid; false otherwise
101 */
102 public boolean isValid() {
103 // TODO: figure out what assertions could be made in the base class
104 // NOTE: The thought is to have none, but instead to provide a set
105 // of predicates to allow configs to test validity of present fields,
106 // e.g.:
107 // 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 /**
194 * Sets the specified property as a boolean or clears it if null value given.
195 *
196 * @param name property name
197 * @param value new value or null to clear the property
198 * @return self
199 */
200 protected Config<S> setOrClear(String name, Boolean value) {
201 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700202 object.put(name, value.booleanValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700203 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700204 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700205 }
206 return this;
207 }
208
209 /**
Jonathan Hart111b42b2015-07-14 13:28:05 -0700210 * Gets the specified property as an integer.
211 *
212 * @param name property name
213 * @param defaultValue default value if property not set
214 * @return property value or default value
215 */
216 protected int get(String name, int defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700217 return object.path(name).asInt(defaultValue);
Jonathan Hart111b42b2015-07-14 13:28:05 -0700218 }
219
220 /**
221 * Sets the specified property as an integer 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, Integer value) {
228 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700229 object.put(name, value.intValue());
Jonathan Hart111b42b2015-07-14 13:28:05 -0700230 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700231 object.remove(name);
Jonathan Hart111b42b2015-07-14 13:28:05 -0700232 }
233 return this;
234 }
235
236 /**
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700237 * Gets the specified property as a long.
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 long get(String name, long defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700244 return object.path(name).asLong(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700245 }
246
247 /**
248 * Sets the specified property as a long 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, Long value) {
255 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700256 object.put(name, value.longValue());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700257 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700258 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700259 }
260 return this;
261 }
262
263 /**
264 * Gets the specified property as a double.
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 double get(String name, double defaultValue) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700271 return object.path(name).asDouble(defaultValue);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700272 }
273
274 /**
275 * Sets the specified property as a double 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, Double value) {
282 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700283 object.put(name, value.doubleValue());
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 an enum.
292 *
293 * @param name property name
294 * @param defaultValue default value if property not set
295 * @param enumClass the enum class
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700296 * @param <E> type of enum
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700297 * @return property value or default value
298 */
299 protected <E extends Enum<E>> E get(String name, E defaultValue, Class<E> enumClass) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700300 return Enum.valueOf(enumClass, object.path(name).asText(defaultValue.toString()));
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700301 }
302
303 /**
304 * Sets the specified property as a double or clears it if null value given.
305 *
306 * @param name property name
307 * @param value new value or null to clear the property
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700308 * @param <E> type of enum
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700309 * @return self
310 */
311 protected <E extends Enum> Config<S> setOrClear(String name, E value) {
312 if (value != null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700313 object.put(name, value.toString());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700314 } else {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700315 object.remove(name);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700316 }
317 return this;
318 }
Jonathan Hart111b42b2015-07-14 13:28:05 -0700319
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700320 /**
321 * Gets the specified array property as a list of items.
322 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700323 * @param name property name
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700324 * @param function mapper from string to item
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700325 * @param <T> type of item
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700326 * @return list of items
327 */
328 protected <T> List<T> getList(String name, Function<String, T> function) {
329 List<T> list = Lists.newArrayList();
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700330 ArrayNode arrayNode = (ArrayNode) object.path(name);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700331 arrayNode.forEach(i -> list.add(function.apply(i.asText())));
332 return list;
333 }
334
335 /**
Naoki Shiota399a0b32015-11-15 20:36:13 -0600336 * Gets the specified array property as a list of items.
337 *
338 * @param name property name
339 * @param function mapper from string to item
340 * @param defaultValue default value if property not set
341 * @param <T> type of item
342 * @return list of items
343 */
344 protected <T> List<T> getList(String name, Function<String, T> function, List<T> defaultValue) {
345 List<T> list = Lists.newArrayList();
346 JsonNode jsonNode = object.path(name);
347 if (jsonNode.isMissingNode()) {
348 return defaultValue;
349 }
350 ArrayNode arrayNode = (ArrayNode) jsonNode;
351 arrayNode.forEach(i -> list.add(function.apply(i.asText())));
352 return list;
353 }
354
355 /**
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700356 * Sets the specified property as an array of items in a given collection or
357 * clears it if null is given.
358 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700359 * @param name propertyName
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700360 * @param collection collection of items
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700361 * @param <T> type of items
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700362 * @return self
363 */
364 protected <T> Config<S> setOrClear(String name, Collection<T> collection) {
365 if (collection == null) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700366 object.remove(name);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700367 } else {
368 ArrayNode arrayNode = mapper.createArrayNode();
369 collection.forEach(i -> arrayNode.add(i.toString()));
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700370 object.set(name, arrayNode);
Brian O'Connorce2d8b52015-07-29 16:24:13 -0700371 }
372 return this;
373 }
374
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800375 /**
376 * Indicates whether only the specified fields are present in the backing JSON.
377 *
378 * @param allowedFields allowed field names
379 * @return true if all allowedFields are present; false otherwise
380 */
381 protected boolean hasOnlyFields(String... allowedFields) {
382 Set<String> fields = ImmutableSet.copyOf(allowedFields);
383 return !Iterators.any(object.fieldNames(), f -> !fields.contains(f));
384 }
385
386 /**
387 * Indicates whether the specified field holds a valid MAC address.
388 *
389 * @param field JSON field name
390 * @param presence specifies if field is optional or mandatory
391 * @return true if valid; false otherwise
392 * @throws IllegalArgumentException if field is present, but not valid MAC
393 */
394 protected boolean isMacAddress(String field, FieldPresence presence) {
395 JsonNode node = object.path(field);
396 return isValid(node, presence, node.isTextual() &&
397 MacAddress.valueOf(node.asText()) != null);
398 }
399
400 /**
401 * Indicates whether the specified field holds a valid IP address.
402 *
403 * @param field JSON field name
404 * @param presence specifies if field is optional or mandatory
405 * @return true if valid; false otherwise
406 * @throws IllegalArgumentException if field is present, but not valid IP
407 */
408 protected boolean isIpAddress(String field, FieldPresence presence) {
409 JsonNode node = object.path(field);
410 return isValid(node, presence, node.isTextual() &&
411 IpAddress.valueOf(node.asText()) != null);
412 }
413
414 /**
415 * Indicates whether the specified field holds a valid string value.
416 *
417 * @param field JSON field name
418 * @param presence specifies if field is optional or mandatory
419 * @param pattern optional regex pattern
420 * @return true if valid; false otherwise
421 * @throws IllegalArgumentException if field is present, but not valid MAC
422 */
423 protected boolean isString(String field, FieldPresence presence, String... pattern) {
424 JsonNode node = object.path(field);
425 return isValid(node, presence, node.isTextual() &&
426 (pattern.length > 0 && node.asText().matches(pattern[0]) || pattern.length < 1));
427 }
428
429 /**
430 * Indicates whether the specified field holds a valid number.
431 *
432 * @param field JSON field name
433 * @param presence specifies if field is optional or mandatory
434 * @param minMax optional min/max values
435 * @return true if valid; false otherwise
436 * @throws IllegalArgumentException if field is present, but not valid
437 */
438 protected boolean isNumber(String field, FieldPresence presence, long... minMax) {
439 JsonNode node = object.path(field);
440 return isValid(node, presence, (node.isLong() || node.isInt()) &&
441 (minMax.length > 0 && minMax[0] <= node.asLong() || minMax.length < 1) &&
442 (minMax.length > 1 && minMax[1] > node.asLong() || minMax.length < 2));
443 }
444
445 /**
446 * Indicates whether the specified field holds a valid decimal number.
447 *
448 * @param field JSON field name
449 * @param presence specifies if field is optional or mandatory
450 * @param minMax optional min/max values
451 * @return true if valid; false otherwise
452 * @throws IllegalArgumentException if field is present, but not valid
453 */
454 protected boolean isDecimal(String field, FieldPresence presence, double... minMax) {
455 JsonNode node = object.path(field);
456 return isValid(node, presence, (node.isDouble() || node.isFloat()) &&
457 (minMax.length > 0 && minMax[0] <= node.asDouble() || minMax.length < 1) &&
458 (minMax.length > 1 && minMax[1] > node.asDouble() || minMax.length < 2));
459 }
460
461 /**
462 * Indicates whether the node is present and of correct value or not
463 * mandatory and absent.
464 *
465 * @param node JSON node
466 * @param presence specifies if field is optional or mandatory
467 * @param correctValue true if the value is correct
468 * @return true if the field is as expected
469 */
470 private boolean isValid(JsonNode node, FieldPresence presence, boolean correctValue) {
471 boolean isMandatory = presence == FieldPresence.MANDATORY;
472 return isMandatory && correctValue || !isMandatory && !node.isNull() || correctValue;
473 }
474
Thomas Vachuskaf0e1fae2015-04-24 00:51:51 -0700475}