Allow list-type config to be POSTed to subjectkey/subject/configkey endpoint.

Also add validation that the given JSON node is appropriate for the config
type (list vs object).

Change-Id: Ib1c12b538860a6f18b8311c5f5a786608c04beb8
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 5397f54..c3c2bb6 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
@@ -61,7 +61,11 @@
 import java.util.Set;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static org.onosproject.net.config.NetworkConfigEvent.Type.*;
+import static org.onosproject.net.config.NetworkConfigEvent.Type.CONFIG_ADDED;
+import static org.onosproject.net.config.NetworkConfigEvent.Type.CONFIG_REGISTERED;
+import static org.onosproject.net.config.NetworkConfigEvent.Type.CONFIG_REMOVED;
+import static org.onosproject.net.config.NetworkConfigEvent.Type.CONFIG_UNREGISTERED;
+import static org.onosproject.net.config.NetworkConfigEvent.Type.CONFIG_UPDATED;
 
 /**
  * Implementation of a distributed network configuration store.
@@ -77,6 +81,10 @@
     private static final int MAX_BACKOFF = 10;
     private static final String INVALID_CONFIG_JSON =
             "JSON node does not contain valid configuration";
+    private static final String INVALID_JSON_LIST =
+            "JSON node is not a list for list type config";
+    private static final String INVALID_JSON_OBJECT =
+            "JSON node is not an object for object type config";
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected StorageService storageService;
@@ -262,14 +270,37 @@
      * @return config object or null of no factory found or if the specified
      * JSON is null
      */
-    @SuppressWarnings("unchecked")
     private <S, C extends Config<S>> C createConfig(S subject, Class<C> configClass,
                                                     JsonNode json) {
+        return createConfig(subject, configClass, json, false);
+    }
+
+    /**
+     * Produces a config from the specified subject, config class and raw JSON.
+     *
+     * The config can optionally be detached, which means it does not contain a
+     * reference to an apply delegate. This means a detached config can not be
+     * applied. This should be used only for passing the config object in the
+     * NetworkConfigEvent.
+     *
+     * @param subject     config subject
+     * @param configClass config class
+     * @param json        raw JSON data
+     * @param detached    whether the config should be detached, that is, should
+     *                    be created without setting an apply delegate.
+     * @return config object or null of no factory found or if the specified
+     * JSON is null
+     */
+    @SuppressWarnings("unchecked")
+    private <S, C extends Config<S>> C createConfig(S subject, Class<C> configClass,
+                                                    JsonNode json, boolean detached) {
         if (json != null) {
             ConfigFactory<S, C> factory = factoriesByConfig.get(configClass.getName());
             if (factory != null) {
+                validateJsonType(json, factory);
                 C config = factory.createConfig();
-                config.init(subject, factory.configKey(), json, mapper, applyDelegate);
+                config.init(subject, factory.configKey(), json, mapper,
+                        detached ? null : applyDelegate);
                 return config;
             }
         }
@@ -277,30 +308,27 @@
     }
 
     /**
-     * Produces a detached config from the specified subject, config class and
-     * raw JSON.
+     * Validates that the type of the JSON node is appropriate for the type of
+     * configuration. A list type configuration must be created with an
+     * ArrayNode, and an object type configuration must be created with an
+     * ObjectNode.
      *
-     * A detached config can no longer be applied. This should be used only for
-     * passing the config object in the NetworkConfigEvent.
-     *
-     * @param subject     config subject
-     * @param configClass config class
-     * @param json        raw JSON data
-     * @return config object or null of no factory found or if the specified
-     * JSON is null
+     * @param json JSON node to check
+     * @param factory config factory of configuration
+     * @param <S> subject
+     * @param <C> configuration
+     * @return true if the JSON node type is appropriate for the configuration
      */
-    @SuppressWarnings("unchecked")
-    private Config createDetachedConfig(Object subject,
-            Class configClass, JsonNode json) {
-        if (json != null) {
-            ConfigFactory factory = factoriesByConfig.get(configClass.getName());
-            if (factory != null) {
-                Config config = factory.createConfig();
-                config.init(subject, factory.configKey(), json, mapper, null);
-                return config;
-            }
+    private <S, C extends Config<S>> boolean validateJsonType(JsonNode json,
+                                                              ConfigFactory<S, C> factory) {
+        if (factory.isList() && !(json instanceof ArrayNode)) {
+            throw new IllegalArgumentException(INVALID_JSON_LIST);
         }
-        return null;
+        if (!factory.isList() && !(json instanceof ObjectNode)) {
+            throw new IllegalArgumentException(INVALID_JSON_OBJECT);
+        }
+
+        return true;
     }
 
 
@@ -380,11 +408,11 @@
                 Versioned<JsonNode> oldValue = event.oldValue();
 
                 Config config = (newValue != null) ?
-                        createDetachedConfig(subject, configClass, newValue.value()) :
-                        null;
+                                createConfig(subject, configClass, newValue.value(), true) :
+                                null;
                 Config prevConfig = (oldValue != null) ?
-                        createDetachedConfig(subject, configClass, oldValue.value()) :
-                        null;
+                                    createConfig(subject, configClass, oldValue.value(), true) :
+                                    null;
 
                 NetworkConfigEvent.Type type;
                 switch (event.type()) {
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/NetworkConfigWebResource.java b/web/api/src/main/java/org/onosproject/rest/resources/NetworkConfigWebResource.java
index 9e301f5..8c073ad 100644
--- a/web/api/src/main/java/org/onosproject/rest/resources/NetworkConfigWebResource.java
+++ b/web/api/src/main/java/org/onosproject/rest/resources/NetworkConfigWebResource.java
@@ -15,9 +15,12 @@
  */
 package org.onosproject.rest.resources;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Set;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.SubjectFactory;
+import org.onosproject.rest.AbstractWebResource;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -28,13 +31,9 @@
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-
-import org.onosproject.net.config.Config;
-import org.onosproject.net.config.NetworkConfigService;
-import org.onosproject.net.config.SubjectFactory;
-import org.onosproject.rest.AbstractWebResource;
-
-import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
 
 import static org.onlab.util.Tools.emptyIsNotFound;
 import static org.onlab.util.Tools.nullIsNotFound;
@@ -263,7 +262,7 @@
                            @PathParam("configKey") String configKey,
                            InputStream request) throws IOException {
         NetworkConfigService service = get(NetworkConfigService.class);
-        ObjectNode root = (ObjectNode) mapper().readTree(request);
+        JsonNode root = mapper().readTree(request);
         service.applyConfig(subjectClassKey,
                             service.getSubjectFactory(subjectClassKey).createSubject(subjectKey),
                             configKey, root);