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=61237f01a16933ef742041c787874ec9ee8fc957;hp=470b4735a441ad7b5f3e8f421c53fea904d033d3;hb=7bea173bf1c3c27d5fa366b25a0f83879105c56d;hpb=d245dda0d7f6e14891af8a3a8f44d20939e00ee4 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 470b4735a4..61237f01a1 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,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 @@ -9,8 +10,9 @@ 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 import org.opendaylight.controller.sal.rest.impl.RestconfProvider import org.opendaylight.yangtools.yang.common.QName import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier @@ -18,28 +20,38 @@ 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 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.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 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" - var SchemaContext schemas; + @Property + var SchemaContext globalSchema; + + @Property + var MountService mountService; private val BiMap uriToModuleName = HashBiMap.create(); private val Map moduleNameToUri = uriToModuleName.inverse(); @@ -56,8 +68,8 @@ class ControllerContext implements SchemaServiceListener { } private def void checkPreconditions() { - if (schemas === null) { - throw new ResponseException(Response.Status.SERVICE_UNAVAILABLE, RestconfProvider::NOT_INITALIZED_MSG) + if (globalSchema === null) { + throw new ResponseException(SERVICE_UNAVAILABLE, RestconfProvider::NOT_INITALIZED_MSG) } } @@ -66,7 +78,7 @@ class ControllerContext implements SchemaServiceListener { } public def InstanceIdWithSchemaNode toInstanceIdentifier(String restconfInstance) { - val ret = InstanceIdentifier.builder(); + checkPreconditions val pathArgs = restconfInstance.split("/"); if (pathArgs.empty) { return null; @@ -74,29 +86,26 @@ class ControllerContext implements SchemaServiceListener { if (pathArgs.head.empty) { pathArgs.remove(0) } - val schemaNode = ret.collectPathArguments(pathArgs, restconfInstance.findModule); - 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) - } - - private def findModule(String restconfInstance) { - checkPreconditions - checkNotNull(restconfInstance); - val pathArgs = restconfInstance.split("/"); - if (pathArgs.empty) { - return null; + val iiWithSchemaNode = collectPathArguments(InstanceIdentifier.builder(), pathArgs, + globalSchema.getLatestModule(startModule), null); + if (iiWithSchemaNode === null) { + throw new ResponseException(BAD_REQUEST, "URI has bad format") } - val modulWithFirstYangStatement = pathArgs.filter[s|s.contains(":")].head - val startModule = modulWithFirstYangStatement.toModuleName(); - return getLatestModule(startModule) + return iiWithSchemaNode } - private def getLatestModule(String moduleName) { - checkPreconditions + private 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] + return modules.filterLatestModule + } + + private def filterLatestModule(Iterable modules) { var latestModule = modules.head for (module : modules) { if (module.revision.after(latestModule.revision)) { @@ -105,13 +114,38 @@ 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 + val moduleSchemas = globalSchema.findModuleByNamespace(namespace) + return moduleSchemas?.filterLatestModule + } + + def findModuleByNamespace(MountInstance mountPoint, URI namespace) { + checkArgument(namespace !== null && mountPoint !== null) + val mountPointSchema = mountPoint.schemaContext; + val moduleSchemas = mountPointSchema?.findModuleByNamespace(namespace) + return moduleSchemas?.filterLatestModule + } def String toFullRestconfIdentifier(InstanceIdentifier path) { checkPreconditions 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); @@ -134,30 +168,36 @@ 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 = schemas.findModuleByNamespace(namespace); - if(moduleSchemas === null) throw new IllegalArgumentException() - var latestModule = moduleSchemas.head - for (m : moduleSchemas) { - if (m.revision.after(latestModule.revision)) { - latestModule = m - } - } - if(latestModule === null) throw new IllegalArgumentException() + val moduleSchemas = globalSchema.findModuleByNamespace(namespace); + if(moduleSchemas === null) return null + var latestModule = moduleSchemas.filterLatestModule + if(latestModule === null) return null uriToModuleName.put(namespace, latestModule.name) module = latestModule.name; } return module } + def findNamespaceByModuleName(String module) { + var namespace = moduleNameToUri.get(module) + if (namespace === null) { + var latestModule = globalSchema.getLatestModule(module) + 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; @@ -214,38 +254,92 @@ class ControllerContext implements SchemaServiceListener { if(object === null) return ""; return URLEncoder.encode(object.toString) } - - private def DataSchemaNode collectPathArguments(InstanceIdentifierBuilder builder, List strings, - DataNodeContainer parentNode) { + + private def InstanceIdWithSchemaNode collectPathArguments(InstanceIdentifierBuilder builder, List strings, + DataNodeContainer parentNode, MountInstance mountPoint) { checkNotNull(strings) if (parentNode === null) { return null; } if (strings.empty) { - return parentNode as DataSchemaNode; + return new InstanceIdWithSchemaNode(builder.toInstance, parentNode as DataSchemaNode, mountPoint) } - 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 = 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.") + } + } 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 - } - if (targetNode instanceof ChoiceNode) { - return null } - + // Number of consumed elements var consumed = 1; if (targetNode instanceof ListSchemaNode) { @@ -254,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(); @@ -264,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; @@ -278,47 +374,88 @@ 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, mountPoint); return result } - return targetNode + 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; + } + val allCases = container.childNodes.filter(ChoiceNode).map[cases].flatten + for (caze : allCases) { + potentialNode = caze.findInstanceDataChild(name, moduleNamespace); + 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 map, DataSchemaNode node, String uriValue) { checkNotNull(uriValue); checkArgument(node instanceof LeafSchemaNode); - val decoded = URLDecoder.decode(uriValue); + 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) + if(baseType instanceof IdentityrefTypeDefinition) { + decoded = toQName(urlDecoded) + } + } 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(":"); - 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) { 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) { @@ -326,42 +463,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; - } }