Merge changes I85b49247,Icca28a4a
authorEd Warnicke <eaw@cisco.com>
Fri, 10 Jan 2014 17:40:32 +0000 (17:40 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Fri, 10 Jan 2014 17:40:32 +0000 (17:40 +0000)
* changes:
  Added DELETE operation
  Changed POST operation

24 files changed:
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfService.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfServiceLegacy.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonMapper.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.xtend
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/CompositeNodeWrapper.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.xtend
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/EmptyNodeWrapper.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/InstanceIdWithSchemaNode.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/NodeWrapper.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestCodec.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/SimpleNodeWrapper.java
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/cnsn/test/JsonToCnSnTest.java
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/InvokeRpcMethodTest.java
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/NormalizeNodeTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestConfigDataTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java
opendaylight/md-sal/sal-rest-connector/src/test/resources/normalize-node/yang/normalize-node-module [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface.xml [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface2.xml [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface3.xml [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/yang1/test-interface.yang [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/yang2/test-interface2.yang [new file with mode: 0644]

index 012b51fb5e339b6a0a4332e44d146b72f7e7ee76..60a8f285a2cc17188dcd4ea1cbb25055bb171a81 100644 (file)
@@ -7,6 +7,8 @@
  */
 package org.opendaylight.controller.sal.rest.api;
 
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.PUT;
@@ -66,30 +68,50 @@ public interface RestconfService extends RestconfServiceLegacy {
     @Produces({Draft01.MediaTypes.DATA+JSON,Draft01.MediaTypes.DATA+XML,
                Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, 
                MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML})
+    @Consumes({Draft01.MediaTypes.DATA+JSON,Draft01.MediaTypes.DATA+XML,
+               Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, 
+               MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML})
     public StructuredData invokeRpc(@PathParam("identifier") String identifier, CompositeNode payload);
     
+    @POST
+    @Path("/operations/{identifier}")
+    @Produces({Draft01.MediaTypes.DATA+JSON,Draft01.MediaTypes.DATA+XML,
+               Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, 
+               MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML})
+    public StructuredData invokeRpc(@PathParam("identifier") String identifier);
+    
     @GET
     @Path("/config/{identifier:.+}")
     @Produces({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, 
                MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML})
     public StructuredData readConfigurationData(@PathParam("identifier") String identifier);
-    
-    @POST
-    @Path("/config/{identifier:.+}")
+
+    @GET
+    @Path("/operational/{identifier:.+}")
     @Produces({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, 
                MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML})
-    public Response createConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload);
+    public StructuredData readOperationalData(@PathParam("identifier") String identifier);
 
     @PUT
     @Path("/config/{identifier:.+}")
-    @Produces({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, 
+    @Consumes({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, 
                MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML})
     public Response updateConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload);
 
-    @GET
-    @Path("/operational/{identifier:.+}")
-    @Produces({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, 
+    @POST
+    @Path("/config/{identifier:.+}")
+    @Consumes({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, 
                MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML})
-    public StructuredData readOperationalData(@PathParam("identifier") String identifier);
+    public Response createConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload);
+
+    @POST
+    @Path("/config")
+    @Consumes({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, 
+               MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML})
+    public Response createConfigurationData(CompositeNode payload);
+
+    @DELETE
+    @Path("/config/{identifier:.+}")
+    public Response deleteConfigurationData(@PathParam("identifier") String identifier);
 
 }
index 35da98b1a0db82e72d6cb32de8e0e34c858e1a41..9b69c1f09a9a3f546259b9cb4d0bc993ebb90b98 100644 (file)
@@ -1,5 +1,6 @@
 package org.opendaylight.controller.sal.rest.api;
 
+import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.PUT;
@@ -33,14 +34,14 @@ public interface RestconfServiceLegacy {
     @Deprecated
     @POST
     @Path("/datastore/{identifier:.+}")
-    @Produces({Draft01.MediaTypes.DATA+JSON,Draft01.MediaTypes.DATA+XML, 
+    @Consumes({Draft01.MediaTypes.DATA+JSON,Draft01.MediaTypes.DATA+XML, 
                MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML})
     public Response createConfigurationDataLegacy(@PathParam("identifier") String identifier, CompositeNode payload);
 
     @Deprecated
     @PUT
     @Path("/datastore/{identifier:.+}")
-    @Produces({Draft01.MediaTypes.DATA+JSON,Draft01.MediaTypes.DATA+XML, 
+    @Consumes({Draft01.MediaTypes.DATA+JSON,Draft01.MediaTypes.DATA+XML, 
                MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML})
     public Response updateConfigurationDataLegacy(@PathParam("identifier") String identifier, CompositeNode payload);
 
index 8b1bdef8ba71b9be2f72d8d9158a412d9d79a84e..7f7e8606c3228d8b2cbebff176a81a5e2fbf9017 100644 (file)
@@ -194,7 +194,7 @@ class JsonMapper {
             if (node.getValue() instanceof QName) {
                 IdentityValuesDTO valueDTO = (IdentityValuesDTO) RestCodec.from(baseType).serialize(node.getValue());
                 IdentityValue valueFromDTO = valueDTO.getValuesWithNamespaces().get(0);
-                String moduleName = ControllerContext.getInstance().findModuleByNamespace(
+                String moduleName = ControllerContext.getInstance().findModuleNameByNamespace(
                         URI.create(valueFromDTO.getNamespace()));
                 writer.value(moduleName + ":" + valueFromDTO.getValue());
             } else {
index df6b58c8971331b74c89418a6e1ee039e1fcdf10..343601865d80bfeaf3ca5899bdb59c08cee25041 100644 (file)
@@ -41,13 +41,13 @@ class BrokerFacade implements DataReader<InstanceIdentifier, CompositeNode> {
 
     override readConfigurationData(InstanceIdentifier path) {
         checkPreconditions
-        LOG.info("Read Configuration via Restconf: {}",path)
+        LOG.info("Read Configuration via Restconf: {}", path)
         return dataService.readConfigurationData(path);
     }
 
     override readOperationalData(InstanceIdentifier path) {
         checkPreconditions
-        LOG.info("Read Operational via Restconf: {}",path)
+        LOG.info("Read Operational via Restconf: {}", path)
         return dataService.readOperationalData(path);
     }
 
@@ -60,15 +60,28 @@ class BrokerFacade implements DataReader<InstanceIdentifier, CompositeNode> {
     def commitConfigurationDataPut(InstanceIdentifier path, CompositeNode payload) {
         checkPreconditions
         val transaction = dataService.beginTransaction;
+        LOG.info("Put Configuration via Restconf: {}", path)
         transaction.putConfigurationData(path, payload);
-        return transaction.commit()
+        return transaction.commit
     }
 
-    def commitOperationalDataPut(InstanceIdentifier path, CompositeNode payload) {
+    def commitConfigurationDataPost(InstanceIdentifier path, CompositeNode payload) {
         checkPreconditions
         val transaction = dataService.beginTransaction;
-        transaction.putOperationalData(path, payload);
-        return transaction.commit()
+        transaction.putConfigurationData(path, payload);
+        if (payload == transaction.createdConfigurationData.get(path)) {
+            LOG.info("Post Configuration via Restconf: {}", path)
+            return transaction.commit
+        }
+        LOG.info("Post Configuration via Restconf was not executed because data already exists: {}", path)
+        return null;
     }
-    
+
+    def commitConfigurationDataDelete(InstanceIdentifier path) {
+        checkPreconditions
+        val transaction = dataService.beginTransaction;
+        transaction.removeConfigurationData(path)
+        return transaction.commit
+    }
+
 }
index 0ded60dae4fc0131ad6eb9da0cef06aee6f2cdff..74a32d452e212682abee966adffad4f062577fcb 100644 (file)
@@ -78,6 +78,11 @@ public final class CompositeNodeWrapper implements NodeWrapper<CompositeNode>, C
         Preconditions.checkState(compositeNode == null, "Data can be inconsistent.");
         return Collections.unmodifiableList(values);
     }
+    
+    @Override
+    public boolean isChangeAllowed() {
+        return compositeNode == null ? true : false;
+    }
 
     @Override
     public CompositeNode unwrap() {
@@ -230,4 +235,5 @@ public final class CompositeNodeWrapper implements NodeWrapper<CompositeNode>, C
     public Set<java.util.Map.Entry<QName, List<Node<?>>>> entrySet() {
         return unwrap().entrySet();
     }
+
 }
index 1a60e14589998adbe8390495fe022d4bd5cc30a6..308975c8c599a595d24f928cd6dbe4f4c76b8b9b 100644 (file)
@@ -12,6 +12,7 @@ import java.util.Map
 import java.util.concurrent.ConcurrentHashMap
 import javax.ws.rs.core.Response
 import org.opendaylight.controller.sal.core.api.model.SchemaServiceListener
+import org.opendaylight.controller.sal.core.api.mount.MountService
 import org.opendaylight.controller.sal.rest.impl.RestUtil
 import org.opendaylight.controller.sal.rest.impl.RestconfProvider
 import org.opendaylight.yangtools.yang.common.QName
@@ -26,16 +27,17 @@ import org.opendaylight.yangtools.yang.model.api.ChoiceNode
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
+import org.opendaylight.yangtools.yang.model.api.Module
 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
 import org.opendaylight.yangtools.yang.model.api.SchemaContext
 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition
 import org.slf4j.LoggerFactory
 
 import static com.google.common.base.Preconditions.*
-import org.opendaylight.controller.sal.core.api.mount.MountService
-import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode
+import java.util.ArrayList
 
 class ControllerContext implements SchemaServiceListener {
     val static LOG = LoggerFactory.getLogger(ControllerContext)
@@ -73,6 +75,7 @@ class ControllerContext implements SchemaServiceListener {
     }
 
     public def InstanceIdWithSchemaNode toInstanceIdentifier(String restconfInstance) {
+        checkPreconditions
         val ret = InstanceIdentifier.builder();
         val pathArgs = restconfInstance.split("/");
         if (pathArgs.empty) {
@@ -81,24 +84,28 @@ class ControllerContext implements SchemaServiceListener {
         if (pathArgs.head.empty) {
             pathArgs.remove(0)
         }
-        val schemaNode = ret.collectPathArguments(pathArgs, globalSchema.findModule(pathArgs.head));
+        val mountPoints = new ArrayList
+        val schemaNode = ret.collectPathArguments(pathArgs, globalSchema.findModule(pathArgs.head), mountPoints);
         if (schemaNode === null) {
             return null
         }
-        return new InstanceIdWithSchemaNode(ret.toInstance, schemaNode)
+        return new InstanceIdWithSchemaNode(ret.toInstance, schemaNode, mountPoints.last)
     }
 
-    private static def findModule(SchemaContext context,String argument) {
-        //checkPreconditions
+    private def findModule(SchemaContext context,String argument) {
         checkNotNull(argument);
         val startModule = argument.toModuleName();
         return context.getLatestModule(startModule)
     }
-
-    static def getLatestModule(SchemaContext schema,String moduleName) {
-        checkArgument(schema != null);
+    
+    private def getLatestModule(SchemaContext schema,String moduleName) {
+        checkArgument(schema !== null);
         checkArgument(moduleName !== null && !moduleName.empty)
         val modules = schema.modules.filter[m|m.name == moduleName]
+        return modules.filterLatestModule
+    }
+    
+    private def filterLatestModule(Iterable<Module> modules) {
         var latestModule = modules.head
         for (module : modules) {
             if (module.revision.after(latestModule.revision)) {
@@ -107,6 +114,31 @@ class ControllerContext implements SchemaServiceListener {
         }
         return latestModule
     }
+    
+    def findModuleByName(String moduleName) {
+        checkPreconditions
+        checkArgument(moduleName !== null && !moduleName.empty)
+        return globalSchema.getLatestModule(moduleName)
+    }
+    
+    def findModuleByName(String moduleName, InstanceIdentifier partialPath) {
+        checkArgument(moduleName !== null && !moduleName.empty && partialPath !== null && !partialPath.path.empty)
+        val mountPointSchema = mountService?.getMountPoint(partialPath)?.schemaContext;
+        return mountPointSchema?.getLatestModule(moduleName);
+    }
+    
+    def findModuleByNamespace(URI namespace) {
+        checkPreconditions
+        val moduleSchemas = globalSchema.findModuleByNamespace(namespace)
+        return moduleSchemas?.filterLatestModule
+    }
+    
+    def findModuleByNamespace(URI namespace, InstanceIdentifier partialPath) {
+        checkArgument(namespace !== null && !namespace.toString.empty && partialPath !== null && !partialPath.path.empty)
+        val mountPointSchema = mountService?.getMountPoint(partialPath)?.schemaContext;
+        val moduleSchemas = mountPointSchema?.findModuleByNamespace(namespace)
+        return moduleSchemas?.filterLatestModule
+    }
 
     def String toFullRestconfIdentifier(InstanceIdentifier path) {
         checkPreconditions
@@ -136,18 +168,13 @@ class ControllerContext implements SchemaServiceListener {
         throw new IllegalArgumentException("Conversion of generic path argument is not supported");
     }
 
-    def findModuleByNamespace(URI namespace) {
+    def findModuleNameByNamespace(URI namespace) {
         checkPreconditions
         var module = uriToModuleName.get(namespace)
         if (module === null) {
             val moduleSchemas = globalSchema.findModuleByNamespace(namespace);
             if(moduleSchemas === null) return null
-            var latestModule = moduleSchemas.head
-            for (m : moduleSchemas) {
-                if (m.revision.after(latestModule.revision)) {
-                    latestModule = m
-                }
-            }
+            var latestModule = moduleSchemas.filterLatestModule
             if(latestModule === null) return null
             uriToModuleName.put(namespace, latestModule.name)
             module = latestModule.name;
@@ -155,16 +182,10 @@ class ControllerContext implements SchemaServiceListener {
         return module
     }
 
-    def findNamespaceByModule(String module) {
+    def findNamespaceByModuleName(String module) {
         var namespace = moduleNameToUri.get(module)
         if (namespace === null) {
-            val moduleSchemas = globalSchema.modules.filter[it|it.name.equals(module)]
-            var latestModule = moduleSchemas.head
-            for (m : moduleSchemas) {
-                if (m.revision.after(latestModule.revision)) {
-                    latestModule = m
-                }
-            }
+            var latestModule = globalSchema.getLatestModule(module)
             if(latestModule === null) return null
             namespace = latestModule.namespace
             uriToModuleName.put(namespace, latestModule.name)
@@ -235,7 +256,7 @@ class ControllerContext implements SchemaServiceListener {
     }
 
     private def DataSchemaNode collectPathArguments(InstanceIdentifierBuilder builder, List<String> strings,
-        DataNodeContainer parentNode) {
+        DataNodeContainer parentNode, List<InstanceIdentifier> mountPoints) {
         checkNotNull(strings)
         if (parentNode === null) {
             return null;
@@ -255,8 +276,12 @@ class ControllerContext implements SchemaServiceListener {
             // Node is possibly in other mount point
             val partialPath = builder.toInstance;
             val mountPointSchema = mountService?.getMountPoint(partialPath)?.schemaContext;
-            if(mountPointSchema != null) {
-                return builder.collectPathArguments(strings, mountPointSchema.findModule(strings.head));
+            if(mountPointSchema !== null) {
+                val module = mountPointSchema.findModule(strings.head)
+                if (module !== null) {
+                    mountPoints.add(partialPath)
+                }
+                return builder.collectPathArguments(strings, module, mountPoints);
             }
             return null
         }
@@ -294,7 +319,7 @@ class ControllerContext implements SchemaServiceListener {
         }
         if (targetNode instanceof DataNodeContainer) {
             val remaining = strings.subList(consumed, strings.length);
-            val result = builder.collectPathArguments(remaining, targetNode as DataNodeContainer);
+            val result = builder.collectPathArguments(remaining, targetNode as DataNodeContainer, mountPoints);
             return result
         }
 
@@ -310,7 +335,7 @@ class ControllerContext implements SchemaServiceListener {
         val allCases = container.childNodes.filter(ChoiceNode).map[cases].flatten
         for (caze : allCases) {
             potentialNode = caze.findInstanceDataChild(name);
-            if(potentialNode != null) {
+            if(potentialNode !== null) {
                 return potentialNode;
             }
         }
index c146954dcedad184d51181db8f4a4da395b6498c..cdb9599a4674e3691815eb6c3ad2895caddb029d 100644 (file)
@@ -64,6 +64,11 @@ public final class EmptyNodeWrapper implements NodeWrapper<Node<?>>, Node<Void>
         this.namespace = namespace;
     }
 
+    @Override
+    public boolean isChangeAllowed() {
+        return unwrapped == null ? true : false;
+    }
+
     @Override
     public Node<?> unwrap() {
         if (unwrapped == null) {
index ad0654af785bd13ac690409723ac45d0e55f22e9..ba0e47ff928c5d68739556cc05e0aee732199d28 100644 (file)
@@ -7,10 +7,12 @@ public class InstanceIdWithSchemaNode {
 
     private final InstanceIdentifier instanceIdentifier;
     private final DataSchemaNode schemaNode;
+    private final InstanceIdentifier mountPoint;
 
-    public InstanceIdWithSchemaNode(InstanceIdentifier instanceIdentifier, DataSchemaNode schemaNode) {
+    public InstanceIdWithSchemaNode(InstanceIdentifier instanceIdentifier, DataSchemaNode schemaNode, InstanceIdentifier mountPoint) {
         this.instanceIdentifier = instanceIdentifier;
         this.schemaNode = schemaNode;
+        this.mountPoint = mountPoint;
     }
 
     public InstanceIdentifier getInstanceIdentifier() {
@@ -21,4 +23,8 @@ public class InstanceIdWithSchemaNode {
         return schemaNode;
     }
 
+    public InstanceIdentifier getMountPoint() {
+        return mountPoint;
+    }
+
 }
index 675e1194396147334509e35d5a6b4ebb21e40b35..6b8665f7653c26e8881c685aa0e507231a0d8db6 100644 (file)
@@ -11,6 +11,8 @@ public interface NodeWrapper<T extends Node<?>> {
     
     T unwrap();
     
+    boolean isChangeAllowed();
+    
     URI getNamespace();
 
     void setNamespace(URI namespace);
index 40fba88356e89f924e6c2903e7460102a74d818c..45f3f7f30bd3de9b29117e27b50767ae7e5c5166 100644 (file)
@@ -107,7 +107,7 @@ public class RestCodec {
         public QName deserialize(IdentityValuesDTO data) {
             IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
             String namespace = valueWithNamespace.getNamespace();
-            URI validNamespace = ControllerContext.getInstance().findNamespaceByModule(namespace);
+            URI validNamespace = ControllerContext.getInstance().findNamespaceByModuleName(namespace);
             if (validNamespace == null) {
                 validNamespace = URI.create(namespace);
             }
index 4645a411c17c88baa7d55d94c21fcb944faf4d2f..a65c0ff97aa73324083a0826186462d59c7006c6 100644 (file)
@@ -1,6 +1,7 @@
 package org.opendaylight.controller.sal.restconf.impl
 
 import java.util.ArrayList
+import java.util.HashMap
 import java.util.List
 import java.util.Set
 import javax.ws.rs.core.Response
@@ -8,6 +9,8 @@ import org.opendaylight.controller.md.sal.common.api.TransactionStatus
 import org.opendaylight.controller.sal.rest.api.RestconfService
 import org.opendaylight.yangtools.yang.common.QName
 import org.opendaylight.yangtools.yang.data.api.CompositeNode
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder
 import org.opendaylight.yangtools.yang.data.api.Node
 import org.opendaylight.yangtools.yang.data.impl.NodeFactory
 import org.opendaylight.yangtools.yang.model.api.ChoiceNode
@@ -17,10 +20,12 @@ import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
+import org.opendaylight.yangtools.yang.model.api.Module
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition
 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition
 
 import static javax.ws.rs.core.Response.Status.*
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition
 
 class RestconfImpl implements RestconfService {
 
@@ -55,41 +60,27 @@ class RestconfImpl implements RestconfService {
         return null;
     }
 
-    override readData(String identifier) {
-        val instanceIdentifierWithSchemaNode = identifier.resolveInstanceIdentifier
-        val data = broker.readOperationalData(instanceIdentifierWithSchemaNode.getInstanceIdentifier);
-        return new StructuredData(data, instanceIdentifierWithSchemaNode.schemaNode)
-    }
-
-    override createConfigurationData(String identifier, CompositeNode payload) {
-        val identifierWithSchemaNode = identifier.resolveInstanceIdentifier
-        val value = normalizeNode(payload, identifierWithSchemaNode.schemaNode)
-        val status = broker.commitConfigurationDataPut(identifierWithSchemaNode.instanceIdentifier, value).get();
-        switch status.result {
-            case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
-            default: Response.status(INTERNAL_SERVER_ERROR).build
-        }
+    override invokeRpc(String identifier, CompositeNode payload) {
+        return callRpc(identifier.rpcDefinition, payload)
     }
-
-    override updateConfigurationData(String identifier, CompositeNode payload) {
-        val identifierWithSchemaNode = identifier.resolveInstanceIdentifier
-        val value = normalizeNode(payload, identifierWithSchemaNode.schemaNode)
-        val status = broker.commitConfigurationDataPut(identifierWithSchemaNode.instanceIdentifier, value).get();
-        switch status.result {
-            case TransactionStatus.COMMITED: Response.status(OK).build
-            default: Response.status(INTERNAL_SERVER_ERROR).build
-        }
+    
+    override invokeRpc(String identifier) {
+        return callRpc(identifier.rpcDefinition, null)
     }
-
-    override invokeRpc(String identifier, CompositeNode payload) {
-        val rpc = identifier.rpcDefinition
+    
+    private def StructuredData callRpc(RpcDefinition rpc, CompositeNode payload) {
         if (rpc === null) {
             throw new ResponseException(NOT_FOUND, "RPC does not exist.");
         }
-        val value = normalizeNode(payload, rpc.input)
-        val List<Node<?>> input = new ArrayList
-        input.add(value)
-        val rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, input, null, null)
+        var CompositeNode rpcRequest;
+        if (payload === null) {
+            rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, null, null, null)
+        } else {
+            val value = normalizeNode(payload, rpc.input, null)
+            val List<Node<?>> input = new ArrayList
+            input.add(value)
+            rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, input, null, null)
+        }
         val rpcResult = broker.invokeRpc(rpc.QName, rpcRequest);
         if (!rpcResult.successful) {
             throw new ResponseException(INTERNAL_SERVER_ERROR, "Operation failed")
@@ -100,6 +91,12 @@ class RestconfImpl implements RestconfService {
         return new StructuredData(rpcResult.result, rpc.output)
     }
 
+    override readData(String identifier) {
+        val instanceIdentifierWithSchemaNode = identifier.resolveInstanceIdentifier
+        val data = broker.readOperationalData(instanceIdentifierWithSchemaNode.getInstanceIdentifier);
+        return new StructuredData(data, instanceIdentifierWithSchemaNode.schemaNode)
+    }
+
     override readConfigurationData(String identifier) {
         val instanceIdentifierWithSchemaNode = identifier.resolveInstanceIdentifier
         val data = broker.readConfigurationData(instanceIdentifierWithSchemaNode.getInstanceIdentifier);
@@ -116,10 +113,61 @@ class RestconfImpl implements RestconfService {
         updateConfigurationData(identifier, payload);
     }
 
+    override updateConfigurationData(String identifier, CompositeNode payload) {
+        val identifierWithSchemaNode = identifier.resolveInstanceIdentifier
+        val value = normalizeNode(payload, identifierWithSchemaNode.schemaNode, identifierWithSchemaNode.mountPoint)
+        val status = broker.commitConfigurationDataPut(identifierWithSchemaNode.instanceIdentifier, value).get();
+        switch status.result {
+            case TransactionStatus.COMMITED: Response.status(OK).build
+            default: Response.status(INTERNAL_SERVER_ERROR).build
+        }
+    }
+
     override createConfigurationDataLegacy(String identifier, CompositeNode payload) {
         createConfigurationData(identifier, payload);
     }
 
+    override createConfigurationData(String identifier, CompositeNode payload) {
+        val uncompleteIdentifierWithSchemaNode = identifier.resolveInstanceIdentifier
+        var schemaNode = (uncompleteIdentifierWithSchemaNode.schemaNode as DataNodeContainer).getSchemaChildNode(payload)
+        if (schemaNode === null) {
+            schemaNode = payload.findModule(uncompleteIdentifierWithSchemaNode.instanceIdentifier)?.getSchemaChildNode(payload)
+        }
+        val value = normalizeNode(payload, schemaNode, uncompleteIdentifierWithSchemaNode.instanceIdentifier)
+        val completeIdentifierWithSchemaNode = uncompleteIdentifierWithSchemaNode.addLastIdentifierFromData(value, schemaNode)
+        val status = broker.commitConfigurationDataPost(completeIdentifierWithSchemaNode.instanceIdentifier, value)?.get();
+        if (status === null) {
+            return Response.status(ACCEPTED).build
+        }
+        switch status.result {
+            case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
+            default: Response.status(INTERNAL_SERVER_ERROR).build
+        }
+    }
+    
+    override createConfigurationData(CompositeNode payload) {
+        val schemaNode = payload.findModule(null)?.getSchemaChildNode(payload)
+        val value = normalizeNode(payload, schemaNode, null)
+        val identifierWithSchemaNode = addLastIdentifierFromData(null, value, schemaNode)
+        val status = broker.commitConfigurationDataPost(identifierWithSchemaNode.instanceIdentifier, value)?.get();
+        if (status === null) {
+            return Response.status(ACCEPTED).build
+        }
+        switch status.result {
+            case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
+            default: Response.status(INTERNAL_SERVER_ERROR).build
+        }
+    }
+    
+    override deleteConfigurationData(String identifier) {
+        val instanceIdentifierWithSchemaNode = identifier.resolveInstanceIdentifier
+        val status = broker.commitConfigurationDataDelete(instanceIdentifierWithSchemaNode.getInstanceIdentifier).get;
+        switch status.result {
+            case TransactionStatus.COMMITED: Response.status(OK).build
+            default: Response.status(INTERNAL_SERVER_ERROR).build
+        }
+    }
+
     private def InstanceIdWithSchemaNode resolveInstanceIdentifier(String identifier) {
         val identifierWithSchemaNode = identifier.toInstanceIdentifier
         if (identifierWithSchemaNode === null) {
@@ -128,18 +176,89 @@ class RestconfImpl implements RestconfService {
         return identifierWithSchemaNode
     }
 
-    private def CompositeNode normalizeNode(CompositeNode node, DataSchemaNode schema) {
+    private def dispatch Module findModule(CompositeNode data, InstanceIdentifier partialPath) {
+        if (partialPath !== null && !partialPath.path.empty) {
+            return data.nodeType.namespace.findModuleByNamespace(partialPath)
+        } else {
+            return data.nodeType.namespace.findModuleByNamespace
+        }
+    }
+
+    private def dispatch Module findModule(CompositeNodeWrapper data, InstanceIdentifier partialPath) {
+        var Module module = null;
+        if (partialPath !== null && !partialPath.path.empty) {
+            module = data.namespace.findModuleByNamespace(partialPath) // namespace from XML
+            if (module === null) {
+                module = data.namespace.toString.findModuleByName(partialPath) // namespace (module name) from JSON
+            }
+        } else {
+            module = data.namespace.findModuleByNamespace // namespace from XML
+            if (module === null) {
+                module = data.namespace.toString.findModuleByName // namespace (module name) from JSON
+            }
+        }
+        return module
+    }
+    
+    private def dispatch DataSchemaNode getSchemaChildNode(DataNodeContainer parentSchemaNode, CompositeNode data) {
+        return parentSchemaNode?.getDataChildByName(data.nodeType.localName)
+    }
+    
+    private def dispatch DataSchemaNode getSchemaChildNode(DataNodeContainer parentSchemaNode, CompositeNodeWrapper data) {
+        return parentSchemaNode?.getDataChildByName(data.localName)
+    }
+
+    private def InstanceIdWithSchemaNode addLastIdentifierFromData(InstanceIdWithSchemaNode identifierWithSchemaNode, CompositeNode data, DataSchemaNode schemaOfData) {
+        val iiOriginal = identifierWithSchemaNode?.instanceIdentifier
+        var  InstanceIdentifierBuilder iiBuilder = null
+        if (iiOriginal === null) { 
+            iiBuilder = InstanceIdentifier.builder
+        } else {
+            iiBuilder = InstanceIdentifier.builder(iiOriginal)
+        }
+
+        if (schemaOfData instanceof ListSchemaNode) {
+            iiBuilder.nodeWithKey(schemaOfData.QName, (schemaOfData as ListSchemaNode).resolveKeysFromData(data))
+        } else {
+            iiBuilder.node(schemaOfData.QName)
+        }
+        return new InstanceIdWithSchemaNode(iiBuilder.toInstance, schemaOfData, identifierWithSchemaNode?.mountPoint)
+    }
+
+    private def resolveKeysFromData(ListSchemaNode listNode, CompositeNode dataNode) {
+        val keyValues = new HashMap<QName, Object>();
+        for (key : listNode.keyDefinition) {
+            val dataNodeKeyValueObject = dataNode.getSimpleNodesByName(key.localName)?.head?.value
+            if (dataNodeKeyValueObject === null) {
+                throw new ResponseException(BAD_REQUEST, "Data contains list \"" + dataNode.nodeType.localName + "\" which does not contain key: \"" + key.localName + "\"")
+            }
+            keyValues.put(key, dataNodeKeyValueObject);
+        }
+        return keyValues
+    }
+
+    private def CompositeNode normalizeNode(CompositeNode node, DataSchemaNode schema, InstanceIdentifier mountPoint) {
+        if (schema !== null && !schema.containerOrList) {
+            throw new ResponseException(BAD_REQUEST, "Root element has to be container or list yang datatype.");
+        }
         if (node instanceof CompositeNodeWrapper) {
-            normalizeNode(node as CompositeNodeWrapper, schema, null)
+            if ((node  as CompositeNodeWrapper).changeAllowed) {
+                normalizeNode(node as CompositeNodeWrapper, schema, null, mountPoint)
+            }
             return (node as CompositeNodeWrapper).unwrap()
         }
         return node
     }
 
-    private def void normalizeNode(NodeWrapper<?> nodeBuilder, DataSchemaNode schema, QName previousAugment) {
+    private def isContainerOrList(DataSchemaNode schemaNode) {
+        return (schemaNode instanceof ContainerSchemaNode) || (schemaNode instanceof ListSchemaNode)
+    }
+
+    private def void normalizeNode(NodeWrapper<?> nodeBuilder, DataSchemaNode schema, QName previousAugment,
+        InstanceIdentifier mountPoint) {
         if (schema === null) {
             throw new ResponseException(BAD_REQUEST,
-                "Data has bad format\n" + nodeBuilder.localName + " does not exist in yang schema.");
+                "Data has bad format.\n\"" + nodeBuilder.localName + "\" does not exist in yang schema.");
         }
         var validQName = schema.QName
         var currentAugment = previousAugment;
@@ -148,15 +267,18 @@ class RestconfImpl implements RestconfService {
         } else if (previousAugment !== null && schema.QName.namespace !== previousAugment.namespace) {
             validQName = QName.create(currentAugment, schema.QName.localName);
         }
-        val moduleName = controllerContext.findModuleByNamespace(validQName.namespace);
+        var moduleName = controllerContext.findModuleNameByNamespace(validQName.namespace);
+        if (moduleName === null && mountPoint !== null && !mountPoint.path.empty) {
+            moduleName = controllerContext.findModuleByNamespace(validQName.namespace, mountPoint)?.name
+        }
         if (nodeBuilder.namespace === null || nodeBuilder.namespace == validQName.namespace ||
             nodeBuilder.namespace.toString == moduleName) {
             nodeBuilder.qname = validQName
         } else {
             throw new ResponseException(BAD_REQUEST,
-                "Data has bad format\nIf data is in XML format then namespace for " + nodeBuilder.localName +
-                    " should be " + schema.QName.namespace + ".\n If data is in Json format then module name for " +
-                    nodeBuilder.localName + " should be " + moduleName + ".");
+                "Data has bad format.\nIf data is in XML format then namespace for \"" + nodeBuilder.localName +
+                    "\" should be \"" + schema.QName.namespace + "\".\nIf data is in Json format then module name for \"" +
+                    nodeBuilder.localName + "\" should be \"" + moduleName + "\".");
         }
 
         if (nodeBuilder instanceof CompositeNodeWrapper) {
@@ -164,7 +286,7 @@ class RestconfImpl implements RestconfService {
             for (child : children) {
                 normalizeNode(child,
                     findFirstSchemaByLocalName(child.localName, (schema as DataNodeContainer).childNodes),
-                    currentAugment)
+                    currentAugment, mountPoint)
             }
             if(schema instanceof ListSchemaNode) {
                 val listKeys = (schema as ListSchemaNode).keyDefinition
@@ -177,7 +299,7 @@ class RestconfImpl implements RestconfService {
                     }
                     if (!foundKey) {
                         throw new ResponseException(BAD_REQUEST,
-                            "Missing key \"" + listKey.localName + "\" of list \"" + schema.QName.localName + "\"")
+                            "Missing key in URI \"" + listKey.localName + "\" of list \"" + schema.QName.localName + "\"")
                     }
                 }
             }
index 97f8102127c2b71f69aaf729aeeeed598043aed0..6aa8ada5ee3135fa0ac7025d9efe0564205f78d8 100644 (file)
@@ -58,6 +58,11 @@ public final class SimpleNodeWrapper implements NodeWrapper<SimpleNode<?>>, Simp
         this.namespace = namespace;
     }
 
+    @Override
+    public boolean isChangeAllowed() {
+        return simpleNode == null ? true : false;
+    }
+
     @Override
     public SimpleNode<Object> unwrap() {
         if (simpleNode == null) {
index f2c0c29bc4372b6b54eeec0dc3bf9d9805f36663..afe458d6e0917e25903d4f655cf7b3a49640f8b5 100644 (file)
@@ -13,7 +13,6 @@ import javax.ws.rs.WebApplicationException;
 import org.junit.Test;
 import org.opendaylight.controller.sal.rest.impl.JsonToCompositeNodeProvider;
 import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper;
-import org.opendaylight.controller.sal.restconf.impl.ResponseException;
 import org.opendaylight.controller.sal.restconf.impl.test.TestUtils;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
@@ -207,15 +206,8 @@ public class JsonToCnSnTest {
         assertEquals("lst", compNode.getNodeType().getLocalName());
         verifyCompositeNode(compNode, "simple:list:yang1");
 
-        String exceptionMessage = "";
-        try {
-            TestUtils.normalizeCompositeNode(compositeNode, modules2, "simple-list-yang2:lst");
-        } catch (ResponseException e) {
-            exceptionMessage = String.valueOf(e.getResponse().getEntity());
-        }
-        assertTrue(exceptionMessage
-                .contains("Data has bad format\nIf data is in XML format then namespace for lst should be simple:list:yang2.\n If data is in Json format then module name for lst should be simple-list-yang2."));
-
+        TestUtils.normalizeCompositeNode(compositeNode, modules2, "simple-list-yang2:lst");
+        verifyCompositeNode(compNode, "simple:list:yang1");
     }
 
     @Test
index 2370035861759584655b0f104516c231f3a6419c..c5f5a1eddc68d112858376d6c446192ac59394ab 100644 (file)
@@ -60,7 +60,7 @@ public class InvokeRpcMethodTest {
         ControllerContext contContext = ControllerContext.getInstance();
         contContext.onGlobalContextUpdated(TestUtils.loadSchemaContext(modules));
         try {
-            contContext.findModuleByNamespace(new URI("invoke:rpc:module"));
+            contContext.findModuleNameByNamespace(new URI("invoke:rpc:module"));
         } catch (URISyntaxException e) {
             assertTrue("Uri wasn't created sucessfuly", false);
         }
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/NormalizeNodeTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/NormalizeNodeTest.java
new file mode 100644 (file)
index 0000000..83e6ae5
--- /dev/null
@@ -0,0 +1,83 @@
+package org.opendaylight.controller.sal.restconf.impl.test;
+
+import static org.junit.Assert.*;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper;
+import org.opendaylight.controller.sal.restconf.impl.ResponseException;
+import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+
+public class NormalizeNodeTest extends YangAndXmlAndDataSchemaLoader {
+
+    @BeforeClass
+    public static void initialization() {
+        dataLoad("/normalize-node/yang/");
+    }
+
+    @Test
+    public void namespaceNotNullAndInvalidNamespaceAndNoModuleNameTest() {
+        boolean exceptionReised = false;
+        try {
+            TestUtils.normalizeCompositeNode(prepareCnSn("wrongnamespace"), modules, schemaNodePath);
+        } catch (ResponseException e) {
+            exceptionReised = true;
+        }
+        assertTrue(exceptionReised);
+    }
+
+    @Test
+    public void namespaceNullTest() {
+        String exceptionMessage = null;
+        try {
+            TestUtils.normalizeCompositeNode(prepareCnSn(null), modules, schemaNodePath);
+        } catch (ResponseException e) {
+            exceptionMessage = String.valueOf(e.getResponse().getEntity());
+        }
+        assertNull(exceptionMessage);
+    }
+
+    @Test
+    public void namespaceValidNamespaceTest() {
+        String exceptionMessage = null;
+        try {
+            TestUtils.normalizeCompositeNode(prepareCnSn("normalize:node:module"), modules, schemaNodePath);
+        } catch (ResponseException e) {
+            exceptionMessage = String.valueOf(e.getResponse().getEntity());
+        }
+        assertNull(exceptionMessage);
+    }
+
+    @Test
+    public void namespaceValidModuleNameTest() {
+        String exceptionMessage = null;
+        try {
+            TestUtils.normalizeCompositeNode(prepareCnSn("normalize-node-module"), modules, schemaNodePath);
+        } catch (ResponseException e) {
+            exceptionMessage = String.valueOf(e.getResponse().getEntity());
+        }
+        assertNull(exceptionMessage);
+    }
+
+    private CompositeNode prepareCnSn(String namespace) {
+        URI uri = null;
+        if (namespace != null) {
+            try {
+                uri = new URI(namespace);
+            } catch (URISyntaxException e) {
+            }
+            assertNotNull(uri);
+        }
+
+        SimpleNodeWrapper lf1 = new SimpleNodeWrapper(uri, "lf1", 43);
+        CompositeNodeWrapper cont = new CompositeNodeWrapper(uri, "cont");
+        cont.addValue(lf1);
+
+        return cont;
+    }
+
+}
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestConfigDataTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestConfigDataTest.java
new file mode 100644 (file)
index 0000000..dccf0d3
--- /dev/null
@@ -0,0 +1,210 @@
+package org.opendaylight.controller.sal.restconf.impl.test;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.times;
+import static org.junit.Assert.assertEquals;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.text.ParseException;
+import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.logging.Level;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
+import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider;
+import org.opendaylight.controller.sal.rest.impl.XmlMapper;
+import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider;
+import org.opendaylight.controller.sal.restconf.impl.BrokerFacade;
+import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
+import org.opendaylight.controller.sal.restconf.impl.RestconfImpl;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.controller.sal.core.api.mount.MountInstance;
+import org.opendaylight.controller.sal.core.api.mount.MountService;
+
+import com.google.common.base.Charsets;
+
+public class RestConfigDataTest extends JerseyTest {
+
+    private static ControllerContext controllerContext;
+    private static BrokerFacade brokerFacade;
+    private static RestconfImpl restconfImpl;
+    private static MountService mountService;
+    private static SchemaContext schemaContext;
+
+    private static final MediaType MEDIA_TYPE_XML_DRAFT02 = new MediaType("application", "yang.data+xml");
+
+    @BeforeClass
+    public static void init() throws FileNotFoundException {
+        Set<Module> modules = TestUtils.loadModulesFrom("/test-config-data/yang1");
+        schemaContext = TestUtils.loadSchemaContext(modules);
+        initMocking();
+    }
+    
+    private static void initMocking() {
+        controllerContext = ControllerContext.getInstance();
+        controllerContext.setSchemas(schemaContext);
+        mountService = mock(MountService.class);
+        controllerContext.setMountService(mountService);
+        brokerFacade = mock(BrokerFacade.class);
+        restconfImpl = RestconfImpl.getInstance();
+        restconfImpl.setBroker(brokerFacade);
+        restconfImpl.setControllerContext(controllerContext);
+    }
+
+    @Test
+    public void createConfigurationDataTest() throws UnsupportedEncodingException, ParseException {
+        initMocking();
+        String URI_1 = createUri("/config", "");
+        String URI_2 = createUri("/config/", "");
+        String URI_3 = createUri("/config/", "test-interface:interfaces/");
+        String URI_4 = createUri("/config/", "test-interface:interfaces/");
+        String URI_5 = createUri("/config/", "test-interface:interfaces/test-interface2:class");
+
+        RpcResult<TransactionStatus> rpcResult = new DummyRpcResult.Builder<TransactionStatus>().result(
+                TransactionStatus.COMMITED).build();
+        Future<RpcResult<TransactionStatus>> dummyFuture = DummyFuture.builder().rpcResult(rpcResult).build();
+
+        when(brokerFacade.commitConfigurationDataPost(any(InstanceIdentifier.class), any(CompositeNode.class)))
+                .thenReturn(dummyFuture);
+
+        ArgumentCaptor<InstanceIdentifier> instanceIdCaptor = ArgumentCaptor.forClass(InstanceIdentifier.class);
+        ArgumentCaptor<CompositeNode> compNodeCaptor = ArgumentCaptor.forClass(CompositeNode.class);
+
+        // Test URI_1
+        Entity<String> entity = createEntity("/test-config-data/xml/test-interface.xml");
+        Response response = target(URI_1).request(MEDIA_TYPE_XML_DRAFT02).post(entity);
+        assertEquals(204, response.getStatus());
+        verify(brokerFacade).commitConfigurationDataPost(instanceIdCaptor.capture(), compNodeCaptor.capture());
+        String identifier = "[(urn:ietf:params:xml:ns:yang:test-interface?revision=2014-07-01)interfaces]";
+        assertEquals("Bad format URI", identifier, instanceIdCaptor.getValue().getPath().toString());
+
+        // Test URI_2
+        response = target(URI_2).request(MEDIA_TYPE_XML_DRAFT02).post(entity);
+        assertEquals(204, response.getStatus());
+        verify(brokerFacade, times(2))
+                .commitConfigurationDataPost(instanceIdCaptor.capture(), compNodeCaptor.capture());
+        assertEquals("Bad format URI", identifier, instanceIdCaptor.getValue().getPath().toString());
+
+        // Test URI_3
+        entity = createEntity("/test-config-data/xml/test-interface2.xml");
+        response = target(URI_3).request(MEDIA_TYPE_XML_DRAFT02).post(entity);
+        assertEquals(204, response.getStatus());
+        verify(brokerFacade, times(3))
+                .commitConfigurationDataPost(instanceIdCaptor.capture(), compNodeCaptor.capture());
+
+        identifier = "[(urn:ietf:params:xml:ns:yang:test-interface?revision=2014-07-01)interfaces, (urn:ietf:params:xml:ns:yang:test-interface?revision=2014-07-01)interface[{(urn:ietf:params:xml:ns:yang:test-interface?revision=2014-07-01)name=eth0}]]";
+        assertEquals("Bad format URI", identifier, instanceIdCaptor.getValue().getPath().toString());
+
+        // Test URI_4
+        Set<Module> modules2 = TestUtils.loadModulesFrom("/test-config-data/yang2");
+        SchemaContext schemaContext2 = TestUtils.loadSchemaContext(modules2);
+        MountInstance mountInstance = mock(MountInstance.class);
+        when(mountInstance.getSchemaContext()).thenReturn(schemaContext2);
+        when(mountService.getMountPoint(any(InstanceIdentifier.class))).thenReturn(mountInstance);
+
+        entity = createEntity("/test-config-data/xml/test-interface3.xml");
+        response = target(URI_4).request(MEDIA_TYPE_XML_DRAFT02).post(entity);
+        assertEquals(204, response.getStatus());
+        verify(brokerFacade, times(4))
+                .commitConfigurationDataPost(instanceIdCaptor.capture(), compNodeCaptor.capture());
+        identifier = "[(urn:ietf:params:xml:ns:yang:test-interface?revision=2014-07-01)interfaces, (urn:ietf:params:xml:ns:yang:test-interface2?revision=2014-08-01)class]";
+        assertEquals("Bad format URI", identifier, instanceIdCaptor.getValue().getPath().toString());
+
+        // Test URI_5
+        response = target(URI_5).request(MEDIA_TYPE_XML_DRAFT02).post(entity);
+        assertEquals(204, response.getStatus());
+        verify(brokerFacade, times(5))
+                .commitConfigurationDataPost(instanceIdCaptor.capture(), compNodeCaptor.capture());
+        identifier = "[(urn:ietf:params:xml:ns:yang:test-interface?revision=2014-07-01)interfaces, (urn:ietf:params:xml:ns:yang:test-interface2?revision=2014-08-01)class, (urn:ietf:params:xml:ns:yang:test-interface2?revision=2014-08-01)class]";
+        assertEquals("Bad format URI", identifier, instanceIdCaptor.getValue().getPath().toString());
+    }
+    
+    @Test
+    public void testExistingData() throws UnsupportedEncodingException {
+        initMocking();
+        String URI_1 = createUri("/config", "");
+        String URI_2 = createUri("/config/", "");
+        String URI_3 = createUri("/config/", "test-interface:interfaces/");
+        String URI_4 = createUri("/config/", "test-interface:interfaces/");
+        String URI_5 = createUri("/config/", "test-interface:interfaces/test-interface2:class");
+
+        when(brokerFacade.commitConfigurationDataPost(any(InstanceIdentifier.class), any(CompositeNode.class)))
+                .thenReturn(null);
+
+        // Test URI_1
+        Entity<String> entity = createEntity("/test-config-data/xml/test-interface.xml");
+        Response response = target(URI_1).request(MEDIA_TYPE_XML_DRAFT02).post(entity);
+        assertEquals(202, response.getStatus());
+
+        // Test URI_2
+        response = target(URI_2).request(MEDIA_TYPE_XML_DRAFT02).post(entity);
+        assertEquals(202, response.getStatus());
+
+        // Test URI_3
+        entity = createEntity("/test-config-data/xml/test-interface2.xml");
+        response = target(URI_3).request(MEDIA_TYPE_XML_DRAFT02).post(entity);
+        assertEquals(202, response.getStatus());
+
+        // Test URI_4
+        Set<Module> modules2 = TestUtils.loadModulesFrom("/test-config-data/yang2");
+        SchemaContext schemaContext2 = TestUtils.loadSchemaContext(modules2);
+        MountInstance mountInstance = mock(MountInstance.class);
+        when(mountInstance.getSchemaContext()).thenReturn(schemaContext2);
+        when(mountService.getMountPoint(any(InstanceIdentifier.class))).thenReturn(mountInstance);
+
+        entity = createEntity("/test-config-data/xml/test-interface3.xml");
+        response = target(URI_4).request(MEDIA_TYPE_XML_DRAFT02).post(entity);
+        assertEquals(202, response.getStatus());
+
+        // Test URI_5
+        response = target(URI_5).request(MEDIA_TYPE_XML_DRAFT02).post(entity);
+        assertEquals(202, response.getStatus());
+    }
+
+    private String createUri(String prefix, String encodedPart) throws UnsupportedEncodingException {
+        return URI.create(prefix + URLEncoder.encode(encodedPart, Charsets.US_ASCII.name()).toString()).toASCIIString();
+    }
+
+    private Entity<String> createEntity(final String relativePathToXml) {
+        InputStream inputStream = XmlMapper.class.getResourceAsStream(relativePathToXml);
+        String xml = TestUtils.getDocumentInPrintableForm(TestUtils.loadDocumentFrom(inputStream));
+        Entity<String> entity = Entity.entity(xml, MEDIA_TYPE_XML_DRAFT02);
+
+        return entity;
+    }
+
+    @Override
+    protected Application configure() {
+        enable(TestProperties.LOG_TRAFFIC);
+        enable(TestProperties.DUMP_ENTITY);
+        enable(TestProperties.RECORD_LOG_LEVEL);
+        set(TestProperties.RECORD_LOG_LEVEL, Level.ALL.intValue());
+
+        ResourceConfig resourceConfig = new ResourceConfig();
+        resourceConfig = resourceConfig.registerInstances(restconfImpl, StructuredDataToXmlProvider.INSTANCE,
+                XmlToCompositeNodeProvider.INSTANCE);
+        return resourceConfig;
+    }
+}
index 4295c29a22cea2f538d0006d94c70957163fabf9..3427fbde38f1a7682a838e5fc80c01ecc8df6752 100644 (file)
@@ -149,7 +149,7 @@ public final class TestUtils {
         ControllerContext.getInstance().setSchemas(TestUtils.loadSchemaContext(modules));
 
         prepareMocksForRestconf(modules, restconf);
-        restconf.createConfigurationData(schemaNodePath, compositeNode);
+        restconf.updateConfigurationData(schemaNodePath, compositeNode);
     }
 
     /**
index b18f526a237c4c841758fcace2c4903bd28a68ae..7cce34ffb69dc8954da2e87535585347502eeaca 100644 (file)
@@ -12,7 +12,9 @@ import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URLEncoder;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
 import java.util.concurrent.Future;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
@@ -33,9 +35,15 @@ import org.opendaylight.controller.sal.rest.api.Draft01;
 import org.opendaylight.controller.sal.rest.api.RestconfService;
 import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider;
 import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider;
-import org.opendaylight.controller.sal.restconf.impl.*;
+import org.opendaylight.controller.sal.restconf.impl.BrokerFacade;
+import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper;
+import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
+import org.opendaylight.controller.sal.restconf.impl.RestconfImpl;
+import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper;
 import org.opendaylight.yangtools.yang.common.RpcResult;
-import org.opendaylight.yangtools.yang.data.api.*;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.Node;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
@@ -140,26 +148,34 @@ public class XmlProvidersTest extends JerseyTest {
         RpcResult<TransactionStatus> rpcResult = new DummyRpcResult.Builder<TransactionStatus>().result(
                 TransactionStatus.COMMITED).build();
         Future<RpcResult<TransactionStatus>> dummyFuture = DummyFuture.builder().rpcResult(rpcResult).build();
-        when(brokerFacade.commitOperationalDataPut(any(InstanceIdentifier.class), any(CompositeNode.class)))
-                .thenReturn(dummyFuture);
         when(brokerFacade.commitConfigurationDataPut(any(InstanceIdentifier.class), any(CompositeNode.class)))
                 .thenReturn(dummyFuture);
+        when(brokerFacade.commitConfigurationDataPost(any(InstanceIdentifier.class), any(CompositeNode.class)))
+                .thenReturn(dummyFuture);
 
         String uri = createUri("/config/", "ietf-interfaces:interfaces/interface/eth0");
         Response response = target(uri).request(MEDIA_TYPE_DRAFT02).put(entity);
         assertEquals(200, response.getStatus());
+
+        uri = createUri("/config/", "ietf-interfaces:interfaces");
         response = target(uri).request(MEDIA_TYPE_DRAFT02).post(entity);
         assertEquals(204, response.getStatus());
 
         uri = createUri("/config/", "ietf-interfaces:interfaces/interface/eth0");
         response = target(uri).request(MEDIA_TYPE_DRAFT02).put(entity);
         assertEquals(200, response.getStatus());
+
+        uri = createUri("/config/", "ietf-interfaces:interfaces");
         response = target(uri).request(MEDIA_TYPE_DRAFT02).post(entity);
         assertEquals(204, response.getStatus());
 
         uri = createUri("/datastore/", "ietf-interfaces:interfaces/interface/eth0");
+        entity = Entity.entity(xml, MEDIA_TYPE);
         response = target(uri).request(MEDIA_TYPE).put(entity);
         assertEquals(200, response.getStatus());
+
+        uri = createUri("/datastore/", "ietf-interfaces:interfaces");
+        entity = Entity.entity(xml, MEDIA_TYPE);
         response = target(uri).request(MEDIA_TYPE).post(entity);
         assertEquals(204, response.getStatus());
     }
@@ -172,8 +188,6 @@ public class XmlProvidersTest extends JerseyTest {
         RpcResult<TransactionStatus> rpcResult = new DummyRpcResult.Builder<TransactionStatus>().result(
                 TransactionStatus.FAILED).build();
         Future<RpcResult<TransactionStatus>> dummyFuture = DummyFuture.builder().rpcResult(rpcResult).build();
-        when(brokerFacade.commitOperationalDataPut(any(InstanceIdentifier.class), any(CompositeNode.class)))
-                .thenReturn(dummyFuture);
         when(brokerFacade.commitConfigurationDataPut(any(InstanceIdentifier.class), any(CompositeNode.class)))
                 .thenReturn(dummyFuture);
 
@@ -190,9 +204,10 @@ public class XmlProvidersTest extends JerseyTest {
         assertEquals(500, response.getStatus());
 
         uri = createUri("/datastore/", "ietf-interfaces:interfaces/interface/eth0");
-        response = target(uri).request(MEDIA_TYPE).put(entity);
+        entity = Entity.entity(xml, MEDIA_TYPE);
+        response = target(uri).request().put(entity);
         assertEquals(500, response.getStatus());
-        response = target(uri).request(MEDIA_TYPE).post(entity);
+        response = target(uri).request().accept(MEDIA_TYPE).post(entity);
         assertEquals(500, response.getStatus());
     }
 
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/normalize-node/yang/normalize-node-module b/opendaylight/md-sal/sal-rest-connector/src/test/resources/normalize-node/yang/normalize-node-module
new file mode 100644 (file)
index 0000000..15e68ef
--- /dev/null
@@ -0,0 +1,14 @@
+module normalize-node-module {
+  namespace "normalize:node:module";  
+
+  prefix "nonomo";
+  revision 2014-01-09 {    
+  }
+
+    container cont {
+        leaf lf1 {
+            type int32;
+        }
+    }
+  
+}
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface.xml
new file mode 100644 (file)
index 0000000..755c8a9
--- /dev/null
@@ -0,0 +1,8 @@
+<interfaces xmlns="urn:ietf:params:xml:ns:yang:test-interface">
+    <interface>
+        <name>eth0</name>
+        <type>ethernetCsmacd</type>
+        <enabled>false</enabled>
+    </interface>
+</interfaces>
+
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface2.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface2.xml
new file mode 100644 (file)
index 0000000..05db4a5
--- /dev/null
@@ -0,0 +1,5 @@
+<interface>
+    <name>eth0</name>
+    <type>ethernetCsmacd</type>
+    <enabled>false</enabled>
+</interface>
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface3.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface3.xml
new file mode 100644 (file)
index 0000000..e59ba17
--- /dev/null
@@ -0,0 +1,6 @@
+<class xmlns="urn:ietf:params:xml:ns:yang:test-interface2">
+    <student>
+        <name>Thomas</name>
+        <age>23</age>
+    </student>
+</class>
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/yang1/test-interface.yang b/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/yang1/test-interface.yang
new file mode 100644 (file)
index 0000000..f683a69
--- /dev/null
@@ -0,0 +1,30 @@
+module test-interface {
+    yang-version 1;
+    namespace "urn:ietf:params:xml:ns:yang:test-interface";
+    prefix "sn";
+
+    description
+        "test file";   
+
+    revision "2014-07-01" {
+        description
+            "Initial revision";
+        reference "will be defined";
+    }
+    
+    container interfaces {
+        list interface {
+            key "name";
+         
+            leaf name {
+                type string;
+            }
+            leaf type {
+                type string;
+            }
+            leaf enabled {
+                type string;
+            }
+        }
+    }
+}
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/yang2/test-interface2.yang b/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/yang2/test-interface2.yang
new file mode 100644 (file)
index 0000000..13bc0eb
--- /dev/null
@@ -0,0 +1,27 @@
+module test-interface2 {
+    yang-version 1;
+    namespace "urn:ietf:params:xml:ns:yang:test-interface2";
+    prefix "snn";
+
+    description
+        "test file";   
+
+    revision "2014-08-01" {
+        description
+            "Initial revision";
+        reference "will be defined";
+    }
+    
+    container class {
+        list student {
+            key "name";
+         
+            leaf name {
+                type string;
+            }
+            leaf age {
+                type string;
+            }
+        }
+    }
+}