Merge "Bug 509: Fixed incorrect merging of Data Store Writes / Events"
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / restconf / impl / ControllerContext.xtend
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.sal.restconf.impl
9
10 import com.google.common.base.Preconditions
11 import com.google.common.base.Splitter
12 import com.google.common.collect.BiMap
13 import com.google.common.collect.FluentIterable
14 import com.google.common.collect.HashBiMap
15 import com.google.common.collect.Lists
16 import java.net.URI
17 import java.net.URLDecoder
18 import java.net.URLEncoder
19 import java.util.ArrayList
20 import java.util.HashMap
21 import java.util.List
22 import java.util.Map
23 import java.util.concurrent.ConcurrentHashMap
24 import org.opendaylight.controller.sal.core.api.mount.MountInstance
25 import org.opendaylight.controller.sal.core.api.mount.MountService
26 import org.opendaylight.controller.sal.rest.impl.RestUtil
27 import org.opendaylight.controller.sal.rest.impl.RestconfProvider
28 import org.opendaylight.yangtools.yang.common.QName
29 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier
30 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder
31 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier
32 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates
33 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument
34 import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec
35 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode
36 import org.opendaylight.yangtools.yang.model.api.ChoiceNode
37 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
38 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer
39 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
40 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode
41 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode
42 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
43 import org.opendaylight.yangtools.yang.model.api.Module
44 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
45 import org.opendaylight.yangtools.yang.model.api.SchemaContext
46 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener
47 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition
48 import org.slf4j.LoggerFactory
49
50 import static com.google.common.base.Preconditions.*
51 import static javax.ws.rs.core.Response.Status.*
52
53 class ControllerContext implements SchemaContextListener {
54     val static LOG = LoggerFactory.getLogger(ControllerContext)
55     val static ControllerContext INSTANCE = new ControllerContext
56     val static NULL_VALUE = "null"
57     val static MOUNT_MODULE = "yang-ext"
58     val static MOUNT_NODE = "mount"
59     public val static MOUNT = "yang-ext:mount"
60     val static URI_ENCODING_CHAR_SET = "ISO-8859-1"
61     val static URI_SLASH_PLACEHOLDER = "%2F";
62
63     @Property
64     var SchemaContext globalSchema;
65     
66     @Property
67     var MountService mountService;
68
69     private val BiMap<URI, String> uriToModuleName = HashBiMap.create();
70     private val Map<String, URI> moduleNameToUri = uriToModuleName.inverse();
71     private val Map<QName, RpcDefinition> qnameToRpc = new ConcurrentHashMap();
72
73     private new() {
74         if (INSTANCE !== null) {
75             throw new IllegalStateException("Already instantiated");
76         }
77     }
78
79     static def getInstance() {
80         return INSTANCE
81     }
82
83     private def void checkPreconditions() {
84         if (globalSchema === null) {
85             throw new ResponseException(SERVICE_UNAVAILABLE, RestconfProvider::NOT_INITALIZED_MSG)
86         }
87     }
88
89     def setSchemas(SchemaContext schemas) {
90         onGlobalContextUpdated(schemas)
91     }
92
93     def InstanceIdWithSchemaNode toInstanceIdentifier(String restconfInstance) {
94         return restconfInstance.toIdentifier(false)
95     }
96
97     def InstanceIdWithSchemaNode toMountPointIdentifier(String restconfInstance) {
98         return restconfInstance.toIdentifier(true)
99     }
100
101     private def InstanceIdWithSchemaNode toIdentifier(String restconfInstance, boolean toMountPointIdentifier) {
102         checkPreconditions
103         val encodedPathArgs = Lists.newArrayList(Splitter.on("/").split(restconfInstance))
104         val pathArgs = urlPathArgsDecode(encodedPathArgs)
105         pathArgs.omitFirstAndLastEmptyString
106         if (pathArgs.empty) {
107             return null;
108         }
109         val startModule = pathArgs.head.toModuleName();
110         if (startModule === null) {
111             throw new ResponseException(BAD_REQUEST, "First node in URI has to be in format \"moduleName:nodeName\"")
112         }
113         var InstanceIdWithSchemaNode iiWithSchemaNode = null;
114         if (toMountPointIdentifier) {
115             iiWithSchemaNode = collectPathArguments(InstanceIdentifier.builder(), pathArgs,
116             globalSchema.getLatestModule(startModule), null, true);
117         } else {
118             iiWithSchemaNode = collectPathArguments(InstanceIdentifier.builder(), pathArgs,
119             globalSchema.getLatestModule(startModule), null, false);
120         }
121         if (iiWithSchemaNode === null) {
122             throw new ResponseException(BAD_REQUEST, "URI has bad format")
123         }
124         return iiWithSchemaNode
125     }
126
127     private def omitFirstAndLastEmptyString(List<String> list) {
128         if (list.empty) {
129             return list;
130         }
131         if (list.head.empty) {
132             list.remove(0)
133         }
134         if (list.empty) {
135             return list;
136         }
137         if (list.last.empty) {
138             list.remove(list.indexOf(list.last))
139         }
140         return list;
141     }
142
143     private def getLatestModule(SchemaContext schema, String moduleName) {
144         checkArgument(schema !== null);
145         checkArgument(moduleName !== null && !moduleName.empty)
146         val modules = schema.modules.filter[m|m.name == moduleName]
147         return modules.filterLatestModule
148     }
149     
150     private def filterLatestModule(Iterable<Module> modules) {
151         var latestModule = modules.head
152         for (module : modules) {
153             if (module.revision.after(latestModule.revision)) {
154                 latestModule = module
155             }
156         }
157         return latestModule
158     }
159     
160     def findModuleByName(String moduleName) {
161         checkPreconditions
162         checkArgument(moduleName !== null && !moduleName.empty)
163         return globalSchema.getLatestModule(moduleName)
164     }
165     
166     def findModuleByName(MountInstance mountPoint, String moduleName) {
167         checkArgument(moduleName !== null && mountPoint !== null)
168         val mountPointSchema = mountPoint.schemaContext;
169         return mountPointSchema?.getLatestModule(moduleName);
170     }
171     
172     def findModuleByNamespace(URI namespace) {
173         checkPreconditions
174         checkArgument(namespace !== null)
175         val moduleSchemas = globalSchema.findModuleByNamespace(namespace)
176         return moduleSchemas?.filterLatestModule
177     }
178     
179     def findModuleByNamespace(MountInstance mountPoint, URI namespace) {
180         checkArgument(namespace !== null && mountPoint !== null)
181         val mountPointSchema = mountPoint.schemaContext;
182         val moduleSchemas = mountPointSchema?.findModuleByNamespace(namespace)
183         return moduleSchemas?.filterLatestModule
184     }
185
186     def findModuleByNameAndRevision(QName module) {
187         checkPreconditions
188         checkArgument(module !== null && module.localName !== null && module.revision !== null)
189         return globalSchema.findModuleByName(module.localName, module.revision)
190     }
191
192     def findModuleByNameAndRevision(MountInstance mountPoint, QName module) {
193         checkPreconditions
194         checkArgument(module !== null && module.localName !== null && module.revision !== null && mountPoint !== null)
195         return mountPoint.schemaContext?.findModuleByName(module.localName, module.revision)
196     }
197
198     def getDataNodeContainerFor(InstanceIdentifier path) {
199         checkPreconditions
200         val elements = path.path;
201         val startQName = elements.head.nodeType;
202         val initialModule = globalSchema.findModuleByNamespaceAndRevision(startQName.namespace, startQName.revision)
203         var node = initialModule as DataNodeContainer;
204         for (element : elements) {
205             val potentialNode = node.childByQName(element.nodeType);
206             if (potentialNode === null || !potentialNode.listOrContainer) {
207                 return null
208             }
209             node = potentialNode as DataNodeContainer
210         }
211         return node
212     }
213
214     def String toFullRestconfIdentifier(InstanceIdentifier path) {
215         checkPreconditions
216         val elements = path.path;
217         val ret = new StringBuilder();
218         val startQName = elements.head.nodeType;
219         val initialModule = globalSchema.findModuleByNamespaceAndRevision(startQName.namespace, startQName.revision)
220         var node = initialModule as DataNodeContainer;
221         for (element : elements) {
222             val potentialNode = node.childByQName(element.nodeType);
223             if (!potentialNode.listOrContainer) {
224                 return null
225             }
226             node = potentialNode as DataNodeContainer
227             ret.append(element.convertToRestconfIdentifier(node));
228         }
229         return ret.toString
230     }
231
232     private def dispatch CharSequence convertToRestconfIdentifier(NodeIdentifier argument, ContainerSchemaNode node) {
233         '''/«argument.nodeType.toRestconfIdentifier()»'''
234     }
235
236     private def dispatch CharSequence convertToRestconfIdentifier(NodeIdentifierWithPredicates argument, ListSchemaNode node) {
237         val nodeIdentifier = argument.nodeType.toRestconfIdentifier();
238         val keyValues = argument.keyValues;
239         return '''/«nodeIdentifier»/«FOR key : node.keyDefinition SEPARATOR "/"»«keyValues.get(key).toUriString»«ENDFOR»'''
240     }
241
242     private def dispatch CharSequence convertToRestconfIdentifier(PathArgument argument, DataNodeContainer node) {
243         throw new IllegalArgumentException("Conversion of generic path argument is not supported");
244     }
245
246     def findModuleNameByNamespace(URI namespace) {
247         checkPreconditions
248         var moduleName = uriToModuleName.get(namespace)
249         if (moduleName === null) {
250             val module = findModuleByNamespace(namespace)
251             if (module === null) return null
252             moduleName = module.name
253             uriToModuleName.put(namespace, moduleName)
254         }
255         return moduleName
256     }
257     
258     def findModuleNameByNamespace(MountInstance mountPoint, URI namespace) {
259         val module = mountPoint.findModuleByNamespace(namespace);
260         return module?.name
261     }
262
263     def findNamespaceByModuleName(String moduleName) {
264         var namespace = moduleNameToUri.get(moduleName)
265         if (namespace === null) {
266             var module = findModuleByName(moduleName)
267             if(module === null) return null
268             namespace = module.namespace
269             uriToModuleName.put(namespace, moduleName)
270         }
271         return namespace
272     }
273     
274     def findNamespaceByModuleName(MountInstance mountPoint, String moduleName) {
275         val module = mountPoint.findModuleByName(moduleName)
276         return module?.namespace
277     }
278
279     def getAllModules(MountInstance mountPoint) {
280         checkPreconditions
281         return mountPoint?.schemaContext?.modules
282     }
283     
284     def getAllModules() {
285         checkPreconditions
286         return globalSchema.modules
287     }
288
289     def CharSequence toRestconfIdentifier(QName qname) {
290         checkPreconditions
291         var module = uriToModuleName.get(qname.namespace)
292         if (module === null) {
293             val moduleSchema = globalSchema.findModuleByNamespaceAndRevision(qname.namespace, qname.revision);
294             if(moduleSchema === null) return null
295             uriToModuleName.put(qname.namespace, moduleSchema.name)
296             module = moduleSchema.name;
297         }
298         return '''«module»:«qname.localName»''';
299     }
300
301     def CharSequence toRestconfIdentifier(MountInstance mountPoint, QName qname) {
302         val moduleSchema = mountPoint?.schemaContext.findModuleByNamespaceAndRevision(qname.namespace, qname.revision);
303         if(moduleSchema === null) return null
304         val module = moduleSchema.name;
305         return '''«module»:«qname.localName»''';
306     }
307
308     private static dispatch def DataSchemaNode childByQName(ChoiceNode container, QName name) {
309         for (caze : container.cases) {
310             val ret = caze.childByQName(name)
311             if (ret !== null) {
312                 return ret;
313             }
314         }
315         return null;
316     }
317
318     private static dispatch def DataSchemaNode childByQName(ChoiceCaseNode container, QName name) {
319         val ret = container.getDataChildByName(name);
320         return ret;
321     }
322
323     private static dispatch def DataSchemaNode childByQName(ContainerSchemaNode container, QName name) {
324         return container.dataNodeChildByQName(name);
325     }
326
327     private static dispatch def DataSchemaNode childByQName(ListSchemaNode container, QName name) {
328         return container.dataNodeChildByQName(name);
329     }
330
331     private static dispatch def DataSchemaNode childByQName(Module container, QName name) {
332         return container.dataNodeChildByQName(name);
333     }
334
335     private static dispatch def DataSchemaNode childByQName(DataSchemaNode container, QName name) {
336         return null;
337     }
338
339     private static def DataSchemaNode dataNodeChildByQName(DataNodeContainer container, QName name) {
340         var ret = container.getDataChildByName(name);
341         if (ret === null) {
342
343             // Find in Choice Cases
344             for (node : container.childNodes) {
345                 if (node instanceof ChoiceCaseNode) {
346                     val caseNode = (node as ChoiceCaseNode);
347                     ret = caseNode.childByQName(name);
348                     if (ret !== null) {
349                         return ret;
350                     }
351                 }
352             }
353         }
354         return ret;
355     }
356
357     private def toUriString(Object object) {
358         if(object === null) return "";
359         return URLEncoder.encode(object.toString,URI_ENCODING_CHAR_SET)        
360     }
361     
362     private def InstanceIdWithSchemaNode collectPathArguments(InstanceIdentifierBuilder builder, List<String> strings,
363         DataNodeContainer parentNode, MountInstance mountPoint, boolean returnJustMountPoint) {
364         checkNotNull(strings)
365         if (parentNode === null) {
366             return null;
367         }
368         if (strings.empty) {
369             return new InstanceIdWithSchemaNode(builder.toInstance, parentNode as DataSchemaNode, mountPoint)
370         }
371         
372         val nodeName = strings.head.toNodeName
373         val moduleName = strings.head.toModuleName
374         var DataSchemaNode targetNode = null
375         if (!moduleName.nullOrEmpty) {
376             // if it is mount point
377             if (moduleName == MOUNT_MODULE && nodeName == MOUNT_NODE) {
378                 if (mountPoint !== null) {
379                     throw new ResponseException(BAD_REQUEST, "Restconf supports just one mount point in URI.")
380                 }
381                 
382                 if (mountService === null) {
383                     throw new ResponseException(SERVICE_UNAVAILABLE, "MountService was not found. " 
384                         + "Finding behind mount points does not work."
385                     )
386                 }
387                 
388                 val partialPath = builder.toInstance;
389                 val mount = mountService.getMountPoint(partialPath)
390                 if (mount === null) {
391                     LOG.debug("Instance identifier to missing mount point: {}", partialPath)
392                     throw new ResponseException(BAD_REQUEST, "Mount point does not exist.")
393                 }
394                 
395                 val mountPointSchema = mount.schemaContext;
396                 if (mountPointSchema === null) {
397                     throw new ResponseException(BAD_REQUEST, "Mount point does not contain any schema with modules.")
398                 }
399                 
400                 if (returnJustMountPoint) {
401                     return new InstanceIdWithSchemaNode(InstanceIdentifier.builder().toInstance, mountPointSchema, mount)
402                 }
403                 
404                 if (strings.size == 1) { // any data node is not behind mount point
405                     return new InstanceIdWithSchemaNode(InstanceIdentifier.builder().toInstance, mountPointSchema, mount)
406                 }
407                 
408                 val moduleNameBehindMountPoint = strings.get(1).toModuleName()
409                 if (moduleNameBehindMountPoint === null) {
410                     throw new ResponseException(BAD_REQUEST,
411                         "First node after mount point in URI has to be in format \"moduleName:nodeName\"")
412                 }
413                 
414                 val moduleBehindMountPoint = mountPointSchema.getLatestModule(moduleNameBehindMountPoint)
415                 if (moduleBehindMountPoint === null) {
416                     throw new ResponseException(BAD_REQUEST,
417                         "URI has bad format. \"" + moduleName + "\" module does not exist in mount point.")
418                 }
419                 
420                 return collectPathArguments(InstanceIdentifier.builder(), strings.subList(1, strings.size),
421                     moduleBehindMountPoint, mount, returnJustMountPoint);
422             }
423             
424             var Module module = null;
425             if (mountPoint === null) {
426                 module = globalSchema.getLatestModule(moduleName)
427                 if (module === null) {
428                     throw new ResponseException(BAD_REQUEST,
429                         "URI has bad format. \"" + moduleName + "\" module does not exist.")
430                 }
431             } else {
432                 module = mountPoint.schemaContext?.getLatestModule(moduleName)
433                 if (module === null) {
434                     throw new ResponseException(BAD_REQUEST,
435                         "URI has bad format. \"" + moduleName + "\" module does not exist in mount point.")
436                 }
437             }
438             targetNode = parentNode.findInstanceDataChildByNameAndNamespace(nodeName, module.namespace)
439             if (targetNode === null) {
440                 throw new ResponseException(BAD_REQUEST, "URI has bad format. Possible reasons:\n" + 
441                     "1. \"" + strings.head + "\" was not found in parent data node.\n" + 
442                     "2. \"" + strings.head + "\" is behind mount point. Then it should be in format \"/" + MOUNT + "/" + strings.head + "\".")
443             }
444         } else { // string without module name
445             val potentialSchemaNodes = parentNode.findInstanceDataChildrenByName(nodeName)
446             if (potentialSchemaNodes.size > 1) {
447                 val StringBuilder namespacesOfPotentialModules = new StringBuilder;
448                 for (potentialNodeSchema : potentialSchemaNodes) {
449                     namespacesOfPotentialModules.append("   ").append(potentialNodeSchema.QName.namespace.toString).append("\n")
450                 }
451                 throw new ResponseException(BAD_REQUEST, "URI has bad format. Node \"" + nodeName + "\" is added as augment from more than one module. " 
452                         + "Therefore the node must have module name and it has to be in format \"moduleName:nodeName\"."
453                         + "\nThe node is added as augment from modules with namespaces:\n" + namespacesOfPotentialModules)
454             }
455             targetNode = potentialSchemaNodes.head
456             if (targetNode === null) {
457                 throw new ResponseException(BAD_REQUEST, "URI has bad format. \"" + nodeName + "\" was not found in parent data node.\n")
458             }
459         }
460         
461         if (!targetNode.isListOrContainer) {
462             throw new ResponseException(BAD_REQUEST,"URI has bad format. Node \"" + strings.head + "\" must be Container or List yang type.")
463         }
464         // Number of consumed elements
465         var consumed = 1;
466         if (targetNode instanceof ListSchemaNode) {
467             val listNode = targetNode as ListSchemaNode;
468             val keysSize = listNode.keyDefinition.size
469
470             // every key has to be filled
471             if ((strings.length - consumed) < keysSize) {
472                 throw new ResponseException(BAD_REQUEST,"Missing key for list \"" + listNode.QName.localName + "\".")
473             }
474             val uriKeyValues = strings.subList(consumed, consumed + keysSize);
475             val keyValues = new HashMap<QName, Object>();
476             var i = 0;
477             for (key : listNode.keyDefinition) {
478                 val uriKeyValue = uriKeyValues.get(i);
479
480                 // key value cannot be NULL
481                 if (uriKeyValue.equals(NULL_VALUE)) {
482                     throw new ResponseException(BAD_REQUEST, "URI has bad format. List \"" + listNode.QName.localName 
483                         + "\" cannot contain \"null\" value as a key."
484                     )
485                 }
486                 keyValues.addKeyValue(listNode.getDataChildByName(key), uriKeyValue, mountPoint);
487                 i = i + 1;
488             }
489             consumed = consumed + i;
490             builder.nodeWithKey(targetNode.QName, keyValues);
491         } else {
492
493             // Only one instance of node is allowed
494             builder.node(targetNode.QName);
495         }
496         if (targetNode instanceof DataNodeContainer) {
497             val remaining = strings.subList(consumed, strings.length);
498             val result = builder.collectPathArguments(remaining, targetNode as DataNodeContainer, mountPoint, returnJustMountPoint);
499             return result
500         }
501
502         return new InstanceIdWithSchemaNode(builder.toInstance, targetNode, mountPoint)
503     }
504
505     def DataSchemaNode findInstanceDataChildByNameAndNamespace(DataNodeContainer container,
506         String name, URI namespace) {
507         Preconditions.checkNotNull(namespace)
508         val potentialSchemaNodes = container.findInstanceDataChildrenByName(name)
509         return potentialSchemaNodes.filter[n|n.QName.namespace == namespace].head
510     }
511     
512     def List<DataSchemaNode> findInstanceDataChildrenByName(DataNodeContainer container, String name) {
513         Preconditions.checkNotNull(container)
514         Preconditions.checkNotNull(name)
515         val instantiatedDataNodeContainers = new ArrayList
516         instantiatedDataNodeContainers.collectInstanceDataNodeContainers(container, name)
517         return instantiatedDataNodeContainers
518     }
519     
520     private def void collectInstanceDataNodeContainers(List<DataSchemaNode> potentialSchemaNodes, DataNodeContainer container,
521         String name) {
522         val nodes = container.childNodes.filter[n|n.QName.localName == name]
523         for (potentialNode : nodes) {
524             if (potentialNode.isInstantiatedDataSchema) {
525                 potentialSchemaNodes.add(potentialNode)
526             }
527         }
528         val allCases = container.childNodes.filter(ChoiceNode).map[cases].flatten
529         for (caze : allCases) {
530             collectInstanceDataNodeContainers(potentialSchemaNodes, caze, name)
531         }
532     }
533     
534     def boolean isInstantiatedDataSchema(DataSchemaNode node) {
535         switch node {
536             LeafSchemaNode: return true
537             LeafListSchemaNode: return true
538             ContainerSchemaNode: return true
539             ListSchemaNode: return true
540             default: return false
541         }
542     }
543     
544     private def void addKeyValue(HashMap<QName, Object> map, DataSchemaNode node, String uriValue, MountInstance mountPoint) {
545         checkNotNull(uriValue);
546         checkArgument(node instanceof LeafSchemaNode);
547         val urlDecoded = URLDecoder.decode(uriValue);
548         val typedef = (node as LeafSchemaNode).type;
549         
550         var decoded = RestCodec.from(typedef, mountPoint)?.deserialize(urlDecoded)
551         var additionalInfo = ""
552         if(decoded === null) {
553             var baseType = RestUtil.resolveBaseTypeFrom(typedef)
554             if(baseType instanceof IdentityrefTypeDefinition) {
555                 decoded = toQName(urlDecoded)
556                 additionalInfo = "For key which is of type identityref it should be in format module_name:identity_name."
557             }
558         }
559         if (decoded === null) {
560             throw new ResponseException(BAD_REQUEST, uriValue + " from URI can't be resolved. "+  additionalInfo )
561         }                
562         
563         map.put(node.QName, decoded);
564     }
565
566     private static def String toModuleName(String str) {
567         checkNotNull(str)
568         if (str.contains(":")) {
569             val args = str.split(":");
570             if (args.size === 2) {
571                 return args.get(0);
572             }
573         }
574         return null;
575     }
576
577     private def String toNodeName(String str) {
578         if (str.contains(":")) {
579             val args = str.split(":");
580             if (args.size === 2) {
581                 return args.get(1);
582             }
583         }
584         return str;
585     }
586
587     private def QName toQName(String name) {
588         val module = name.toModuleName;
589         val node = name.toNodeName;
590         val namespace = FluentIterable.from(globalSchema.modules.sort[o1,o2 | o1.revision.compareTo(o2.revision)])
591             .transform[QName.create(namespace,revision,it.name)].findFirst[module == localName]
592         if (namespace === null) {
593             return null
594         }
595         return QName.create(namespace, node);
596     }
597
598     private def boolean isListOrContainer(DataSchemaNode node) {
599         return ((node instanceof ListSchemaNode) || (node instanceof ContainerSchemaNode))
600     }
601
602     def getRpcDefinition(String name) {
603         val validName = name.toQName
604         if (validName === null) {
605             return null
606         }
607         return qnameToRpc.get(validName)
608     }
609
610     override onGlobalContextUpdated(SchemaContext context) {
611         if (context !== null) {
612             qnameToRpc.clear
613             this.globalSchema = context;
614             for (operation : context.operations) {
615                 val qname = operation.QName;
616                 qnameToRpc.put(qname, operation);
617             }
618         }
619     }
620
621
622     def urlPathArgsDecode(List<String> strings) {
623         val List<String> decodedPathArgs = new ArrayList();
624         for (pathArg : strings) {
625             decodedPathArgs.add(URLDecoder.decode(pathArg, URI_ENCODING_CHAR_SET))
626         }
627         return decodedPathArgs
628     }
629
630     def urlPathArgDecode(String pathArg) {
631         if (pathArg !== null) {
632             return URLDecoder.decode(pathArg, URI_ENCODING_CHAR_SET)
633         }
634         return null
635     }    
636
637 }