Changed mount point URI decoding in restconf
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / restconf / impl / ControllerContext.xtend
index 308975c8c599a595d24f928cd6dbe4f4c76b8b9b..61237f01a16933ef742041c787874ec9ee8fc957 100644 (file)
@@ -10,7 +10,6 @@ import java.util.HashMap
 import java.util.List
 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
@@ -37,12 +36,16 @@ import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition
 import org.slf4j.LoggerFactory
 
 import static com.google.common.base.Preconditions.*
-import java.util.ArrayList
+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)
     val static ControllerContext INSTANCE = new ControllerContext
     val static NULL_VALUE = "null"
+    val static MOUNT_MODULE = "yang-ext"
+    val static MOUNT_NODE = "mount"
+    val static MOUNT = "yang-ext:mount"
 
     @Property
     var SchemaContext globalSchema;
@@ -66,7 +69,7 @@ class ControllerContext implements SchemaServiceListener {
 
     private def void checkPreconditions() {
         if (globalSchema === null) {
-            throw new ResponseException(Response.Status.SERVICE_UNAVAILABLE, RestconfProvider::NOT_INITALIZED_MSG)
+            throw new ResponseException(SERVICE_UNAVAILABLE, RestconfProvider::NOT_INITALIZED_MSG)
         }
     }
 
@@ -76,7 +79,6 @@ class ControllerContext implements SchemaServiceListener {
 
     public def InstanceIdWithSchemaNode toInstanceIdentifier(String restconfInstance) {
         checkPreconditions
-        val ret = InstanceIdentifier.builder();
         val pathArgs = restconfInstance.split("/");
         if (pathArgs.empty) {
             return null;
@@ -84,21 +86,19 @@ class ControllerContext implements SchemaServiceListener {
         if (pathArgs.head.empty) {
             pathArgs.remove(0)
         }
-        val mountPoints = new ArrayList
-        val schemaNode = ret.collectPathArguments(pathArgs, globalSchema.findModule(pathArgs.head), mountPoints);
-        if (schemaNode === null) {
-            return null
+        val startModule = pathArgs.head.toModuleName();
+        if (startModule === null) {
+            throw new ResponseException(BAD_REQUEST, "First node in URI has to be in format \"moduleName:nodeName\"")
         }
-        return new InstanceIdWithSchemaNode(ret.toInstance, schemaNode, mountPoints.last)
+        val iiWithSchemaNode = collectPathArguments(InstanceIdentifier.builder(), pathArgs,
+            globalSchema.getLatestModule(startModule), null);
+        if (iiWithSchemaNode === null) {
+            throw new ResponseException(BAD_REQUEST, "URI has bad format")
+        }
+        return iiWithSchemaNode
     }
 
-    private def findModule(SchemaContext context,String argument) {
-        checkNotNull(argument);
-        val startModule = argument.toModuleName();
-        return context.getLatestModule(startModule)
-    }
-    
-    private def getLatestModule(SchemaContext schema,String moduleName) {
+    private def getLatestModule(SchemaContext schema, String moduleName) {
         checkArgument(schema !== null);
         checkArgument(moduleName !== null && !moduleName.empty)
         val modules = schema.modules.filter[m|m.name == moduleName]
@@ -121,9 +121,9 @@ class ControllerContext implements SchemaServiceListener {
         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;
+    def findModuleByName(MountInstance mountPoint, String moduleName) {
+        checkArgument(moduleName !== null && mountPoint !== null)
+        val mountPointSchema = mountPoint.schemaContext;
         return mountPointSchema?.getLatestModule(moduleName);
     }
     
@@ -133,9 +133,9 @@ class ControllerContext implements SchemaServiceListener {
         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;
+    def findModuleByNamespace(MountInstance mountPoint, URI namespace) {
+        checkArgument(namespace !== null && mountPoint !== null)
+        val mountPointSchema = mountPoint.schemaContext;
         val moduleSchemas = mountPointSchema?.findModuleByNamespace(namespace)
         return moduleSchemas?.filterLatestModule
     }
@@ -254,39 +254,92 @@ class ControllerContext implements SchemaServiceListener {
         if(object === null) return "";
         return URLEncoder.encode(object.toString)
     }
-
-    private def DataSchemaNode collectPathArguments(InstanceIdentifierBuilder builder, List<String> strings,
-        DataNodeContainer parentNode, List<InstanceIdentifier> mountPoints) {
+    
+    private def InstanceIdWithSchemaNode collectPathArguments(InstanceIdentifierBuilder builder, List<String> strings,
+        DataNodeContainer parentNode, MountInstance mountPoint) {
         checkNotNull(strings)
         if (parentNode === null) {
             return null;
         }
         if (strings.empty) {
-            return parentNode as DataSchemaNode;
-        }
-        val nodeRef = strings.head;
-
-        val nodeName = nodeRef.toNodeName;
-        var targetNode = parentNode.findInstanceDataChild(nodeName);
-        if (targetNode instanceof ChoiceNode) {
-            return null
+            return new InstanceIdWithSchemaNode(builder.toInstance, parentNode as DataSchemaNode, mountPoint)
         }
         
-        if (targetNode === null) {
-            // Node is possibly in other mount point
-            val partialPath = builder.toInstance;
-            val mountPointSchema = mountService?.getMountPoint(partialPath)?.schemaContext;
-            if(mountPointSchema !== null) {
-                val module = mountPointSchema.findModule(strings.head)
-                if (module !== null) {
-                    mountPoints.add(partialPath)
+        val nodeName = strings.head.toNodeName
+        val moduleName = strings.head.toModuleName
+        var DataSchemaNode targetNode = null
+        if (!moduleName.nullOrEmpty) {
+            // if it is mount point
+            if (moduleName == MOUNT_MODULE && nodeName == MOUNT_NODE) {
+                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. " 
+                        + "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)
+                if (module === null) {
+                    throw new ResponseException(BAD_REQUEST,
+                        "URI has bad format. \"" + moduleName + "\" module does not exist.")
                 }
-                return builder.collectPathArguments(strings, module, mountPoints);
+            } else {
+                module = mountPoint.schemaContext?.getLatestModule(moduleName)
+                if (module === null) {
+                    throw new ResponseException(BAD_REQUEST,
+                        "URI has bad format. \"" + moduleName + "\" module does not exist in mount point.")
+                }
+            }
+            targetNode = parentNode.findInstanceDataChild(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" + 
+                    "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)
+            if (targetNode === null) {
+                throw new ResponseException(BAD_REQUEST, "URI has bad format. \"" + nodeName + "\" was not found in parent data node.\n")
             }
-            return null
         }
         
-
         // Number of consumed elements
         var consumed = 1;
         if (targetNode instanceof ListSchemaNode) {
@@ -295,7 +348,7 @@ class ControllerContext implements SchemaServiceListener {
 
             // every key has to be filled
             if ((strings.length - consumed) < keysSize) {
-                return null;
+                throw new ResponseException(BAD_REQUEST,"Missing key for list \"" + listNode.QName.localName + "\".")
             }
             val uriKeyValues = strings.subList(consumed, consumed + keysSize);
             val keyValues = new HashMap<QName, Object>();
@@ -305,7 +358,9 @@ class ControllerContext implements SchemaServiceListener {
 
                 // key value cannot be NULL
                 if (uriKeyValue.equals(NULL_VALUE)) {
-                    return null
+                    throw new ResponseException(BAD_REQUEST, "URI has bad format. List \"" + listNode.QName.localName 
+                        + "\" cannot contain \"null\" value as a key."
+                    )
                 }
                 keyValues.addKeyValue(listNode.getDataChildByName(key), uriKeyValue);
                 i = i + 1;
@@ -319,23 +374,28 @@ class ControllerContext implements SchemaServiceListener {
         }
         if (targetNode instanceof DataNodeContainer) {
             val remaining = strings.subList(consumed, strings.length);
-            val result = builder.collectPathArguments(remaining, targetNode as DataNodeContainer, mountPoints);
+            val result = builder.collectPathArguments(remaining, targetNode as DataNodeContainer, mountPoint);
             return result
         }
 
-        return targetNode
+        return new InstanceIdWithSchemaNode(builder.toInstance, targetNode, mountPoint)
     }
-    
-    static def DataSchemaNode findInstanceDataChild(DataNodeContainer container, String name) {
-        // FIXME: Add namespace comparison
-        var potentialNode = container.getDataChildByName(name);
-        if(potentialNode.instantiatedDataSchema) {
+
+    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;
         }
         val allCases = container.childNodes.filter(ChoiceNode).map[cases].flatten
         for (caze : allCases) {
-            potentialNode = caze.findInstanceDataChild(name);
-            if(potentialNode !== null) {
+            potentialNode = caze.findInstanceDataChild(name, moduleNamespace);
+            if (potentialNode !== null) {
                 return potentialNode;
             }
         }
@@ -372,21 +432,21 @@ class ControllerContext implements SchemaServiceListener {
         checkNotNull(str)
         if (str.contains(":")) {
             val args = str.split(":");
-            checkArgument(args.size === 2);
-            return args.get(0);
-        } else {
-            return null;
+            if (args.size === 2) {
+                return args.get(0);
+            }
         }
+        return null;
     }
 
     private def String toNodeName(String str) {
         if (str.contains(":")) {
             val args = str.split(":");
-            checkArgument(args.size === 2);
-            return args.get(1);
-        } else {
-            return str;
+            if (args.size === 2) {
+                return args.get(1);
+            }
         }
+        return str;
     }
 
     private def QName toQName(String name) {