X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=blobdiff_plain;f=opendaylight%2Fmd-sal%2Fsal-rest-connector%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fsal%2Frestconf%2Fimpl%2FControllerContext.xtend;h=2b9c78bb87a2134313676e0097f47e18ca8bd17e;hp=b26810347c160c1114057e3819263cfa2ee2890e;hb=ccb09272d8234b70295c55ffa0419c27af1d69ec;hpb=ba470e5b912b7251110b6e7a1da583144a5b8fe8 diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.xtend b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.xtend index b26810347c..2b9c78bb87 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.xtend +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.xtend @@ -1,16 +1,27 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ package org.opendaylight.controller.sal.restconf.impl +import com.google.common.base.Preconditions +import com.google.common.base.Splitter import com.google.common.collect.BiMap import com.google.common.collect.FluentIterable import com.google.common.collect.HashBiMap +import com.google.common.collect.Lists 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.controller.sal.core.api.model.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,20 +43,22 @@ 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.SchemaContextListener 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 { +class ControllerContext implements SchemaContextListener { 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" public val static MOUNT = "yang-ext:mount" + val static URI_ENCODING_CHAR_SET = "ISO-8859-1" + val static URI_SLASH_PLACEHOLDER = "%2F"; @Property var SchemaContext globalSchema; @@ -77,27 +90,56 @@ class ControllerContext implements SchemaServiceListener { onGlobalContextUpdated(schemas) } - public def InstanceIdWithSchemaNode toInstanceIdentifier(String restconfInstance) { + def InstanceIdWithSchemaNode toInstanceIdentifier(String restconfInstance) { + return restconfInstance.toIdentifier(false) + } + + def InstanceIdWithSchemaNode toMountPointIdentifier(String restconfInstance) { + return restconfInstance.toIdentifier(true) + } + + private def InstanceIdWithSchemaNode toIdentifier(String restconfInstance, boolean toMountPointIdentifier) { checkPreconditions - val pathArgs = restconfInstance.split("/"); + val encodedPathArgs = Lists.newArrayList(Splitter.on("/").split(restconfInstance)) + val pathArgs = urlPathArgsDecode(encodedPathArgs) + pathArgs.omitFirstAndLastEmptyString if (pathArgs.empty) { return null; } - if (pathArgs.head.empty) { - pathArgs.remove(0) - } val startModule = pathArgs.head.toModuleName(); if (startModule === null) { throw new ResponseException(BAD_REQUEST, "First node in URI has to be in format \"moduleName:nodeName\"") } - val iiWithSchemaNode = collectPathArguments(InstanceIdentifier.builder(), pathArgs, - globalSchema.getLatestModule(startModule), null); + var InstanceIdWithSchemaNode iiWithSchemaNode = null; + if (toMountPointIdentifier) { + iiWithSchemaNode = collectPathArguments(InstanceIdentifier.builder(), pathArgs, + globalSchema.getLatestModule(startModule), null, true); + } else { + iiWithSchemaNode = collectPathArguments(InstanceIdentifier.builder(), pathArgs, + globalSchema.getLatestModule(startModule), null, false); + } if (iiWithSchemaNode === null) { throw new ResponseException(BAD_REQUEST, "URI has bad format") } return iiWithSchemaNode } + private def omitFirstAndLastEmptyString(List list) { + if (list.empty) { + return list; + } + if (list.head.empty) { + list.remove(0) + } + if (list.empty) { + return list; + } + if (list.last.empty) { + list.remove(list.indexOf(list.last)) + } + return list; + } + private def getLatestModule(SchemaContext schema, String moduleName) { checkArgument(schema !== null); checkArgument(moduleName !== null && !moduleName.empty) @@ -141,31 +183,63 @@ class ControllerContext implements SchemaServiceListener { return moduleSchemas?.filterLatestModule } + def findModuleByNameAndRevision(QName module) { + checkPreconditions + checkArgument(module !== null && module.localName !== null && module.revision !== null) + return globalSchema.findModuleByName(module.localName, module.revision) + } + + def findModuleByNameAndRevision(MountInstance mountPoint, QName module) { + checkPreconditions + checkArgument(module !== null && module.localName !== null && module.revision !== null && mountPoint !== null) + return mountPoint.schemaContext?.findModuleByName(module.localName, module.revision) + } + + def getDataNodeContainerFor(InstanceIdentifier path) { + checkPreconditions + val elements = path.path; + val startQName = elements.head.nodeType; + val initialModule = globalSchema.findModuleByNamespaceAndRevision(startQName.namespace, startQName.revision) + var node = initialModule as DataNodeContainer; + for (element : elements) { + val potentialNode = node.childByQName(element.nodeType); + if (potentialNode === null || !potentialNode.listOrContainer) { + return null + } + node = potentialNode as DataNodeContainer + } + return node + } + def String toFullRestconfIdentifier(InstanceIdentifier path) { checkPreconditions val elements = path.path; val ret = new StringBuilder(); - val startQName = elements.get(0).nodeType; + val startQName = elements.head.nodeType; val initialModule = globalSchema.findModuleByNamespaceAndRevision(startQName.namespace, startQName.revision) - var node = initialModule as DataSchemaNode; + var node = initialModule as DataNodeContainer; for (element : elements) { - node = node.childByQName(element.nodeType); - ret.append(element.toRestconfIdentifier(node)); + val potentialNode = node.childByQName(element.nodeType); + if (!potentialNode.listOrContainer) { + return null + } + node = potentialNode as DataNodeContainer + ret.append(element.convertToRestconfIdentifier(node)); } return ret.toString } - private def dispatch CharSequence toRestconfIdentifier(NodeIdentifier argument, DataSchemaNode node) { + private def dispatch CharSequence convertToRestconfIdentifier(NodeIdentifier argument, ContainerSchemaNode node) { '''/«argument.nodeType.toRestconfIdentifier()»''' } - private def dispatch CharSequence toRestconfIdentifier(NodeIdentifierWithPredicates argument, ListSchemaNode node) { + private def dispatch CharSequence convertToRestconfIdentifier(NodeIdentifierWithPredicates argument, ListSchemaNode node) { val nodeIdentifier = argument.nodeType.toRestconfIdentifier(); val keyValues = argument.keyValues; return '''/«nodeIdentifier»/«FOR key : node.keyDefinition SEPARATOR "/"»«keyValues.get(key).toUriString»«ENDFOR»''' } - private def dispatch CharSequence toRestconfIdentifier(PathArgument argument, DataSchemaNode node) { + private def dispatch CharSequence convertToRestconfIdentifier(PathArgument argument, DataNodeContainer node) { throw new IllegalArgumentException("Conversion of generic path argument is not supported"); } @@ -202,18 +276,35 @@ class ControllerContext implements SchemaServiceListener { return module?.namespace } + def getAllModules(MountInstance mountPoint) { + checkPreconditions + return mountPoint?.schemaContext?.modules + } + + def getAllModules() { + checkPreconditions + return globalSchema.modules + } + def CharSequence toRestconfIdentifier(QName qname) { checkPreconditions var module = uriToModuleName.get(qname.namespace) if (module === null) { val moduleSchema = globalSchema.findModuleByNamespaceAndRevision(qname.namespace, qname.revision); - if(moduleSchema === null) throw new IllegalArgumentException() + if(moduleSchema === null) return null uriToModuleName.put(qname.namespace, moduleSchema.name) module = moduleSchema.name; } return '''«module»:«qname.localName»'''; } + def CharSequence toRestconfIdentifier(MountInstance mountPoint, QName qname) { + val moduleSchema = mountPoint?.schemaContext.findModuleByNamespaceAndRevision(qname.namespace, qname.revision); + if(moduleSchema === null) return null + val module = moduleSchema.name; + return '''«module»:«qname.localName»'''; + } + private static dispatch def DataSchemaNode childByQName(ChoiceNode container, QName name) { for (caze : container.cases) { val ret = caze.childByQName(name) @@ -237,6 +328,10 @@ class ControllerContext implements SchemaServiceListener { return container.dataNodeChildByQName(name); } + private static dispatch def DataSchemaNode childByQName(Module container, QName name) { + return container.dataNodeChildByQName(name); + } + private static dispatch def DataSchemaNode childByQName(DataSchemaNode container, QName name) { return null; } @@ -261,11 +356,11 @@ class ControllerContext implements SchemaServiceListener { private def toUriString(Object object) { if(object === null) return ""; - return URLEncoder.encode(object.toString) + return URLEncoder.encode(object.toString,URI_ENCODING_CHAR_SET) } private def InstanceIdWithSchemaNode collectPathArguments(InstanceIdentifierBuilder builder, List strings, - DataNodeContainer parentNode, MountInstance mountPoint) { + DataNodeContainer parentNode, MountInstance mountPoint, boolean returnJustMountPoint) { checkNotNull(strings) if (parentNode === null) { return null; @@ -302,6 +397,10 @@ class ControllerContext implements SchemaServiceListener { throw new ResponseException(BAD_REQUEST, "Mount point does not contain any schema with modules.") } + if (returnJustMountPoint) { + return new InstanceIdWithSchemaNode(InstanceIdentifier.builder().toInstance, mountPointSchema, mount) + } + if (strings.size == 1) { // any data node is not behind mount point return new InstanceIdWithSchemaNode(InstanceIdentifier.builder().toInstance, mountPointSchema, mount) } @@ -319,7 +418,7 @@ class ControllerContext implements SchemaServiceListener { } return collectPathArguments(InstanceIdentifier.builder(), strings.subList(1, strings.size), - moduleBehindMountPoint, mount); + moduleBehindMountPoint, mount, returnJustMountPoint); } var Module module = null; @@ -336,19 +435,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" + "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.isListOrContainer) { + 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) { @@ -383,35 +495,43 @@ class ControllerContext implements SchemaServiceListener { } if (targetNode instanceof DataNodeContainer) { val remaining = strings.subList(consumed, strings.length); - val result = builder.collectPathArguments(remaining, targetNode as DataNodeContainer, mountPoint); + val result = builder.collectPathArguments(remaining, targetNode as DataNodeContainer, mountPoint, returnJustMountPoint); return result } 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 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 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,7 +540,7 @@ class ControllerContext implements SchemaServiceListener { default: return false } } - + private def void addKeyValue(HashMap map, DataSchemaNode node, String uriValue) { checkNotNull(uriValue); checkArgument(node instanceof LeafSchemaNode); @@ -461,22 +581,51 @@ class ControllerContext implements SchemaServiceListener { private def QName toQName(String name) { val module = name.toModuleName; val node = name.toNodeName; - val namespace = FluentIterable.from(globalSchema.modules.sort[o1,o2 | o1.revision.compareTo(o2.revision)]) // + 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); + if (namespace === null) { + return null + } + return QName.create(namespace, node); + } + + private def boolean isListOrContainer(DataSchemaNode node) { + return ((node instanceof ListSchemaNode) || (node instanceof ContainerSchemaNode)) } def getRpcDefinition(String name) { - return qnameToRpc.get(name.toQName) + val validName = name.toQName + if (validName === null) { + return null + } + return qnameToRpc.get(validName) } override onGlobalContextUpdated(SchemaContext context) { - this.globalSchema = context; - for (operation : context.operations) { - val qname = operation.QName; - qnameToRpc.put(qname, operation); + if (context !== null) { + qnameToRpc.clear + this.globalSchema = context; + for (operation : context.operations) { + val qname = operation.QName; + qnameToRpc.put(qname, operation); + } } } + + def urlPathArgsDecode(List strings) { + val List decodedPathArgs = new ArrayList(); + for (pathArg : strings) { + decodedPathArgs.add(URLDecoder.decode(pathArg, URI_ENCODING_CHAR_SET)) + } + return decodedPathArgs + } + + def urlPathArgDecode(String pathArg) { + if (pathArg !== null) { + return URLDecoder.decode(pathArg, URI_ENCODING_CHAR_SET) + } + return null + } + }