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