Adding ability for Config to be backed by generic JsonNode, i.e. either ObjectNode or ArrayNode.

Change-Id: I5f9ec423cd5f23f61c97a57073d9d11071c47997
diff --git a/apps/routing-api/src/main/java/org/onosproject/routing/config/BgpConfig.java b/apps/routing-api/src/main/java/org/onosproject/routing/config/BgpConfig.java
index 54e2e0e..19107be 100644
--- a/apps/routing-api/src/main/java/org/onosproject/routing/config/BgpConfig.java
+++ b/apps/routing-api/src/main/java/org/onosproject/routing/config/BgpConfig.java
@@ -46,7 +46,7 @@
     public Set<BgpSpeakerConfig> bgpSpeakers() {
         Set<BgpSpeakerConfig> speakers = Sets.newHashSet();
 
-        JsonNode speakersNode = node.get(SPEAKERS);
+        JsonNode speakersNode = object.get(SPEAKERS);
         speakersNode.forEach(jsonNode -> {
             Set<IpAddress> listenAddresses = Sets.newHashSet();
             jsonNode.path(PEERS).forEach(addressNode ->
diff --git a/cli/src/main/java/org/onosproject/cli/cfg/NetworkConfigCommand.java b/cli/src/main/java/org/onosproject/cli/cfg/NetworkConfigCommand.java
index a254e47..5f2f86e 100644
--- a/cli/src/main/java/org/onosproject/cli/cfg/NetworkConfigCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/cfg/NetworkConfigCommand.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.cli.cfg;
 
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.karaf.shell.commands.Argument;
@@ -51,17 +52,17 @@
     @Override
     protected void execute() {
         service = get(NetworkConfigService.class);
-        ObjectNode root = new ObjectMapper().createObjectNode();
+        JsonNode root = new ObjectMapper().createObjectNode();
         if (isNullOrEmpty(subjectKey)) {
-            addAll(root);
+            addAll((ObjectNode) root);
         } else {
             SubjectFactory subjectFactory = service.getSubjectFactory(subjectKey);
             if (isNullOrEmpty(subject)) {
-                addSubjectClass(root, subjectFactory);
+                addSubjectClass((ObjectNode) root, subjectFactory);
             } else {
                 Object s = subjectFactory.createSubject(subject);
                 if (isNullOrEmpty(configKey)) {
-                    addSubject(root, s);
+                    addSubject((ObjectNode) root, s);
                 } else {
                     root = getSubjectConfig(getConfig(s, subjectKey, configKey));
                 }
@@ -89,7 +90,7 @@
         service.getConfigs(s).forEach(c -> root.set(c.key(), c.node()));
     }
 
-    private ObjectNode getSubjectConfig(Config config) {
+    private JsonNode getSubjectConfig(Config config) {
         return config != null ? config.node() : null;
     }
 
diff --git a/core/api/src/main/java/org/onosproject/net/config/Config.java b/core/api/src/main/java/org/onosproject/net/config/Config.java
index ee7b584..068ff88 100644
--- a/core/api/src/main/java/org/onosproject/net/config/Config.java
+++ b/core/api/src/main/java/org/onosproject/net/config/Config.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.net.config;
 
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -41,8 +42,12 @@
 
     protected S subject;
     protected String key;
-    protected ObjectNode node;
+
+    protected JsonNode node;
+    protected ObjectNode object;
+    protected ArrayNode array;
     protected ObjectMapper mapper;
+
     protected ConfigApplyDelegate delegate;
 
     /**
@@ -50,15 +55,17 @@
      *
      * @param subject  configuration subject
      * @param key      configuration key
-     * @param node     JSON object node where configuration data is stored
+     * @param node     JSON node where configuration data is stored
      * @param mapper   JSON object mapper
      * @param delegate delegate context
      */
-    public void init(S subject, String key, ObjectNode node, ObjectMapper mapper,
+    public void init(S subject, String key, JsonNode node, ObjectMapper mapper,
                      ConfigApplyDelegate delegate) {
         this.subject = checkNotNull(subject);
         this.key = key;
         this.node = checkNotNull(node);
+        this.object = node instanceof ObjectNode ? (ObjectNode) node : null;
+        this.array = node instanceof ArrayNode ? (ArrayNode) node : null;
         this.mapper = checkNotNull(mapper);
         this.delegate = checkNotNull(delegate);
     }
@@ -88,8 +95,8 @@
      *
      * @return JSON node backing the configuration
      */
-    public ObjectNode node() {
-        return node;
+    public JsonNode node() {
+        return object;
     }
 
     /**
@@ -110,7 +117,7 @@
      * @return property value or default value
      */
     protected String get(String name, String defaultValue) {
-        return node.path(name).asText(defaultValue);
+        return object.path(name).asText(defaultValue);
     }
 
     /**
@@ -122,9 +129,9 @@
      */
     protected Config<S> setOrClear(String name, String value) {
         if (value != null) {
-            node.put(name, value);
+            object.put(name, value);
         } else {
-            node.remove(name);
+            object.remove(name);
         }
         return this;
     }
@@ -137,7 +144,7 @@
      * @return property value or default value
      */
     protected boolean get(String name, boolean defaultValue) {
-        return node.path(name).asBoolean(defaultValue);
+        return object.path(name).asBoolean(defaultValue);
     }
 
     /**
@@ -149,9 +156,9 @@
      */
     protected Config<S> setOrClear(String name, Boolean value) {
         if (value != null) {
-            node.put(name, value.booleanValue());
+            object.put(name, value.booleanValue());
         } else {
-            node.remove(name);
+            object.remove(name);
         }
         return this;
     }
@@ -164,7 +171,7 @@
      * @return property value or default value
      */
     protected int get(String name, int defaultValue) {
-        return node.path(name).asInt(defaultValue);
+        return object.path(name).asInt(defaultValue);
     }
 
     /**
@@ -176,9 +183,9 @@
      */
     protected Config<S> setOrClear(String name, Integer value) {
         if (value != null) {
-            node.put(name, value.intValue());
+            object.put(name, value.intValue());
         } else {
-            node.remove(name);
+            object.remove(name);
         }
         return this;
     }
@@ -191,7 +198,7 @@
      * @return property value or default value
      */
     protected long get(String name, long defaultValue) {
-        return node.path(name).asLong(defaultValue);
+        return object.path(name).asLong(defaultValue);
     }
 
     /**
@@ -203,9 +210,9 @@
      */
     protected Config<S> setOrClear(String name, Long value) {
         if (value != null) {
-            node.put(name, value.longValue());
+            object.put(name, value.longValue());
         } else {
-            node.remove(name);
+            object.remove(name);
         }
         return this;
     }
@@ -218,7 +225,7 @@
      * @return property value or default value
      */
     protected double get(String name, double defaultValue) {
-        return node.path(name).asDouble(defaultValue);
+        return object.path(name).asDouble(defaultValue);
     }
 
     /**
@@ -230,9 +237,9 @@
      */
     protected Config<S> setOrClear(String name, Double value) {
         if (value != null) {
-            node.put(name, value.doubleValue());
+            object.put(name, value.doubleValue());
         } else {
-            node.remove(name);
+            object.remove(name);
         }
         return this;
     }
@@ -247,7 +254,7 @@
      * @return property value or default value
      */
     protected <E extends Enum<E>> E get(String name, E defaultValue, Class<E> enumClass) {
-        return Enum.valueOf(enumClass, node.path(name).asText(defaultValue.toString()));
+        return Enum.valueOf(enumClass, object.path(name).asText(defaultValue.toString()));
     }
 
     /**
@@ -260,9 +267,9 @@
      */
     protected <E extends Enum> Config<S> setOrClear(String name, E value) {
         if (value != null) {
-            node.put(name, value.toString());
+            object.put(name, value.toString());
         } else {
-            node.remove(name);
+            object.remove(name);
         }
         return this;
     }
@@ -277,7 +284,7 @@
      */
     protected <T> List<T> getList(String name, Function<String, T> function) {
         List<T> list = Lists.newArrayList();
-        ArrayNode arrayNode = (ArrayNode) node.path(name);
+        ArrayNode arrayNode = (ArrayNode) object.path(name);
         arrayNode.forEach(i -> list.add(function.apply(i.asText())));
         return list;
     }
@@ -293,11 +300,11 @@
      */
     protected <T> Config<S> setOrClear(String name, Collection<T> collection) {
         if (collection == null) {
-            node.remove(name);
+            object.remove(name);
         } else {
             ArrayNode arrayNode = mapper.createArrayNode();
             collection.forEach(i -> arrayNode.add(i.toString()));
-            node.set(name, arrayNode);
+            object.set(name, arrayNode);
         }
         return this;
     }
diff --git a/core/api/src/main/java/org/onosproject/net/config/ConfigFactory.java b/core/api/src/main/java/org/onosproject/net/config/ConfigFactory.java
index f8b72df..25a3402 100644
--- a/core/api/src/main/java/org/onosproject/net/config/ConfigFactory.java
+++ b/core/api/src/main/java/org/onosproject/net/config/ConfigFactory.java
@@ -30,6 +30,7 @@
     private final SubjectFactory<S> subjectFactory;
     private final Class<C> configClass;
     private final String configKey;
+    private final boolean isList;
 
     /**
      * Creates a new configuration factory for the specified class of subjects
@@ -38,14 +39,37 @@
      * composite JSON trees.
      *
      * @param subjectFactory subject factory
-     * @param configClass  configuration class
-     * @param configKey    configuration class key
+     * @param configClass    configuration class
+     * @param configKey      configuration class key
      */
     protected ConfigFactory(SubjectFactory<S> subjectFactory,
                             Class<C> configClass, String configKey) {
+        this(subjectFactory, configClass, configKey, false);
+    }
+
+    /**
+     * Creates a new configuration factory for the specified class of subjects
+     * capable of generating the configurations of the specified class. The
+     * subject and configuration class keys are used merely as keys for use in
+     * composite JSON trees.
+     * <p>
+     * Note that configurations backed by JSON array are not easily extensible
+     * at the top-level as they are inherently limited to holding an ordered
+     * list of items.
+     * </p>
+     *
+     * @param subjectFactory subject factory
+     * @param configClass    configuration class
+     * @param configKey      configuration class key
+     * @param isList         true to indicate backing by JSON array
+     */
+    protected ConfigFactory(SubjectFactory<S> subjectFactory,
+                            Class<C> configClass, String configKey,
+                            boolean isList) {
         this.subjectFactory = subjectFactory;
         this.configClass = configClass;
         this.configKey = configKey;
+        this.isList = isList;
     }
 
     /**
@@ -85,4 +109,14 @@
      */
     public abstract C createConfig();
 
+    /**
+     * Indicates whether the configuration is a list and should be backed by
+     * a JSON array rather than JSON object.
+     *
+     * @return true if backed by JSON array
+     */
+    public boolean isList() {
+        return isList;
+    }
+
 }
diff --git a/core/api/src/main/java/org/onosproject/net/config/NetworkConfigService.java b/core/api/src/main/java/org/onosproject/net/config/NetworkConfigService.java
index 465751b..c1eed98 100644
--- a/core/api/src/main/java/org/onosproject/net/config/NetworkConfigService.java
+++ b/core/api/src/main/java/org/onosproject/net/config/NetworkConfigService.java
@@ -15,7 +15,7 @@
  */
 package org.onosproject.net.config;
 
-import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.annotations.Beta;
 import org.onosproject.event.ListenerService;
 
@@ -130,7 +130,7 @@
      * @return configuration or null if one is not available
      */
     <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass,
-                                           ObjectNode json);
+                                           JsonNode json);
 
     /**
      * Clears any configuration for the specified subject and configuration
diff --git a/core/api/src/main/java/org/onosproject/net/config/NetworkConfigStore.java b/core/api/src/main/java/org/onosproject/net/config/NetworkConfigStore.java
index c06fe6d..9dd66e8 100644
--- a/core/api/src/main/java/org/onosproject/net/config/NetworkConfigStore.java
+++ b/core/api/src/main/java/org/onosproject/net/config/NetworkConfigStore.java
@@ -15,7 +15,7 @@
  */
 package org.onosproject.net.config;
 
-import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.JsonNode;
 import org.onosproject.store.Store;
 
 import java.util.Set;
@@ -115,7 +115,7 @@
      * @return configuration object
      */
     <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass,
-                                           ObjectNode json);
+                                           JsonNode json);
 
     /**
      * Clears the configuration of the given class for the specified subject.
diff --git a/core/api/src/main/java/org/onosproject/net/config/basics/OpticalPortConfig.java b/core/api/src/main/java/org/onosproject/net/config/basics/OpticalPortConfig.java
index d0ad5c3..b06c422 100644
--- a/core/api/src/main/java/org/onosproject/net/config/basics/OpticalPortConfig.java
+++ b/core/api/src/main/java/org/onosproject/net/config/basics/OpticalPortConfig.java
@@ -33,7 +33,7 @@
      * @return the port type, or null if invalid or unset
      */
     public Port.Type type() {
-        JsonNode type = node.path(TYPE);
+        JsonNode type = object.path(TYPE);
         if (type.isMissingNode()) {
             return null;
         }
@@ -72,7 +72,7 @@
     }
 
     private String getStringValue(String field) {
-        JsonNode name = node.path(field);
+        JsonNode name = object.path(field);
         return name.isMissingNode() ? "" : name.asText();
     }
 
@@ -84,7 +84,7 @@
      * @return an Optional that may contain a frequency value.
      */
     public Optional<Long> staticLambda() {
-        JsonNode sl = node.path(STATIC_LAMBDA);
+        JsonNode sl = object.path(STATIC_LAMBDA);
         if (sl.isMissingNode()) {
             return Optional.empty();
         }
@@ -98,7 +98,7 @@
      * @return a port speed value whose default is 0.
      */
     public Optional<Integer> speed() {
-        JsonNode s = node.path(SPEED);
+        JsonNode s = object.path(SPEED);
         if (s.isMissingNode()) {
             return Optional.empty();
         }
diff --git a/core/api/src/test/java/org/onosproject/net/config/NetworkConfigServiceAdapter.java b/core/api/src/test/java/org/onosproject/net/config/NetworkConfigServiceAdapter.java
index 06632ca..b70d14e 100644
--- a/core/api/src/test/java/org/onosproject/net/config/NetworkConfigServiceAdapter.java
+++ b/core/api/src/test/java/org/onosproject/net/config/NetworkConfigServiceAdapter.java
@@ -1,4 +1,3 @@
-
 /*
  * Copyright 2015 Open Networking Laboratory
  *
@@ -14,10 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.onosproject.net.config;
 
-import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.JsonNode;
 
 import java.util.Set;
 
@@ -71,7 +69,7 @@
     }
 
     @Override
-    public <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass, ObjectNode json) {
+    public <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass, JsonNode json) {
         return null;
     }
 
diff --git a/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigManager.java b/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigManager.java
index 5c6cc0e..5cd96ca 100644
--- a/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigManager.java
+++ b/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigManager.java
@@ -15,7 +15,7 @@
  */
 package org.onosproject.net.config.impl;
 
-import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 import org.apache.felix.scr.annotations.Activate;
@@ -197,7 +197,7 @@
     }
 
     @Override
-    public <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass, ObjectNode json) {
+    public <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass, JsonNode json) {
         checkNotNull(subject, NULL_SUBJECT_MSG);
         checkNotNull(configClass, NULL_CCLASS_MSG);
         return store.applyConfig(subject, configClass, json);
diff --git a/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java b/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java
index b33f32a..3e73d8f 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.store.config.impl;
 
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.BooleanNode;
@@ -77,12 +78,12 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected StorageService storageService;
 
-    private ConsistentMap<ConfigKey, ObjectNode> configs;
+    private ConsistentMap<ConfigKey, JsonNode> configs;
 
     private final Map<String, ConfigFactory> factoriesByConfig = Maps.newConcurrentMap();
     private final ObjectMapper mapper = new ObjectMapper();
     private final ConfigApplyDelegate applyDelegate = new InternalApplyDelegate();
-    private final MapEventListener<ConfigKey, ObjectNode> listener = new InternalMapListener();
+    private final MapEventListener<ConfigKey, JsonNode> listener = new InternalMapListener();
 
     @Activate
     public void activate() {
@@ -93,7 +94,7 @@
                           TextNode.class, BooleanNode.class,
                           LongNode.class, DoubleNode.class, ShortNode.class, IntNode.class);
 
-        configs = storageService.<ConfigKey, ObjectNode>consistentMapBuilder()
+        configs = storageService.<ConfigKey, JsonNode>consistentMapBuilder()
                 .withSerializer(Serializer.using(kryoBuilder.build()))
                 .withName("onos-network-configs")
                 .withRelaxedReadConsistency()
@@ -168,21 +169,24 @@
     @Override
     public <S, T extends Config<S>> T getConfig(S subject, Class<T> configClass) {
         // TODO: need to identify and address the root cause for timeouts.
-        Versioned<ObjectNode> json = Tools.retryable(configs::get, ConsistentMapException.class, 1, MAX_BACKOFF)
-                                          .apply(key(subject, configClass));
+        Versioned<JsonNode> json = Tools.retryable(configs::get, ConsistentMapException.class, 1, MAX_BACKOFF)
+                .apply(key(subject, configClass));
         return json != null ? createConfig(subject, configClass, json.value()) : null;
     }
 
 
     @Override
     public <S, C extends Config<S>> C createConfig(S subject, Class<C> configClass) {
-        Versioned<ObjectNode> json = configs.computeIfAbsent(key(subject, configClass),
-                                                             k -> mapper.createObjectNode());
+        ConfigFactory<S, C> factory = getConfigFactory(configClass);
+        Versioned<JsonNode> json = configs.computeIfAbsent(key(subject, configClass),
+                                                             k -> factory.isList() ?
+                                                                     mapper.createArrayNode() :
+                                                                     mapper.createObjectNode());
         return createConfig(subject, configClass, json.value());
     }
 
     @Override
-    public <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass, ObjectNode json) {
+    public <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass, JsonNode json) {
         return createConfig(subject, configClass,
                             configs.putAndGet(key(subject, configClass), json).value());
     }
@@ -203,7 +207,7 @@
      */
     @SuppressWarnings("unchecked")
     private <S, C extends Config<S>> C createConfig(S subject, Class<C> configClass,
-                                                    ObjectNode json) {
+                                                    JsonNode json) {
         if (json != null) {
             ConfigFactory<S, C> factory = factoriesByConfig.get(configClass.getName());
             if (factory != null) {
@@ -259,9 +263,9 @@
         }
     }
 
-    private class InternalMapListener implements MapEventListener<ConfigKey, ObjectNode> {
+    private class InternalMapListener implements MapEventListener<ConfigKey, JsonNode> {
         @Override
-        public void event(MapEvent<ConfigKey, ObjectNode> event) {
+        public void event(MapEvent<ConfigKey, JsonNode> event) {
             NetworkConfigEvent.Type type;
             switch (event.type()) {
                 case INSERT:
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java b/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java
index 600fe3d..acda179 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java
@@ -50,7 +50,8 @@
         Set<Interface> interfaces = Sets.newHashSet();
 
         try {
-            for (JsonNode intfNode : node.path(INTERFACES)) {
+            // TODO: rework this to take advantage of ArrayNode backing
+            for (JsonNode intfNode : object.path(INTERFACES)) {
                 Set<InterfaceIpAddress> ips = getIps(intfNode);
                 if (ips.isEmpty()) {
                     throw new ConfigException(IP_MISSING_ERROR);