Support for mount points in Restconf
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / restconf / impl / ControllerContext.xtend
index fed56fe297a8ed9cc929437329701799ce06ccfb..1a60e14589998adbe8390495fe022d4bd5cc30a6 100644 (file)
@@ -1,6 +1,7 @@
 package org.opendaylight.controller.sal.restconf.impl
 
 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
@@ -11,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.rest.impl.RestUtil
 import org.opendaylight.controller.sal.rest.impl.RestconfProvider
 import org.opendaylight.yangtools.yang.common.QName
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier
@@ -18,6 +20,7 @@ import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdent
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument
+import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec
 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode
 import org.opendaylight.yangtools.yang.model.api.ChoiceNode
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
@@ -27,21 +30,23 @@ import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
 import org.opendaylight.yangtools.yang.model.api.SchemaContext
-import org.opendaylight.yangtools.yang.model.api.SchemaNode
-import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition
-import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil
+import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition
+import org.slf4j.LoggerFactory
 
 import static com.google.common.base.Preconditions.*
-import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec
-import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition
+import org.opendaylight.controller.sal.core.api.mount.MountService
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode
 
 class ControllerContext implements SchemaServiceListener {
-
+    val static LOG = LoggerFactory.getLogger(ControllerContext)
     val static ControllerContext INSTANCE = new ControllerContext
-
     val static NULL_VALUE = "null"
 
-    var SchemaContext schemas;
+    @Property
+    var SchemaContext globalSchema;
+    
+    @Property
+    var MountService mountService;
 
     private val BiMap<URI, String> uriToModuleName = HashBiMap.create();
     private val Map<String, URI> moduleNameToUri = uriToModuleName.inverse();
@@ -58,7 +63,7 @@ class ControllerContext implements SchemaServiceListener {
     }
 
     private def void checkPreconditions() {
-        if (schemas === null) {
+        if (globalSchema === null) {
             throw new ResponseException(Response.Status.SERVICE_UNAVAILABLE, RestconfProvider::NOT_INITALIZED_MSG)
         }
     }
@@ -76,29 +81,24 @@ class ControllerContext implements SchemaServiceListener {
         if (pathArgs.head.empty) {
             pathArgs.remove(0)
         }
-        val schemaNode = ret.collectPathArguments(pathArgs, restconfInstance.findModule);
+        val schemaNode = ret.collectPathArguments(pathArgs, globalSchema.findModule(pathArgs.head));
         if (schemaNode === null) {
             return null
         }
         return new InstanceIdWithSchemaNode(ret.toInstance, schemaNode)
     }
 
-    private def findModule(String restconfInstance) {
-        checkPreconditions
-        checkNotNull(restconfInstance);
-        val pathArgs = restconfInstance.split("/");
-        if (pathArgs.empty) {
-            return null;
-        }
-        val modulWithFirstYangStatement = pathArgs.filter[s|s.contains(":")].head
-        val startModule = modulWithFirstYangStatement.toModuleName();
-        return getLatestModule(startModule)
+    private static def findModule(SchemaContext context,String argument) {
+        //checkPreconditions
+        checkNotNull(argument);
+        val startModule = argument.toModuleName();
+        return context.getLatestModule(startModule)
     }
 
-    private def getLatestModule(String moduleName) {
-        checkPreconditions
+    static def getLatestModule(SchemaContext schema,String moduleName) {
+        checkArgument(schema != null);
         checkArgument(moduleName !== null && !moduleName.empty)
-        val modules = schemas.modules.filter[m|m.name == moduleName]
+        val modules = schema.modules.filter[m|m.name == moduleName]
         var latestModule = modules.head
         for (module : modules) {
             if (module.revision.after(latestModule.revision)) {
@@ -113,7 +113,7 @@ class ControllerContext implements SchemaServiceListener {
         val elements = path.path;
         val ret = new StringBuilder();
         val startQName = elements.get(0).nodeType;
-        val initialModule = schemas.findModuleByNamespaceAndRevision(startQName.namespace, startQName.revision)
+        val initialModule = globalSchema.findModuleByNamespaceAndRevision(startQName.namespace, startQName.revision)
         var node = initialModule as DataSchemaNode;
         for (element : elements) {
             node = node.childByQName(element.nodeType);
@@ -140,26 +140,43 @@ class ControllerContext implements SchemaServiceListener {
         checkPreconditions
         var module = uriToModuleName.get(namespace)
         if (module === null) {
-            val moduleSchemas = schemas.findModuleByNamespace(namespace);
-            if(moduleSchemas === null) throw new IllegalArgumentException()
+            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
                 }
             }
-            if(latestModule === null) throw new IllegalArgumentException()
+            if(latestModule === null) return null
             uriToModuleName.put(namespace, latestModule.name)
             module = latestModule.name;
         }
         return module
     }
 
+    def findNamespaceByModule(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
+                }
+            }
+            if(latestModule === null) return null
+            namespace = latestModule.namespace
+            uriToModuleName.put(namespace, latestModule.name)
+        }
+        return namespace
+    }
+
     def CharSequence toRestconfIdentifier(QName qname) {
         checkPreconditions
         var module = uriToModuleName.get(qname.namespace)
         if (module === null) {
-            val moduleSchema = schemas.findModuleByNamespaceAndRevision(qname.namespace, qname.revision);
+            val moduleSchema = globalSchema.findModuleByNamespaceAndRevision(qname.namespace, qname.revision);
             if(moduleSchema === null) throw new IllegalArgumentException()
             uriToModuleName.put(qname.namespace, moduleSchema.name)
             module = moduleSchema.name;
@@ -228,25 +245,22 @@ class ControllerContext implements SchemaServiceListener {
         }
         val nodeRef = strings.head;
 
-        val nodeName = nodeRef.toNodeName();
-        val targetNode = parentNode.getDataChildByName(nodeName);
-        if (targetNode === null) {
-            val children = parentNode.childNodes
-            for (child : children) {
-                if (child instanceof ChoiceNode) {
-                    val choice = child as ChoiceNode
-                    for (caze : choice.cases) {
-                        val result = builder.collectPathArguments(strings, caze as DataNodeContainer);
-                        if (result !== null)
-                            return result
-                    }
-                }
-            }
+        val nodeName = nodeRef.toNodeName;
+        var targetNode = parentNode.findInstanceDataChild(nodeName);
+        if (targetNode instanceof ChoiceNode) {
             return null
         }
-        if (targetNode instanceof ChoiceNode) {
+        
+        if (targetNode === null) {
+            // 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));
+            }
             return null
         }
+        
 
         // Number of consumed elements
         var consumed = 1;
@@ -286,18 +300,42 @@ class ControllerContext implements SchemaServiceListener {
 
         return targetNode
     }
+    
+    static def DataSchemaNode findInstanceDataChild(DataNodeContainer container, String name) {
+        // FIXME: Add namespace comparison
+        var potentialNode = container.getDataChildByName(name);
+        if(potentialNode.instantiatedDataSchema) {
+            return potentialNode;
+        }
+        val allCases = container.childNodes.filter(ChoiceNode).map[cases].flatten
+        for (caze : allCases) {
+            potentialNode = caze.findInstanceDataChild(name);
+            if(potentialNode != null) {
+                return potentialNode;
+            }
+        }
+        return null;
+    }
+    
+    static def boolean isInstantiatedDataSchema(DataSchemaNode node) {
+        switch node {
+            LeafSchemaNode: return true
+            LeafListSchemaNode: return true
+            ContainerSchemaNode: return true
+            ListSchemaNode: return true
+            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 = typedef
-            while (baseType.baseType != null) {
-                baseType = baseType.baseType;
-            }
+        if(decoded === null) {
+            var baseType = RestUtil.resolveBaseTypeFrom(typedef)
             if(baseType instanceof IdentityrefTypeDefinition) {
                 decoded = toQName(urlDecoded)
             }
@@ -305,7 +343,7 @@ class ControllerContext implements SchemaServiceListener {
         map.put(node.QName, decoded);
     }
 
-    private def String toModuleName(String str) {
+    private static def String toModuleName(String str) {
         checkNotNull(str)
         if (str.contains(":")) {
             val args = str.split(":");
@@ -329,8 +367,10 @@ class ControllerContext implements SchemaServiceListener {
     private def QName toQName(String name) {
         val module = name.toModuleName;
         val node = name.toNodeName;
-        val namespace = moduleNameToUri.get(module);
-        return new QName(namespace, null, node);
+        val namespace = FluentIterable.from(globalSchema.modules.sort[o1,o2 | o1.revision.compareTo(o2.revision)]) //
+            .transform[QName.create(namespace,revision,it.name)].findFirst[module == localName]
+        ;
+        return QName.create(namespace,node);
     }
 
     def getRpcDefinition(String name) {
@@ -338,42 +378,11 @@ class ControllerContext implements SchemaServiceListener {
     }
 
     override onGlobalContextUpdated(SchemaContext context) {
-        this.schemas = context;
+        this.globalSchema = context;
         for (operation : context.operations) {
-            val qname = new QName(operation.QName.namespace, null, operation.QName.localName);
+            val qname = operation.QName;
             qnameToRpc.put(qname, operation);
         }
     }
 
-    /**
-     * Resolve target type from leafref type.
-     * 
-     * According to RFC 6020 referenced element has to be leaf (chapter 9.9).
-     * Therefore if other element is referenced then null value is returned.
-     * 
-     * Currently only cases without path-predicate are supported.
-     * 
-     * @param leafRef
-     * @param schemaNode
-     *            data schema node which contains reference
-     * @return type if leaf is referenced and it is possible to find referenced
-     *         node in schema context. In other cases null value is returned
-     */
-    def LeafSchemaNode resolveTypeFromLeafref(LeafrefTypeDefinition leafRef, DataSchemaNode schemaNode) {
-        val xPath = leafRef.getPathStatement();
-        val module = SchemaContextUtil.findParentModule(schemas, schemaNode);
-
-        var SchemaNode foundSchemaNode
-        if (xPath.isAbsolute()) {
-            foundSchemaNode = SchemaContextUtil.findDataSchemaNode(schemas, module, xPath);
-        } else {
-            foundSchemaNode = SchemaContextUtil.findDataSchemaNodeForRelativeXPath(schemas, module, schemaNode, xPath);
-        }
-
-        if (foundSchemaNode instanceof LeafSchemaNode) {
-            return foundSchemaNode as LeafSchemaNode;
-        }
-
-        return null;
-    }
 }