Bug 1245: Dropped Binding prefix from Binding Data APIs.
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / restconf / impl / ControllerContext.java
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 java.io.UnsupportedEncodingException;
11 import java.net.URI;
12 import java.net.URLDecoder;
13 import java.net.URLEncoder;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collections;
17 import java.util.Comparator;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.concurrent.ConcurrentHashMap;
23
24 import javax.ws.rs.core.Response.Status;
25
26 import org.opendaylight.controller.sal.core.api.mount.MountInstance;
27 import org.opendaylight.controller.sal.core.api.mount.MountService;
28 import org.opendaylight.controller.sal.rest.api.Draft02;
29 import org.opendaylight.controller.sal.rest.impl.RestUtil;
30 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
31 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
32 import org.opendaylight.yangtools.concepts.Codec;
33 import org.opendaylight.yangtools.yang.common.QName;
34 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder;
36 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
38 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
39 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
40 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
41 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
43 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
45 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.Module;
49 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
50 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
51 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
52 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
53 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 import com.google.common.base.Function;
58 import com.google.common.base.Objects;
59 import com.google.common.base.Optional;
60 import com.google.common.base.Preconditions;
61 import com.google.common.base.Predicate;
62 import com.google.common.base.Splitter;
63 import com.google.common.base.Strings;
64 import com.google.common.collect.BiMap;
65 import com.google.common.collect.FluentIterable;
66 import com.google.common.collect.HashBiMap;
67 import com.google.common.collect.Iterables;
68 import com.google.common.collect.Lists;
69
70 public class ControllerContext implements SchemaContextListener {
71     private final static Logger LOG = LoggerFactory.getLogger( ControllerContext.class );
72
73     private final static ControllerContext INSTANCE = new ControllerContext();
74
75     private final static String NULL_VALUE = "null";
76
77     private final static String MOUNT_MODULE = "yang-ext";
78
79     private final static String MOUNT_NODE = "mount";
80
81     public final static String MOUNT = "yang-ext:mount";
82
83     private final static String URI_ENCODING_CHAR_SET = "ISO-8859-1";
84
85     private final BiMap<URI, String> uriToModuleName = HashBiMap.<URI, String> create();
86
87     private final Map<String, URI> moduleNameToUri = uriToModuleName.inverse();
88
89     private final Map<QName, RpcDefinition> qnameToRpc = new ConcurrentHashMap<>();
90
91     private volatile SchemaContext globalSchema;
92     private volatile MountService mountService;
93
94     public void setGlobalSchema( final SchemaContext globalSchema ) {
95         this.globalSchema = globalSchema;
96     }
97
98     public void setMountService( final MountService mountService ) {
99         this.mountService = mountService;
100     }
101
102     private ControllerContext() {
103     }
104
105     public static ControllerContext getInstance() {
106         return ControllerContext.INSTANCE;
107     }
108
109     private void checkPreconditions() {
110         if( globalSchema == null ) {
111             throw new RestconfDocumentedException( Status.SERVICE_UNAVAILABLE );
112         }
113     }
114
115     public void setSchemas( final SchemaContext schemas ) {
116         this.onGlobalContextUpdated( schemas );
117     }
118
119     public InstanceIdWithSchemaNode toInstanceIdentifier( final String restconfInstance ) {
120         return this.toIdentifier( restconfInstance, false );
121     }
122
123     public InstanceIdWithSchemaNode toMountPointIdentifier( final String restconfInstance ) {
124         return this.toIdentifier( restconfInstance, true );
125     }
126
127     private InstanceIdWithSchemaNode toIdentifier( final String restconfInstance,
128             final boolean toMountPointIdentifier ) {
129         this.checkPreconditions();
130
131         Iterable<String> split = Splitter.on( "/" ).split( restconfInstance );
132         final ArrayList<String> encodedPathArgs = Lists.<String> newArrayList( split );
133         final List<String> pathArgs = this.urlPathArgsDecode( encodedPathArgs );
134         this.omitFirstAndLastEmptyString( pathArgs );
135         if( pathArgs.isEmpty() ) {
136             return null;
137         }
138
139         String first = pathArgs.iterator().next();
140         final String startModule = ControllerContext.toModuleName( first );
141         if( startModule == null ) {
142             throw new RestconfDocumentedException(
143                     "First node in URI has to be in format \"moduleName:nodeName\"",
144                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
145         }
146
147         InstanceIdentifierBuilder builder = InstanceIdentifier.builder();
148         Module latestModule = this.getLatestModule( globalSchema, startModule );
149         InstanceIdWithSchemaNode iiWithSchemaNode = this.collectPathArguments( builder, pathArgs,
150                 latestModule, null, toMountPointIdentifier );
151
152         if( iiWithSchemaNode == null ) {
153             throw new RestconfDocumentedException(
154                     "URI has bad format", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
155         }
156
157         return iiWithSchemaNode;
158     }
159
160     private List<String> omitFirstAndLastEmptyString( final List<String> list ) {
161         if( list.isEmpty() ) {
162             return list;
163         }
164
165         String head = list.iterator().next();
166         if( head.isEmpty() ) {
167             list.remove( 0 );
168         }
169
170         if( list.isEmpty() ) {
171             return list;
172         }
173
174         String last = list.get( list.size() - 1 );
175         if( last.isEmpty() ) {
176             list.remove( list.size() - 1 );
177         }
178
179         return list;
180     }
181
182     private Module getLatestModule( final SchemaContext schema, final String moduleName ) {
183         Preconditions.checkArgument( schema != null );
184         Preconditions.checkArgument( moduleName != null && !moduleName.isEmpty() );
185
186         Predicate<Module> filter = new Predicate<Module>() {
187             @Override
188             public boolean apply( final Module m ) {
189                 return Objects.equal( m.getName(), moduleName );
190             }
191         };
192
193         Iterable<Module> modules = Iterables.filter( schema.getModules(), filter );
194         return this.filterLatestModule( modules );
195     }
196
197     private Module filterLatestModule( final Iterable<Module> modules ) {
198         Module latestModule = modules.iterator().hasNext() ? modules.iterator().next() : null;
199         for( final Module module : modules ) {
200             if( module.getRevision().after( latestModule.getRevision() ) ) {
201                 latestModule = module;
202             }
203         }
204         return latestModule;
205     }
206
207     public Module findModuleByName( final String moduleName ) {
208         this.checkPreconditions();
209         Preconditions.checkArgument( moduleName != null && !moduleName.isEmpty() );
210         return this.getLatestModule( globalSchema, moduleName );
211     }
212
213     public Module findModuleByName( final MountInstance mountPoint, final String moduleName ) {
214         Preconditions.checkArgument( moduleName != null && mountPoint != null );
215
216         final SchemaContext mountPointSchema = mountPoint.getSchemaContext();
217         return mountPointSchema == null ? null : this.getLatestModule( mountPointSchema, moduleName );
218     }
219
220     public Module findModuleByNamespace( final URI namespace ) {
221         this.checkPreconditions();
222         Preconditions.checkArgument( namespace != null );
223
224         final Set<Module> moduleSchemas = globalSchema.findModuleByNamespace( namespace );
225         return moduleSchemas == null ? null : this.filterLatestModule( moduleSchemas );
226     }
227
228     public Module findModuleByNamespace( final MountInstance mountPoint, final URI namespace ) {
229         Preconditions.checkArgument( namespace != null && mountPoint != null );
230
231         final SchemaContext mountPointSchema = mountPoint.getSchemaContext();
232         Set<Module> moduleSchemas = mountPointSchema == null ? null :
233             mountPointSchema.findModuleByNamespace( namespace );
234         return moduleSchemas == null ? null : this.filterLatestModule( moduleSchemas );
235     }
236
237     public Module findModuleByNameAndRevision( final QName module ) {
238         this.checkPreconditions();
239         Preconditions.checkArgument( module != null && module.getLocalName() != null &&
240                 module.getRevision() != null );
241
242         return globalSchema.findModuleByName( module.getLocalName(), module.getRevision() );
243     }
244
245     public Module findModuleByNameAndRevision( final MountInstance mountPoint, final QName module ) {
246         this.checkPreconditions();
247         Preconditions.checkArgument( module != null && module.getLocalName() != null &&
248                 module.getRevision() != null && mountPoint != null );
249
250         SchemaContext schemaContext = mountPoint.getSchemaContext();
251         return schemaContext == null ? null :
252             schemaContext.findModuleByName( module.getLocalName(), module.getRevision() );
253     }
254
255     public DataNodeContainer getDataNodeContainerFor( final InstanceIdentifier path ) {
256         this.checkPreconditions();
257
258         final List<PathArgument> elements = path.getPath();
259         PathArgument head = elements.iterator().next();
260         final QName startQName = head.getNodeType();
261         final Module initialModule = globalSchema.findModuleByNamespaceAndRevision(
262                 startQName.getNamespace(), startQName.getRevision() );
263         DataNodeContainer node = initialModule;
264         for( final PathArgument element : elements ) {
265             QName _nodeType = element.getNodeType();
266             final DataSchemaNode potentialNode = ControllerContext.childByQName( node, _nodeType );
267             if( potentialNode == null || !this.isListOrContainer( potentialNode ) ) {
268                 return null;
269             }
270             node = (DataNodeContainer) potentialNode;
271         }
272
273         return node;
274     }
275
276     public String toFullRestconfIdentifier( final InstanceIdentifier path ) {
277         this.checkPreconditions();
278
279         final List<PathArgument> elements = path.getPath();
280         final StringBuilder builder = new StringBuilder();
281         PathArgument head = elements.iterator().next();
282         final QName startQName = head.getNodeType();
283         final Module initialModule = globalSchema.findModuleByNamespaceAndRevision(
284                 startQName.getNamespace(), startQName.getRevision() );
285         DataNodeContainer node = initialModule;
286         for( final PathArgument element : elements ) {
287             QName _nodeType = element.getNodeType();
288             final DataSchemaNode potentialNode = ControllerContext.childByQName( node, _nodeType );
289             if( !this.isListOrContainer( potentialNode ) ) {
290                 return null;
291             }
292             node = ((DataNodeContainer) potentialNode);
293             builder.append( this.convertToRestconfIdentifier( element, node ) );
294         }
295
296         return builder.toString();
297     }
298
299     public String findModuleNameByNamespace( final URI namespace ) {
300         this.checkPreconditions();
301
302         String moduleName = this.uriToModuleName.get( namespace );
303         if( moduleName == null ) {
304             final Module module = this.findModuleByNamespace( namespace );
305             if( module != null ) {
306                 moduleName = module.getName();
307                 this.uriToModuleName.put( namespace, moduleName );
308             }
309         }
310
311         return moduleName;
312     }
313
314     public String findModuleNameByNamespace( final MountInstance mountPoint, final URI namespace ) {
315         final Module module = this.findModuleByNamespace( mountPoint, namespace );
316         return module == null ? null : module.getName();
317     }
318
319     public URI findNamespaceByModuleName( final String moduleName ) {
320         URI namespace = this.moduleNameToUri.get( moduleName );
321         if( namespace == null ) {
322             Module module = this.findModuleByName( moduleName );
323             if( module != null ) {
324                 URI _namespace = module.getNamespace();
325                 namespace = _namespace;
326                 this.uriToModuleName.put( namespace, moduleName );
327             }
328         }
329         return namespace;
330     }
331
332     public URI findNamespaceByModuleName( final MountInstance mountPoint, final String moduleName ) {
333         final Module module = this.findModuleByName( mountPoint, moduleName );
334         return module == null ? null : module.getNamespace();
335     }
336
337     public Set<Module> getAllModules( final MountInstance mountPoint ) {
338         this.checkPreconditions();
339
340         SchemaContext schemaContext = mountPoint == null ? null : mountPoint.getSchemaContext();
341         return schemaContext == null ? null : schemaContext.getModules();
342     }
343
344     public Set<Module> getAllModules() {
345         this.checkPreconditions();
346         return globalSchema.getModules();
347     }
348
349     public CharSequence toRestconfIdentifier( final QName qname ) {
350         this.checkPreconditions();
351
352         String module = this.uriToModuleName.get( qname.getNamespace() );
353         if( module == null ) {
354             final Module moduleSchema = globalSchema.findModuleByNamespaceAndRevision(
355                     qname.getNamespace(), qname.getRevision() );
356             if( moduleSchema == null ) {
357                 return null;
358             }
359
360             this.uriToModuleName.put( qname.getNamespace(), moduleSchema.getName() );
361             module = moduleSchema.getName();
362         }
363
364         StringBuilder builder = new StringBuilder();
365         builder.append( module );
366         builder.append( ":" );
367         builder.append( qname.getLocalName() );
368         return builder.toString();
369     }
370
371     public CharSequence toRestconfIdentifier( final MountInstance mountPoint, final QName qname ) {
372         if( mountPoint == null ) {
373             return null;
374         }
375
376         SchemaContext schemaContext = mountPoint.getSchemaContext();
377
378         final Module moduleSchema = schemaContext.findModuleByNamespaceAndRevision(
379                 qname.getNamespace(), qname.getRevision() );
380         if( moduleSchema == null ) {
381             return null;
382         }
383
384         StringBuilder builder = new StringBuilder();
385         builder.append( moduleSchema.getName() );
386         builder.append( ":" );
387         builder.append( qname.getLocalName() );
388         return builder.toString();
389     }
390
391     public Module getRestconfModule() {
392         return findModuleByNameAndRevision( Draft02.RestConfModule.IETF_RESTCONF_QNAME );
393     }
394
395     public DataSchemaNode getRestconfModuleErrorsSchemaNode() {
396         Module restconfModule = getRestconfModule();
397         if( restconfModule == null ) {
398             return null;
399         }
400
401         Set<GroupingDefinition> groupings = restconfModule.getGroupings();
402
403         final Predicate<GroupingDefinition> filter = new Predicate<GroupingDefinition>() {
404             @Override
405             public boolean apply(final GroupingDefinition g) {
406                 return Objects.equal(g.getQName().getLocalName(),
407                         Draft02.RestConfModule.ERRORS_GROUPING_SCHEMA_NODE);
408             }
409         };
410
411         Iterable<GroupingDefinition> filteredGroups = Iterables.filter(groupings, filter);
412
413         final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
414
415         List<DataSchemaNode> instanceDataChildrenByName =
416                 this.findInstanceDataChildrenByName(restconfGrouping,
417                         Draft02.RestConfModule.ERRORS_CONTAINER_SCHEMA_NODE);
418         return Iterables.getFirst(instanceDataChildrenByName, null);
419     }
420
421     public DataSchemaNode getRestconfModuleRestConfSchemaNode( final Module inRestconfModule,
422             final String schemaNodeName ) {
423         Module restconfModule = inRestconfModule;
424         if( restconfModule == null ) {
425             restconfModule = getRestconfModule();
426         }
427
428         if( restconfModule == null ) {
429             return null;
430         }
431
432         Set<GroupingDefinition> groupings = restconfModule.getGroupings();
433
434         final Predicate<GroupingDefinition> filter = new Predicate<GroupingDefinition>() {
435             @Override
436             public boolean apply(final GroupingDefinition g) {
437                 return Objects.equal(g.getQName().getLocalName(),
438                         Draft02.RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE);
439             }
440         };
441
442         Iterable<GroupingDefinition> filteredGroups = Iterables.filter(groupings, filter);
443
444         final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
445
446         List<DataSchemaNode> instanceDataChildrenByName =
447                 this.findInstanceDataChildrenByName(restconfGrouping,
448                         Draft02.RestConfModule.RESTCONF_CONTAINER_SCHEMA_NODE);
449         final DataSchemaNode restconfContainer = Iterables.getFirst(instanceDataChildrenByName, null);
450
451         if (Objects.equal(schemaNodeName, Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE)) {
452             List<DataSchemaNode> instances =
453                     this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
454                             Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE);
455             return Iterables.getFirst(instances, null);
456         }
457         else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE)) {
458             List<DataSchemaNode> instances =
459                     this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
460                             Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
461             return Iterables.getFirst(instances, null);
462         }
463         else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE)) {
464             List<DataSchemaNode> instances =
465                     this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
466                             Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
467             final DataSchemaNode modules = Iterables.getFirst(instances, null);
468             instances = this.findInstanceDataChildrenByName(((DataNodeContainer) modules),
469                     Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
470             return Iterables.getFirst(instances, null);
471         }
472         else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE)) {
473             List<DataSchemaNode> instances =
474                     this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
475                             Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
476             return Iterables.getFirst(instances, null);
477         }
478         else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE)) {
479             List<DataSchemaNode> instances =
480                     this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
481                             Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
482             final DataSchemaNode modules = Iterables.getFirst(instances, null);
483             instances = this.findInstanceDataChildrenByName(((DataNodeContainer) modules),
484                     Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
485             return Iterables.getFirst(instances, null);
486         }
487         else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE)) {
488             List<DataSchemaNode> instances =
489                     this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
490                             Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
491             return Iterables.getFirst(instances, null);
492         }
493
494         return null;
495     }
496
497     private static DataSchemaNode childByQName( final ChoiceNode container, final QName name ) {
498         for( final ChoiceCaseNode caze : container.getCases() ) {
499             final DataSchemaNode ret = ControllerContext.childByQName( caze, name );
500             if( ret != null ) {
501                 return ret;
502             }
503         }
504
505         return null;
506     }
507
508     private static DataSchemaNode childByQName( final ChoiceCaseNode container, final QName name ) {
509         return container.getDataChildByName( name );
510     }
511
512     private static DataSchemaNode childByQName( final ContainerSchemaNode container, final QName name ) {
513         return ControllerContext.dataNodeChildByQName( container, name );
514     }
515
516     private static DataSchemaNode childByQName( final ListSchemaNode container, final QName name ) {
517         return ControllerContext.dataNodeChildByQName( container, name );
518     }
519
520     private static DataSchemaNode childByQName( final Module container, final QName name ) {
521         return ControllerContext.dataNodeChildByQName( container, name );
522     }
523
524     private static DataSchemaNode childByQName( final DataSchemaNode container, final QName name ) {
525         return null;
526     }
527
528     private static DataSchemaNode dataNodeChildByQName( final DataNodeContainer container, final QName name ) {
529         DataSchemaNode ret = container.getDataChildByName( name );
530         if( ret == null ) {
531             for( final DataSchemaNode node : container.getChildNodes() ) {
532                 if( (node instanceof ChoiceCaseNode) ) {
533                     final ChoiceCaseNode caseNode = ((ChoiceCaseNode) node);
534                     DataSchemaNode childByQName = ControllerContext.childByQName( caseNode, name );
535                     if( childByQName != null ) {
536                         return childByQName;
537                     }
538                 }
539             }
540         }
541         return ret;
542     }
543
544     private String toUriString( final Object object ) throws UnsupportedEncodingException {
545         return object == null ? "" :
546             URLEncoder.encode( object.toString(), ControllerContext.URI_ENCODING_CHAR_SET );
547     }
548
549     private InstanceIdWithSchemaNode collectPathArguments( final InstanceIdentifierBuilder builder,
550             final List<String> strings, final DataNodeContainer parentNode, final MountInstance mountPoint,
551             final boolean returnJustMountPoint ) {
552         Preconditions.<List<String>> checkNotNull( strings );
553
554         if( parentNode == null ) {
555             return null;
556         }
557
558         if( strings.isEmpty() ) {
559             return new InstanceIdWithSchemaNode( builder.toInstance(),
560                     ((DataSchemaNode) parentNode), mountPoint );
561         }
562
563         String head = strings.iterator().next();
564         final String nodeName = this.toNodeName( head );
565         final String moduleName = ControllerContext.toModuleName( head );
566
567         DataSchemaNode targetNode = null;
568         if( !Strings.isNullOrEmpty( moduleName ) ) {
569             if( Objects.equal( moduleName, ControllerContext.MOUNT_MODULE ) &&
570                     Objects.equal( nodeName, ControllerContext.MOUNT_NODE ) ) {
571                 if( mountPoint != null ) {
572                     throw new RestconfDocumentedException(
573                             "Restconf supports just one mount point in URI.",
574                             ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED );
575                 }
576
577                 if( mountService == null ) {
578                     throw new RestconfDocumentedException(
579                             "MountService was not found. Finding behind mount points does not work.",
580                             ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED );
581                 }
582
583                 final InstanceIdentifier partialPath = builder.toInstance();
584                 final MountInstance mount = mountService.getMountPoint( partialPath );
585                 if( mount == null ) {
586                     LOG.debug( "Instance identifier to missing mount point: {}", partialPath );
587                     throw new RestconfDocumentedException(
588                             "Mount point does not exist.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
589                 }
590
591                 final SchemaContext mountPointSchema = mount.getSchemaContext();
592                 if( mountPointSchema == null ) {
593                     throw new RestconfDocumentedException(
594                             "Mount point does not contain any schema with modules.",
595                             ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT );
596                 }
597
598                 if( returnJustMountPoint ) {
599                     InstanceIdentifier instance = InstanceIdentifier.builder().toInstance();
600                     return new InstanceIdWithSchemaNode( instance, mountPointSchema, mount );
601                 }
602
603                 if( strings.size() == 1 ) {
604                     InstanceIdentifier instance = InstanceIdentifier.builder().toInstance();
605                     return new InstanceIdWithSchemaNode( instance, mountPointSchema, mount );
606                 }
607
608                 final String moduleNameBehindMountPoint = toModuleName(  strings.get( 1 ) );
609                 if( moduleNameBehindMountPoint == null ) {
610                     throw new RestconfDocumentedException(
611                             "First node after mount point in URI has to be in format \"moduleName:nodeName\"",
612                             ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
613                 }
614
615                 final Module moduleBehindMountPoint = this.getLatestModule( mountPointSchema,
616                         moduleNameBehindMountPoint );
617                 if( moduleBehindMountPoint == null ) {
618                     throw new RestconfDocumentedException(
619                             "\"" +moduleName + "\" module does not exist in mount point.",
620                             ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
621                 }
622
623                 List<String> subList = strings.subList( 1, strings.size() );
624                 return this.collectPathArguments( InstanceIdentifier.builder(), subList, moduleBehindMountPoint,
625                         mount, returnJustMountPoint );
626             }
627
628             Module module = null;
629             if( mountPoint == null ) {
630                 module = this.getLatestModule( globalSchema, moduleName );
631                 if( module == null ) {
632                     throw new RestconfDocumentedException(
633                             "\"" + moduleName + "\" module does not exist.",
634                             ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
635                 }
636             }
637             else {
638                 SchemaContext schemaContext = mountPoint.getSchemaContext();
639                 module = schemaContext == null ? null :
640                     this.getLatestModule( schemaContext, moduleName );
641                 if( module == null ) {
642                     throw new RestconfDocumentedException(
643                             "\"" + moduleName + "\" module does not exist in mount point.",
644                             ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
645                 }
646             }
647
648             targetNode = this.findInstanceDataChildByNameAndNamespace(
649                     parentNode, nodeName, module.getNamespace() );
650             if( targetNode == null ) {
651                 throw new RestconfDocumentedException(
652                         "URI has bad format. Possible reasons:\n" +
653                                 " 1. \"" + head + "\" was not found in parent data node.\n" +
654                                 " 2. \"" + head + "\" is behind mount point. Then it should be in format \"/" +
655                                 MOUNT + "/" + head + "\".", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
656             }
657         } else {
658             final List<DataSchemaNode> potentialSchemaNodes =
659                     this.findInstanceDataChildrenByName( parentNode, nodeName );
660             if( potentialSchemaNodes.size() > 1 ) {
661                 final StringBuilder strBuilder = new StringBuilder();
662                 for( final DataSchemaNode potentialNodeSchema : potentialSchemaNodes ) {
663                     strBuilder.append( "   " )
664                     .append( potentialNodeSchema.getQName().getNamespace() )
665                     .append( "\n" );
666                 }
667
668                 throw new RestconfDocumentedException(
669                         "URI has bad format. Node \"" + nodeName +
670                         "\" is added as augment from more than one module. " +
671                         "Therefore the node must have module name and it has to be in format \"moduleName:nodeName\"." +
672                         "\nThe node is added as augment from modules with namespaces:\n" +
673                         strBuilder.toString(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
674             }
675
676             if( potentialSchemaNodes.isEmpty() ) {
677                 throw new RestconfDocumentedException(
678                         "\"" + nodeName + "\" in URI was not found in parent data node",
679                         ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
680             }
681
682             targetNode = potentialSchemaNodes.iterator().next();
683         }
684
685         if( !this.isListOrContainer( targetNode ) ) {
686             throw new RestconfDocumentedException(
687                     "URI has bad format. Node \"" + head + "\" must be Container or List yang type.",
688                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
689         }
690
691         int consumed = 1;
692         if( (targetNode instanceof ListSchemaNode) ) {
693             final ListSchemaNode listNode = ((ListSchemaNode) targetNode);
694             final int keysSize = listNode.getKeyDefinition().size();
695             if( (strings.size() - consumed) < keysSize ) {
696                 throw new RestconfDocumentedException(
697                         "Missing key for list \"" + listNode.getQName().getLocalName() + "\".",
698                         ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
699             }
700
701             final List<String> uriKeyValues = strings.subList( consumed, consumed + keysSize );
702             final HashMap<QName, Object> keyValues = new HashMap<QName, Object>();
703             int i = 0;
704             for( final QName key : listNode.getKeyDefinition() ) {
705                 {
706                     final String uriKeyValue = uriKeyValues.get( i );
707                     if( uriKeyValue.equals( NULL_VALUE ) ) {
708                         throw new RestconfDocumentedException(
709                                 "URI has bad format. List \"" + listNode.getQName().getLocalName() +
710                                 "\" cannot contain \"null\" value as a key.",
711                                 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
712                     }
713
714                     this.addKeyValue( keyValues, listNode.getDataChildByName( key ),
715                             uriKeyValue, mountPoint );
716                     i++;
717                 }
718             }
719
720             consumed = consumed + i;
721             builder.nodeWithKey( targetNode.getQName(), keyValues );
722         }
723         else {
724             builder.node( targetNode.getQName() );
725         }
726
727         if( (targetNode instanceof DataNodeContainer) ) {
728             final List<String> remaining = strings.subList( consumed, strings.size() );
729             return this.collectPathArguments( builder, remaining,
730                     ((DataNodeContainer) targetNode), mountPoint, returnJustMountPoint );
731         }
732
733         return new InstanceIdWithSchemaNode( builder.toInstance(), targetNode, mountPoint );
734     }
735
736     public DataSchemaNode findInstanceDataChildByNameAndNamespace( final DataNodeContainer container,
737             final String name, final URI namespace ) {
738         Preconditions.<URI> checkNotNull( namespace );
739
740         final List<DataSchemaNode> potentialSchemaNodes = this.findInstanceDataChildrenByName( container, name );
741
742         Predicate<DataSchemaNode> filter = new Predicate<DataSchemaNode>() {
743             @Override
744             public boolean apply( final DataSchemaNode node ) {
745                 return Objects.equal( node.getQName().getNamespace(), namespace );
746             }
747         };
748
749         Iterable<DataSchemaNode> result = Iterables.filter( potentialSchemaNodes, filter );
750         return Iterables.getFirst( result, null );
751     }
752
753     public List<DataSchemaNode> findInstanceDataChildrenByName( final DataNodeContainer container,
754             final String name ) {
755         Preconditions.<DataNodeContainer> checkNotNull( container );
756         Preconditions.<String> checkNotNull( name );
757
758         List<DataSchemaNode> instantiatedDataNodeContainers = new ArrayList<DataSchemaNode>();
759         this.collectInstanceDataNodeContainers( instantiatedDataNodeContainers, container, name );
760         return instantiatedDataNodeContainers;
761     }
762
763     private void collectInstanceDataNodeContainers( final List<DataSchemaNode> potentialSchemaNodes,
764             final DataNodeContainer container, final String name ) {
765
766         Set<DataSchemaNode> childNodes = container.getChildNodes();
767
768         Predicate<DataSchemaNode> filter = new Predicate<DataSchemaNode>() {
769             @Override
770             public boolean apply( final DataSchemaNode node ) {
771                 return Objects.equal( node.getQName().getLocalName(), name );
772             }
773         };
774
775         Iterable<DataSchemaNode> nodes = Iterables.filter( childNodes, filter );
776
777         // Can't combine this loop with the filter above because the filter is lazily-applied
778         // by Iterables.filter.
779         for( final DataSchemaNode potentialNode : nodes ) {
780             if( this.isInstantiatedDataSchema( potentialNode ) ) {
781                 potentialSchemaNodes.add( potentialNode );
782             }
783         }
784
785         Iterable<ChoiceNode> choiceNodes = Iterables.<ChoiceNode> filter( container.getChildNodes(),
786                 ChoiceNode.class );
787
788         final Function<ChoiceNode, Set<ChoiceCaseNode>> choiceFunction =
789                 new Function<ChoiceNode, Set<ChoiceCaseNode>>() {
790             @Override
791             public Set<ChoiceCaseNode> apply( final ChoiceNode node ) {
792                 return node.getCases();
793             }
794         };
795
796         Iterable<Set<ChoiceCaseNode>> map = Iterables.<ChoiceNode, Set<ChoiceCaseNode>> transform(
797                 choiceNodes, choiceFunction );
798
799         final Iterable<ChoiceCaseNode> allCases = Iterables.<ChoiceCaseNode> concat( map );
800         for( final ChoiceCaseNode caze : allCases ) {
801             this.collectInstanceDataNodeContainers( potentialSchemaNodes, caze, name );
802         }
803     }
804
805     public boolean isInstantiatedDataSchema( final DataSchemaNode node ) {
806         return node instanceof LeafSchemaNode || node instanceof LeafListSchemaNode ||
807                 node instanceof ContainerSchemaNode || node instanceof ListSchemaNode;
808     }
809
810     private void addKeyValue( final HashMap<QName, Object> map, final DataSchemaNode node,
811             final String uriValue, final MountInstance mountPoint ) {
812         Preconditions.<String> checkNotNull( uriValue );
813         Preconditions.checkArgument( (node instanceof LeafSchemaNode) );
814
815         final String urlDecoded = urlPathArgDecode( uriValue );
816         final TypeDefinition<? extends Object> typedef = ((LeafSchemaNode) node).getType();
817         Codec<Object, Object> codec = RestCodec.from( typedef, mountPoint );
818
819         Object decoded = codec == null ? null : codec.deserialize( urlDecoded );
820         String additionalInfo = "";
821         if( decoded == null ) {
822             TypeDefinition<? extends Object> baseType = RestUtil.resolveBaseTypeFrom( typedef );
823             if( (baseType instanceof IdentityrefTypeDefinition) ) {
824                 decoded = this.toQName( urlDecoded );
825                 additionalInfo = "For key which is of type identityref it should be in format module_name:identity_name.";
826             }
827         }
828
829         if( decoded == null ) {
830             throw new RestconfDocumentedException(
831                     uriValue + " from URI can't be resolved. " + additionalInfo,
832                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
833         }
834
835         map.put( node.getQName(), decoded );
836     }
837
838     private static String toModuleName( final String str ) {
839         Preconditions.<String> checkNotNull( str );
840         if( str.contains( ":" ) ) {
841             final String[] args = str.split( ":" );
842             if( args.length == 2 ) {
843                 return args[0];
844             }
845         }
846         return null;
847     }
848
849     private String toNodeName( final String str ) {
850         if( str.contains( ":" ) ) {
851             final String[] args = str.split( ":" );
852             if( args.length == 2 ) {
853                 return args[1];
854             }
855         }
856         return str;
857     }
858
859     private QName toQName( final String name ) {
860         final String module = toModuleName( name );
861         final String node = this.toNodeName( name );
862         Set<Module> modules = globalSchema.getModules();
863
864         final Comparator<Module> comparator = new Comparator<Module>() {
865             @Override
866             public int compare( final Module o1, final Module o2 ) {
867                 return o1.getRevision().compareTo( o2.getRevision() );
868             }
869         };
870
871         List<Module> sorted = new ArrayList<Module>( modules );
872         Collections.<Module> sort( new ArrayList<Module>( modules ), comparator );
873
874         final Function<Module, QName> transform = new Function<Module, QName>() {
875             @Override
876             public QName apply( final Module m ) {
877                 return QName.create( m.getNamespace(), m.getRevision(), m.getName() );
878             }
879         };
880
881         final Predicate<QName> findFirst = new Predicate<QName>() {
882             @Override
883             public boolean apply( final QName qn ) {
884                 return Objects.equal( module, qn.getLocalName() );
885             }
886         };
887
888         Optional<QName> namespace = FluentIterable.from( sorted )
889                 .transform( transform )
890                 .firstMatch( findFirst );
891         return namespace.isPresent() ? QName.create( namespace.get(), node ) : null;
892     }
893
894     private boolean isListOrContainer( final DataSchemaNode node ) {
895         return node instanceof ListSchemaNode || node instanceof ContainerSchemaNode;
896     }
897
898     public RpcDefinition getRpcDefinition( final String name ) {
899         final QName validName = this.toQName( name );
900         return validName == null ? null : this.qnameToRpc.get( validName );
901     }
902
903     @Override
904     public void onGlobalContextUpdated( final SchemaContext context ) {
905         if( context != null ) {
906             this.qnameToRpc.clear();
907             this.setGlobalSchema( context );
908             Set<RpcDefinition> _operations = context.getOperations();
909             for( final RpcDefinition operation : _operations ) {
910                 {
911                     this.qnameToRpc.put( operation.getQName(), operation );
912                 }
913             }
914         }
915     }
916
917     public List<String> urlPathArgsDecode( final List<String> strings ) {
918         try {
919             List<String> decodedPathArgs = new ArrayList<String>();
920             for( final String pathArg : strings ) {
921                 String _decode = URLDecoder.decode( pathArg, URI_ENCODING_CHAR_SET );
922                 decodedPathArgs.add( _decode );
923             }
924             return decodedPathArgs;
925         }
926         catch( UnsupportedEncodingException e ) {
927             throw new RestconfDocumentedException(
928                     "Invalid URL path '" + strings + "': " + e.getMessage(),
929                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
930         }
931     }
932
933     public String urlPathArgDecode( final String pathArg ) {
934         if( pathArg != null ) {
935             try {
936                 return URLDecoder.decode( pathArg, URI_ENCODING_CHAR_SET );
937             }
938             catch( UnsupportedEncodingException e ) {
939                 throw new RestconfDocumentedException(
940                         "Invalid URL path arg '" + pathArg + "': " + e.getMessage(),
941                         ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
942             }
943         }
944
945         return null;
946     }
947
948     private CharSequence convertToRestconfIdentifier( final PathArgument argument,
949             final DataNodeContainer node ) {
950         if( argument instanceof NodeIdentifier && node instanceof ContainerSchemaNode ) {
951             return convertToRestconfIdentifier( (NodeIdentifier) argument, (ContainerSchemaNode) node );
952         }
953         else if( argument instanceof NodeIdentifierWithPredicates && node instanceof ListSchemaNode ) {
954             return convertToRestconfIdentifier( (NodeIdentifierWithPredicates) argument, (ListSchemaNode) node );
955         }
956         else if( argument != null && node != null ) {
957             throw new IllegalArgumentException(
958                     "Conversion of generic path argument is not supported" );
959         }
960         else {
961             throw new IllegalArgumentException( "Unhandled parameter types: "
962                     + Arrays.<Object> asList( argument, node ).toString() );
963         }
964     }
965
966     private CharSequence convertToRestconfIdentifier( final NodeIdentifier argument,
967             final ContainerSchemaNode node ) {
968         StringBuilder builder = new StringBuilder();
969         builder.append( "/" );
970         QName nodeType = argument.getNodeType();
971         builder.append( this.toRestconfIdentifier( nodeType ) );
972         return builder.toString();
973     }
974
975     private CharSequence convertToRestconfIdentifier( final NodeIdentifierWithPredicates argument,
976             final ListSchemaNode node ) {
977         QName nodeType = argument.getNodeType();
978         final CharSequence nodeIdentifier = this.toRestconfIdentifier( nodeType );
979         final Map<QName, Object> keyValues = argument.getKeyValues();
980
981         StringBuilder builder = new StringBuilder();
982         builder.append( "/" );
983         builder.append( nodeIdentifier );
984         builder.append( "/" );
985
986         List<QName> keyDefinition = node.getKeyDefinition();
987         boolean hasElements = false;
988         for( final QName key : keyDefinition ) {
989             if( !hasElements ) {
990                 hasElements = true;
991             }
992             else {
993                 builder.append( "/" );
994             }
995
996             try {
997                 builder.append( this.toUriString( keyValues.get( key ) ) );
998             } catch( UnsupportedEncodingException e ) {
999                 LOG.error( "Error parsing URI: " + keyValues.get( key ), e );
1000                 return null;
1001             }
1002         }
1003
1004         return builder.toString();
1005     }
1006
1007     private static DataSchemaNode childByQName( final Object container, final QName name ) {
1008         if( container instanceof ChoiceCaseNode ) {
1009             return childByQName( (ChoiceCaseNode) container, name );
1010         }
1011         else if( container instanceof ChoiceNode ) {
1012             return childByQName( (ChoiceNode) container, name );
1013         }
1014         else if( container instanceof ContainerSchemaNode ) {
1015             return childByQName( (ContainerSchemaNode) container, name );
1016         }
1017         else if( container instanceof ListSchemaNode ) {
1018             return childByQName( (ListSchemaNode) container, name );
1019         }
1020         else if( container instanceof DataSchemaNode ) {
1021             return childByQName( (DataSchemaNode) container, name );
1022         }
1023         else if( container instanceof Module ) {
1024             return childByQName( (Module) container, name );
1025         }
1026         else {
1027             throw new IllegalArgumentException( "Unhandled parameter types: "
1028                     + Arrays.<Object> asList( container, name ).toString() );
1029         }
1030     }
1031 }