Instance identifier support
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / restconf / impl / ControllerContext.xtend
index 9a61b43df51467dbe76e40a6f10709e60089e4a0..ced272e00b90cdb4e2b6996ead43d5967195d45f 100644 (file)
@@ -1,16 +1,18 @@
 package org.opendaylight.controller.sal.restconf.impl
 
+import com.google.common.base.Preconditions
 import com.google.common.collect.BiMap
 import com.google.common.collect.FluentIterable
 import com.google.common.collect.HashBiMap
 import java.net.URI
 import java.net.URLDecoder
 import java.net.URLEncoder
+import java.util.ArrayList
 import java.util.HashMap
 import java.util.List
 import java.util.Map
 import java.util.concurrent.ConcurrentHashMap
-import org.opendaylight.yangtools.yang.model.api.SchemaServiceListener
+import org.opendaylight.controller.sal.core.api.mount.MountInstance
 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
@@ -32,12 +34,12 @@ 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.SchemaServiceListener
 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition
 import org.slf4j.LoggerFactory
 
 import static com.google.common.base.Preconditions.*
 import static javax.ws.rs.core.Response.Status.*
-import org.opendaylight.controller.sal.core.api.mount.MountInstance
 
 class ControllerContext implements SchemaServiceListener {
     val static LOG = LoggerFactory.getLogger(ControllerContext)
@@ -49,7 +51,7 @@ class ControllerContext implements SchemaServiceListener {
 
     @Property
     var SchemaContext globalSchema;
-
+    
     @Property
     var MountService mountService;
 
@@ -104,7 +106,7 @@ class ControllerContext implements SchemaServiceListener {
         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) {
@@ -114,26 +116,26 @@ class ControllerContext implements SchemaServiceListener {
         }
         return latestModule
     }
-
+    
     def findModuleByName(String moduleName) {
         checkPreconditions
         checkArgument(moduleName !== null && !moduleName.empty)
         return globalSchema.getLatestModule(moduleName)
     }
-
+    
     def findModuleByName(MountInstance mountPoint, String moduleName) {
         checkArgument(moduleName !== null && mountPoint !== null)
         val mountPointSchema = mountPoint.schemaContext;
         return mountPointSchema?.getLatestModule(moduleName);
     }
-
+    
     def findModuleByNamespace(URI namespace) {
         checkPreconditions
         checkArgument(namespace !== null)
         val moduleSchemas = globalSchema.findModuleByNamespace(namespace)
         return moduleSchemas?.filterLatestModule
     }
-
+    
     def findModuleByNamespace(MountInstance mountPoint, URI namespace) {
         checkArgument(namespace !== null && mountPoint !== null)
         val mountPointSchema = mountPoint.schemaContext;
@@ -180,7 +182,7 @@ class ControllerContext implements SchemaServiceListener {
         }
         return moduleName
     }
-
+    
     def findModuleNameByNamespace(MountInstance mountPoint, URI namespace) {
         val module = mountPoint.findModuleByNamespace(namespace);
         return module?.name
@@ -196,7 +198,7 @@ class ControllerContext implements SchemaServiceListener {
         }
         return namespace
     }
-
+    
     def findNamespaceByModuleName(MountInstance mountPoint, String moduleName) {
         val module = mountPoint.findModuleByName(moduleName)
         return module?.namespace
@@ -263,7 +265,7 @@ class ControllerContext implements SchemaServiceListener {
         if(object === null) return "";
         return URLEncoder.encode(object.toString)
     }
-
+    
     private def InstanceIdWithSchemaNode collectPathArguments(InstanceIdentifierBuilder builder, List<String> strings,
         DataNodeContainer parentNode, MountInstance mountPoint) {
         checkNotNull(strings)
@@ -273,7 +275,7 @@ class ControllerContext implements SchemaServiceListener {
         if (strings.empty) {
             return new InstanceIdWithSchemaNode(builder.toInstance, parentNode as DataSchemaNode, mountPoint)
         }
-
+        
         val nodeName = strings.head.toNodeName
         val moduleName = strings.head.toModuleName
         var DataSchemaNode targetNode = null
@@ -283,45 +285,45 @@ class ControllerContext implements SchemaServiceListener {
                 if (mountPoint !== null) {
                     throw new ResponseException(BAD_REQUEST, "Restconf supports just one mount point in URI.")
                 }
-
+                
                 if (mountService === null) {
-                    throw new ResponseException(SERVICE_UNAVAILABLE, "MountService was not found. "
+                    throw new ResponseException(SERVICE_UNAVAILABLE, "MountService was not found. " 
                         + "Finding behind mount points does not work."
                     )
                 }
-
+                
                 val partialPath = builder.toInstance;
                 val mount = mountService.getMountPoint(partialPath)
                 if (mount === null) {
                     LOG.debug("Instance identifier to missing mount point: {}", partialPath)
                     throw new ResponseException(BAD_REQUEST, "Mount point does not exist.")
                 }
-
+                
                 val mountPointSchema = mount.schemaContext;
                 if (mountPointSchema === null) {
                     throw new ResponseException(BAD_REQUEST, "Mount point does not contain any schema with modules.")
                 }
-
+                
                 if (strings.size == 1) { // any data node is not behind mount point
                     return new InstanceIdWithSchemaNode(InstanceIdentifier.builder().toInstance, mountPointSchema, mount)
                 }
-
+                
                 val moduleNameBehindMountPoint = strings.get(1).toModuleName()
                 if (moduleNameBehindMountPoint === null) {
                     throw new ResponseException(BAD_REQUEST,
                         "First node after mount point in URI has to be in format \"moduleName:nodeName\"")
                 }
-
+                
                 val moduleBehindMountPoint = mountPointSchema.getLatestModule(moduleNameBehindMountPoint)
                 if (moduleBehindMountPoint === null) {
                     throw new ResponseException(BAD_REQUEST,
                         "URI has bad format. \"" + moduleName + "\" module does not exist in mount point.")
                 }
-
+                
                 return collectPathArguments(InstanceIdentifier.builder(), strings.subList(1, strings.size),
                     moduleBehindMountPoint, mount);
             }
-
+            
             var Module module = null;
             if (mountPoint === null) {
                 module = globalSchema.getLatestModule(moduleName)
@@ -336,19 +338,32 @@ class ControllerContext implements SchemaServiceListener {
                         "URI has bad format. \"" + moduleName + "\" module does not exist in mount point.")
                 }
             }
-            targetNode = parentNode.findInstanceDataChild(nodeName, module.namespace)
+            targetNode = parentNode.findInstanceDataChildByNameAndNamespace(nodeName, module.namespace)
             if (targetNode === null) {
-                throw new ResponseException(BAD_REQUEST, "URI has bad format. Possible reasons:\n" +
-                    "1. \"" + strings.head + "\" was not found in parent data node.\n" +
+                throw new ResponseException(BAD_REQUEST, "URI has bad format. Possible reasons:\n" + 
+                    "1. \"" + strings.head + "\" was not found in parent data node.\n" + 
                     "2. \"" + strings.head + "\" is behind mount point. Then it should be in format \"/" + MOUNT + "/" + strings.head + "\".")
             }
         } else { // string without module name
-            targetNode = parentNode.findInstanceDataChild(nodeName, null)
+            val potentialSchemaNodes = parentNode.findInstanceDataChildrenByName(nodeName)
+            if (potentialSchemaNodes.size > 1) {
+                val StringBuilder namespacesOfPotentialModules = new StringBuilder;
+                for (potentialNodeSchema : potentialSchemaNodes) {
+                    namespacesOfPotentialModules.append("   ").append(potentialNodeSchema.QName.namespace.toString).append("\n")
+                }
+                throw new ResponseException(BAD_REQUEST, "URI has bad format. Node \"" + nodeName + "\" is added as augment from more than one module. " 
+                        + "Therefore the node must have module name and it has to be in format \"moduleName:nodeName\"."
+                        + "\nThe node is added as augment from modules with namespaces:\n" + namespacesOfPotentialModules)
+            }
+            targetNode = potentialSchemaNodes.head
             if (targetNode === null) {
                 throw new ResponseException(BAD_REQUEST, "URI has bad format. \"" + nodeName + "\" was not found in parent data node.\n")
             }
         }
-
+        
+        if (!(targetNode instanceof ListSchemaNode) && !(targetNode instanceof ContainerSchemaNode)) {
+            throw new ResponseException(BAD_REQUEST,"URI has bad format. Node \"" + strings.head + "\" must be Container or List yang type.")
+        }
         // Number of consumed elements
         var consumed = 1;
         if (targetNode instanceof ListSchemaNode) {
@@ -367,7 +382,7 @@ class ControllerContext implements SchemaServiceListener {
 
                 // key value cannot be NULL
                 if (uriKeyValue.equals(NULL_VALUE)) {
-                    throw new ResponseException(BAD_REQUEST, "URI has bad format. List \"" + listNode.QName.localName
+                    throw new ResponseException(BAD_REQUEST, "URI has bad format. List \"" + listNode.QName.localName 
                         + "\" cannot contain \"null\" value as a key."
                     )
                 }
@@ -390,28 +405,36 @@ class ControllerContext implements SchemaServiceListener {
         return new InstanceIdWithSchemaNode(builder.toInstance, targetNode, mountPoint)
     }
 
-    def DataSchemaNode findInstanceDataChild(DataNodeContainer container, String name, URI moduleNamespace) {
-        var DataSchemaNode potentialNode = null
-        if (moduleNamespace === null) {
-            potentialNode = container.getDataChildByName(name);
-        } else {
-            potentialNode = container.childNodes.filter[n|n.QName.localName == name && n.QName.namespace == moduleNamespace].head
-        }
-
-        if (potentialNode.instantiatedDataSchema) {
-            return potentialNode;
+    def DataSchemaNode findInstanceDataChildByNameAndNamespace(DataNodeContainer container,
+        String name, URI namespace) {
+        Preconditions.checkNotNull(namespace)
+        val potentialSchemaNodes = container.findInstanceDataChildrenByName(name)
+        return potentialSchemaNodes.filter[n|n.QName.namespace == namespace].head
+    }
+    
+    def List<DataSchemaNode> findInstanceDataChildrenByName(DataNodeContainer container, String name) {
+        Preconditions.checkNotNull(container)
+        Preconditions.checkNotNull(name)
+        val instantiatedDataNodeContainers = new ArrayList
+        instantiatedDataNodeContainers.collectInstanceDataNodeContainers(container, name)
+        return instantiatedDataNodeContainers
+    }
+    
+    private def void collectInstanceDataNodeContainers(List<DataSchemaNode> potentialSchemaNodes, DataNodeContainer container,
+        String name) {
+        val nodes = container.childNodes.filter[n|n.QName.localName == name]
+        for (potentialNode : nodes) {
+            if (potentialNode.isInstantiatedDataSchema) {
+                potentialSchemaNodes.add(potentialNode)
+            }
         }
         val allCases = container.childNodes.filter(ChoiceNode).map[cases].flatten
         for (caze : allCases) {
-            potentialNode = caze.findInstanceDataChild(name, moduleNamespace);
-            if (potentialNode !== null) {
-                return potentialNode;
-            }
+            collectInstanceDataNodeContainers(potentialSchemaNodes, caze, name)
         }
-        return null;
     }
-
-    static def boolean isInstantiatedDataSchema(DataSchemaNode node) {
+    
+    def boolean isInstantiatedDataSchema(DataSchemaNode node) {
         switch node {
             LeafSchemaNode: return true
             LeafListSchemaNode: return true
@@ -420,13 +443,13 @@ class ControllerContext implements SchemaServiceListener {
             default: return false
         }
     }
-
+    
     private def void addKeyValue(HashMap<QName, Object> map, DataSchemaNode node, String uriValue) {
         checkNotNull(uriValue);
         checkArgument(node instanceof LeafSchemaNode);
         val urlDecoded = URLDecoder.decode(uriValue);
         val typedef = (node as LeafSchemaNode).type;
-
+        
         var decoded = TypeDefinitionAwareCodec.from(typedef)?.deserialize(urlDecoded)
         if(decoded === null) {
             var baseType = RestUtil.resolveBaseTypeFrom(typedef)