2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.controller.sal.restconf.impl
10 import com.google.common.base.Preconditions
11 import com.google.common.collect.BiMap
12 import com.google.common.collect.FluentIterable
13 import com.google.common.collect.HashBiMap
15 import java.net.URLDecoder
16 import java.net.URLEncoder
17 import java.util.ArrayList
18 import java.util.HashMap
21 import java.util.concurrent.ConcurrentHashMap
22 import org.opendaylight.controller.sal.core.api.mount.MountInstance
23 import org.opendaylight.controller.sal.core.api.mount.MountService
24 import org.opendaylight.controller.sal.rest.impl.RestUtil
25 import org.opendaylight.controller.sal.rest.impl.RestconfProvider
26 import org.opendaylight.yangtools.yang.common.QName
27 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier
28 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder
29 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier
30 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates
31 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument
32 import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec
33 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode
34 import org.opendaylight.yangtools.yang.model.api.ChoiceNode
35 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
36 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer
37 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
38 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode
39 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode
40 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
41 import org.opendaylight.yangtools.yang.model.api.Module
42 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
43 import org.opendaylight.yangtools.yang.model.api.SchemaContext
44 import org.opendaylight.yangtools.yang.model.api.SchemaServiceListener
45 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition
46 import org.slf4j.LoggerFactory
48 import static com.google.common.base.Preconditions.*
49 import static javax.ws.rs.core.Response.Status.*
51 class ControllerContext implements SchemaServiceListener {
52 val static LOG = LoggerFactory.getLogger(ControllerContext)
53 val static ControllerContext INSTANCE = new ControllerContext
54 val static NULL_VALUE = "null"
55 val static MOUNT_MODULE = "yang-ext"
56 val static MOUNT_NODE = "mount"
57 public val static MOUNT = "yang-ext:mount"
60 var SchemaContext globalSchema;
63 var MountService mountService;
65 private val BiMap<URI, String> uriToModuleName = HashBiMap.create();
66 private val Map<String, URI> moduleNameToUri = uriToModuleName.inverse();
67 private val Map<QName, RpcDefinition> qnameToRpc = new ConcurrentHashMap();
70 if (INSTANCE !== null) {
71 throw new IllegalStateException("Already instantiated");
75 static def getInstance() {
79 private def void checkPreconditions() {
80 if (globalSchema === null) {
81 throw new ResponseException(SERVICE_UNAVAILABLE, RestconfProvider::NOT_INITALIZED_MSG)
85 def setSchemas(SchemaContext schemas) {
86 onGlobalContextUpdated(schemas)
89 def InstanceIdWithSchemaNode toInstanceIdentifier(String restconfInstance) {
90 return restconfInstance.toIdentifier(false)
93 def InstanceIdWithSchemaNode toMountPointIdentifier(String restconfInstance) {
94 return restconfInstance.toIdentifier(true)
97 private def InstanceIdWithSchemaNode toIdentifier(String restconfInstance, boolean toMountPointIdentifier) {
99 val pathArgs = restconfInstance.split("/");
100 if (pathArgs.empty) {
103 if (pathArgs.head.empty) {
106 val startModule = pathArgs.head.toModuleName();
107 if (startModule === null) {
108 throw new ResponseException(BAD_REQUEST, "First node in URI has to be in format \"moduleName:nodeName\"")
110 var InstanceIdWithSchemaNode iiWithSchemaNode = null;
111 if (toMountPointIdentifier) {
112 iiWithSchemaNode = collectPathArguments(InstanceIdentifier.builder(), pathArgs,
113 globalSchema.getLatestModule(startModule), null, true);
115 iiWithSchemaNode = collectPathArguments(InstanceIdentifier.builder(), pathArgs,
116 globalSchema.getLatestModule(startModule), null, false);
118 if (iiWithSchemaNode === null) {
119 throw new ResponseException(BAD_REQUEST, "URI has bad format")
121 return iiWithSchemaNode
124 private def getLatestModule(SchemaContext schema, String moduleName) {
125 checkArgument(schema !== null);
126 checkArgument(moduleName !== null && !moduleName.empty)
127 val modules = schema.modules.filter[m|m.name == moduleName]
128 return modules.filterLatestModule
131 private def filterLatestModule(Iterable<Module> modules) {
132 var latestModule = modules.head
133 for (module : modules) {
134 if (module.revision.after(latestModule.revision)) {
135 latestModule = module
141 def findModuleByName(String moduleName) {
143 checkArgument(moduleName !== null && !moduleName.empty)
144 return globalSchema.getLatestModule(moduleName)
147 def findModuleByName(MountInstance mountPoint, String moduleName) {
148 checkArgument(moduleName !== null && mountPoint !== null)
149 val mountPointSchema = mountPoint.schemaContext;
150 return mountPointSchema?.getLatestModule(moduleName);
153 def findModuleByNamespace(URI namespace) {
155 checkArgument(namespace !== null)
156 val moduleSchemas = globalSchema.findModuleByNamespace(namespace)
157 return moduleSchemas?.filterLatestModule
160 def findModuleByNamespace(MountInstance mountPoint, URI namespace) {
161 checkArgument(namespace !== null && mountPoint !== null)
162 val mountPointSchema = mountPoint.schemaContext;
163 val moduleSchemas = mountPointSchema?.findModuleByNamespace(namespace)
164 return moduleSchemas?.filterLatestModule
167 def findModuleByNameAndRevision(QName module) {
169 checkArgument(module !== null && module.localName !== null && module.revision !== null)
170 return globalSchema.findModuleByName(module.localName, module.revision)
173 def findModuleByNameAndRevision(MountInstance mountPoint, QName module) {
175 checkArgument(module !== null && module.localName !== null && module.revision !== null && mountPoint !== null)
176 return mountPoint.schemaContext?.findModuleByName(module.localName, module.revision)
179 def String toFullRestconfIdentifier(InstanceIdentifier path) {
181 val elements = path.path;
182 val ret = new StringBuilder();
183 val startQName = elements.get(0).nodeType;
184 val initialModule = globalSchema.findModuleByNamespaceAndRevision(startQName.namespace, startQName.revision)
185 var node = initialModule as DataSchemaNode;
186 for (element : elements) {
187 node = node.childByQName(element.nodeType);
188 ret.append(element.toRestconfIdentifier(node));
193 private def dispatch CharSequence toRestconfIdentifier(NodeIdentifier argument, DataSchemaNode node) {
194 '''/«argument.nodeType.toRestconfIdentifier()»'''
197 private def dispatch CharSequence toRestconfIdentifier(NodeIdentifierWithPredicates argument, ListSchemaNode node) {
198 val nodeIdentifier = argument.nodeType.toRestconfIdentifier();
199 val keyValues = argument.keyValues;
200 return '''/«nodeIdentifier»/«FOR key : node.keyDefinition SEPARATOR "/"»«keyValues.get(key).toUriString»«ENDFOR»'''
203 private def dispatch CharSequence toRestconfIdentifier(PathArgument argument, DataSchemaNode node) {
204 throw new IllegalArgumentException("Conversion of generic path argument is not supported");
207 def findModuleNameByNamespace(URI namespace) {
209 var moduleName = uriToModuleName.get(namespace)
210 if (moduleName === null) {
211 val module = findModuleByNamespace(namespace)
212 if (module === null) return null
213 moduleName = module.name
214 uriToModuleName.put(namespace, moduleName)
219 def findModuleNameByNamespace(MountInstance mountPoint, URI namespace) {
220 val module = mountPoint.findModuleByNamespace(namespace);
224 def findNamespaceByModuleName(String moduleName) {
225 var namespace = moduleNameToUri.get(moduleName)
226 if (namespace === null) {
227 var module = findModuleByName(moduleName)
228 if(module === null) return null
229 namespace = module.namespace
230 uriToModuleName.put(namespace, moduleName)
235 def findNamespaceByModuleName(MountInstance mountPoint, String moduleName) {
236 val module = mountPoint.findModuleByName(moduleName)
237 return module?.namespace
240 def getAllModules(MountInstance mountPoint) {
242 return mountPoint?.schemaContext?.modules
245 def getAllModules() {
247 return globalSchema.modules
250 def CharSequence toRestconfIdentifier(QName qname) {
252 var module = uriToModuleName.get(qname.namespace)
253 if (module === null) {
254 val moduleSchema = globalSchema.findModuleByNamespaceAndRevision(qname.namespace, qname.revision);
255 if(moduleSchema === null) return null
256 uriToModuleName.put(qname.namespace, moduleSchema.name)
257 module = moduleSchema.name;
259 return '''«module»:«qname.localName»''';
262 def CharSequence toRestconfIdentifier(MountInstance mountPoint, QName qname) {
263 val moduleSchema = mountPoint?.schemaContext.findModuleByNamespaceAndRevision(qname.namespace, qname.revision);
264 if(moduleSchema === null) return null
265 val module = moduleSchema.name;
266 return '''«module»:«qname.localName»''';
269 private static dispatch def DataSchemaNode childByQName(ChoiceNode container, QName name) {
270 for (caze : container.cases) {
271 val ret = caze.childByQName(name)
279 private static dispatch def DataSchemaNode childByQName(ChoiceCaseNode container, QName name) {
280 val ret = container.getDataChildByName(name);
284 private static dispatch def DataSchemaNode childByQName(ContainerSchemaNode container, QName name) {
285 return container.dataNodeChildByQName(name);
288 private static dispatch def DataSchemaNode childByQName(ListSchemaNode container, QName name) {
289 return container.dataNodeChildByQName(name);
292 private static dispatch def DataSchemaNode childByQName(DataSchemaNode container, QName name) {
296 private static def DataSchemaNode dataNodeChildByQName(DataNodeContainer container, QName name) {
297 var ret = container.getDataChildByName(name);
300 // Find in Choice Cases
301 for (node : container.childNodes) {
302 if (node instanceof ChoiceCaseNode) {
303 val caseNode = (node as ChoiceCaseNode);
304 ret = caseNode.childByQName(name);
314 private def toUriString(Object object) {
315 if(object === null) return "";
316 return URLEncoder.encode(object.toString)
319 private def InstanceIdWithSchemaNode collectPathArguments(InstanceIdentifierBuilder builder, List<String> strings,
320 DataNodeContainer parentNode, MountInstance mountPoint, boolean returnJustMountPoint) {
321 checkNotNull(strings)
322 if (parentNode === null) {
326 return new InstanceIdWithSchemaNode(builder.toInstance, parentNode as DataSchemaNode, mountPoint)
329 val nodeName = strings.head.toNodeName
330 val moduleName = strings.head.toModuleName
331 var DataSchemaNode targetNode = null
332 if (!moduleName.nullOrEmpty) {
333 // if it is mount point
334 if (moduleName == MOUNT_MODULE && nodeName == MOUNT_NODE) {
335 if (mountPoint !== null) {
336 throw new ResponseException(BAD_REQUEST, "Restconf supports just one mount point in URI.")
339 if (mountService === null) {
340 throw new ResponseException(SERVICE_UNAVAILABLE, "MountService was not found. "
341 + "Finding behind mount points does not work."
345 val partialPath = builder.toInstance;
346 val mount = mountService.getMountPoint(partialPath)
347 if (mount === null) {
348 LOG.debug("Instance identifier to missing mount point: {}", partialPath)
349 throw new ResponseException(BAD_REQUEST, "Mount point does not exist.")
352 val mountPointSchema = mount.schemaContext;
353 if (mountPointSchema === null) {
354 throw new ResponseException(BAD_REQUEST, "Mount point does not contain any schema with modules.")
357 if (returnJustMountPoint) {
358 return new InstanceIdWithSchemaNode(InstanceIdentifier.builder().toInstance, mountPointSchema, mount)
361 if (strings.size == 1) { // any data node is not behind mount point
362 return new InstanceIdWithSchemaNode(InstanceIdentifier.builder().toInstance, mountPointSchema, mount)
365 val moduleNameBehindMountPoint = strings.get(1).toModuleName()
366 if (moduleNameBehindMountPoint === null) {
367 throw new ResponseException(BAD_REQUEST,
368 "First node after mount point in URI has to be in format \"moduleName:nodeName\"")
371 val moduleBehindMountPoint = mountPointSchema.getLatestModule(moduleNameBehindMountPoint)
372 if (moduleBehindMountPoint === null) {
373 throw new ResponseException(BAD_REQUEST,
374 "URI has bad format. \"" + moduleName + "\" module does not exist in mount point.")
377 return collectPathArguments(InstanceIdentifier.builder(), strings.subList(1, strings.size),
378 moduleBehindMountPoint, mount, returnJustMountPoint);
381 var Module module = null;
382 if (mountPoint === null) {
383 module = globalSchema.getLatestModule(moduleName)
384 if (module === null) {
385 throw new ResponseException(BAD_REQUEST,
386 "URI has bad format. \"" + moduleName + "\" module does not exist.")
389 module = mountPoint.schemaContext?.getLatestModule(moduleName)
390 if (module === null) {
391 throw new ResponseException(BAD_REQUEST,
392 "URI has bad format. \"" + moduleName + "\" module does not exist in mount point.")
395 targetNode = parentNode.findInstanceDataChildByNameAndNamespace(nodeName, module.namespace)
396 if (targetNode === null) {
397 throw new ResponseException(BAD_REQUEST, "URI has bad format. Possible reasons:\n" +
398 "1. \"" + strings.head + "\" was not found in parent data node.\n" +
399 "2. \"" + strings.head + "\" is behind mount point. Then it should be in format \"/" + MOUNT + "/" + strings.head + "\".")
401 } else { // string without module name
402 val potentialSchemaNodes = parentNode.findInstanceDataChildrenByName(nodeName)
403 if (potentialSchemaNodes.size > 1) {
404 val StringBuilder namespacesOfPotentialModules = new StringBuilder;
405 for (potentialNodeSchema : potentialSchemaNodes) {
406 namespacesOfPotentialModules.append(" ").append(potentialNodeSchema.QName.namespace.toString).append("\n")
408 throw new ResponseException(BAD_REQUEST, "URI has bad format. Node \"" + nodeName + "\" is added as augment from more than one module. "
409 + "Therefore the node must have module name and it has to be in format \"moduleName:nodeName\"."
410 + "\nThe node is added as augment from modules with namespaces:\n" + namespacesOfPotentialModules)
412 targetNode = potentialSchemaNodes.head
413 if (targetNode === null) {
414 throw new ResponseException(BAD_REQUEST, "URI has bad format. \"" + nodeName + "\" was not found in parent data node.\n")
418 if (!(targetNode instanceof ListSchemaNode) && !(targetNode instanceof ContainerSchemaNode)) {
419 throw new ResponseException(BAD_REQUEST,"URI has bad format. Node \"" + strings.head + "\" must be Container or List yang type.")
421 // Number of consumed elements
423 if (targetNode instanceof ListSchemaNode) {
424 val listNode = targetNode as ListSchemaNode;
425 val keysSize = listNode.keyDefinition.size
427 // every key has to be filled
428 if ((strings.length - consumed) < keysSize) {
429 throw new ResponseException(BAD_REQUEST,"Missing key for list \"" + listNode.QName.localName + "\".")
431 val uriKeyValues = strings.subList(consumed, consumed + keysSize);
432 val keyValues = new HashMap<QName, Object>();
434 for (key : listNode.keyDefinition) {
435 val uriKeyValue = uriKeyValues.get(i);
437 // key value cannot be NULL
438 if (uriKeyValue.equals(NULL_VALUE)) {
439 throw new ResponseException(BAD_REQUEST, "URI has bad format. List \"" + listNode.QName.localName
440 + "\" cannot contain \"null\" value as a key."
443 keyValues.addKeyValue(listNode.getDataChildByName(key), uriKeyValue);
446 consumed = consumed + i;
447 builder.nodeWithKey(targetNode.QName, keyValues);
450 // Only one instance of node is allowed
451 builder.node(targetNode.QName);
453 if (targetNode instanceof DataNodeContainer) {
454 val remaining = strings.subList(consumed, strings.length);
455 val result = builder.collectPathArguments(remaining, targetNode as DataNodeContainer, mountPoint, returnJustMountPoint);
459 return new InstanceIdWithSchemaNode(builder.toInstance, targetNode, mountPoint)
462 def DataSchemaNode findInstanceDataChildByNameAndNamespace(DataNodeContainer container,
463 String name, URI namespace) {
464 Preconditions.checkNotNull(namespace)
465 val potentialSchemaNodes = container.findInstanceDataChildrenByName(name)
466 return potentialSchemaNodes.filter[n|n.QName.namespace == namespace].head
469 def List<DataSchemaNode> findInstanceDataChildrenByName(DataNodeContainer container, String name) {
470 Preconditions.checkNotNull(container)
471 Preconditions.checkNotNull(name)
472 val instantiatedDataNodeContainers = new ArrayList
473 instantiatedDataNodeContainers.collectInstanceDataNodeContainers(container, name)
474 return instantiatedDataNodeContainers
477 private def void collectInstanceDataNodeContainers(List<DataSchemaNode> potentialSchemaNodes, DataNodeContainer container,
479 val nodes = container.childNodes.filter[n|n.QName.localName == name]
480 for (potentialNode : nodes) {
481 if (potentialNode.isInstantiatedDataSchema) {
482 potentialSchemaNodes.add(potentialNode)
485 val allCases = container.childNodes.filter(ChoiceNode).map[cases].flatten
486 for (caze : allCases) {
487 collectInstanceDataNodeContainers(potentialSchemaNodes, caze, name)
491 def boolean isInstantiatedDataSchema(DataSchemaNode node) {
493 LeafSchemaNode: return true
494 LeafListSchemaNode: return true
495 ContainerSchemaNode: return true
496 ListSchemaNode: return true
497 default: return false
501 private def void addKeyValue(HashMap<QName, Object> map, DataSchemaNode node, String uriValue) {
502 checkNotNull(uriValue);
503 checkArgument(node instanceof LeafSchemaNode);
504 val urlDecoded = URLDecoder.decode(uriValue);
505 val typedef = (node as LeafSchemaNode).type;
507 var decoded = TypeDefinitionAwareCodec.from(typedef)?.deserialize(urlDecoded)
508 if(decoded === null) {
509 var baseType = RestUtil.resolveBaseTypeFrom(typedef)
510 if(baseType instanceof IdentityrefTypeDefinition) {
511 decoded = toQName(urlDecoded)
514 map.put(node.QName, decoded);
517 private static def String toModuleName(String str) {
519 if (str.contains(":")) {
520 val args = str.split(":");
521 if (args.size === 2) {
528 private def String toNodeName(String str) {
529 if (str.contains(":")) {
530 val args = str.split(":");
531 if (args.size === 2) {
538 private def QName toQName(String name) {
539 val module = name.toModuleName;
540 val node = name.toNodeName;
541 val namespace = FluentIterable.from(globalSchema.modules.sort[o1,o2 | o1.revision.compareTo(o2.revision)]) //
542 .transform[QName.create(namespace,revision,it.name)].findFirst[module == localName]
544 return QName.create(namespace,node);
547 def getRpcDefinition(String name) {
548 return qnameToRpc.get(name.toQName)
551 override onGlobalContextUpdated(SchemaContext context) {
552 this.globalSchema = context;
553 for (operation : context.operations) {
554 val qname = operation.QName;
555 qnameToRpc.put(qname, operation);