6330c0a479caa5acf2926e5ce9820bc3a435e756
[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.AnyXmlSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
41 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
42 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
44 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
46 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.Module;
50 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
51 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
52 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
53 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
54 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 import com.google.common.base.Function;
59 import com.google.common.base.Objects;
60 import com.google.common.base.Optional;
61 import com.google.common.base.Preconditions;
62 import com.google.common.base.Predicate;
63 import com.google.common.base.Splitter;
64 import com.google.common.base.Strings;
65 import com.google.common.collect.BiMap;
66 import com.google.common.collect.FluentIterable;
67 import com.google.common.collect.HashBiMap;
68 import com.google.common.collect.Iterables;
69 import com.google.common.collect.Lists;
70
71 public class ControllerContext implements SchemaContextListener {
72     private final static Logger LOG = LoggerFactory.getLogger( ControllerContext.class );
73
74     private final static ControllerContext INSTANCE = new ControllerContext();
75
76     private final static String NULL_VALUE = "null";
77
78     private final static String MOUNT_MODULE = "yang-ext";
79
80     private final static String MOUNT_NODE = "mount";
81
82     public final static String MOUNT = "yang-ext:mount";
83
84     private final static String URI_ENCODING_CHAR_SET = "ISO-8859-1";
85
86     private final BiMap<URI, String> uriToModuleName = HashBiMap.<URI, String> create();
87
88     private final Map<String, URI> moduleNameToUri = uriToModuleName.inverse();
89
90     private final Map<QName, RpcDefinition> qnameToRpc = new ConcurrentHashMap<>();
91
92     private volatile SchemaContext globalSchema;
93     private volatile MountService mountService;
94
95     public void setGlobalSchema( final SchemaContext globalSchema ) {
96         this.globalSchema = globalSchema;
97     }
98
99     public void setMountService( final MountService mountService ) {
100         this.mountService = mountService;
101     }
102
103     private ControllerContext() {
104     }
105
106     public static ControllerContext getInstance() {
107         return ControllerContext.INSTANCE;
108     }
109
110     private void checkPreconditions() {
111         if( globalSchema == null ) {
112             throw new RestconfDocumentedException( Status.SERVICE_UNAVAILABLE );
113         }
114     }
115
116     public void setSchemas( final SchemaContext schemas ) {
117         this.onGlobalContextUpdated( schemas );
118     }
119
120     public InstanceIdWithSchemaNode toInstanceIdentifier( final String restconfInstance ) {
121         return this.toIdentifier( restconfInstance, false );
122     }
123
124     public InstanceIdWithSchemaNode toMountPointIdentifier( final String restconfInstance ) {
125         return this.toIdentifier( restconfInstance, true );
126     }
127
128     private InstanceIdWithSchemaNode toIdentifier( final String restconfInstance,
129             final boolean toMountPointIdentifier ) {
130         this.checkPreconditions();
131
132         Iterable<String> split = Splitter.on( "/" ).split( restconfInstance );
133         final ArrayList<String> encodedPathArgs = Lists.<String> newArrayList( split );
134         final List<String> pathArgs = this.urlPathArgsDecode( encodedPathArgs );
135         this.omitFirstAndLastEmptyString( pathArgs );
136         if( pathArgs.isEmpty() ) {
137             return null;
138         }
139
140         String first = pathArgs.iterator().next();
141         final String startModule = ControllerContext.toModuleName( first );
142         if( startModule == null ) {
143             throw new RestconfDocumentedException(
144                     "First node in URI has to be in format \"moduleName:nodeName\"",
145                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
146         }
147
148         InstanceIdentifierBuilder builder = InstanceIdentifier.builder();
149         Module latestModule = this.getLatestModule( globalSchema, startModule );
150         InstanceIdWithSchemaNode iiWithSchemaNode = this.collectPathArguments( builder, pathArgs,
151                 latestModule, null, toMountPointIdentifier );
152
153         if( iiWithSchemaNode == null ) {
154             throw new RestconfDocumentedException(
155                     "URI has bad format", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
156         }
157
158         return iiWithSchemaNode;
159     }
160
161     private List<String> omitFirstAndLastEmptyString( final List<String> list ) {
162         if( list.isEmpty() ) {
163             return list;
164         }
165
166         String head = list.iterator().next();
167         if( head.isEmpty() ) {
168             list.remove( 0 );
169         }
170
171         if( list.isEmpty() ) {
172             return list;
173         }
174
175         String last = list.get( list.size() - 1 );
176         if( last.isEmpty() ) {
177             list.remove( list.size() - 1 );
178         }
179
180         return list;
181     }
182
183     private Module getLatestModule( final SchemaContext schema, final String moduleName ) {
184         Preconditions.checkArgument( schema != null );
185         Preconditions.checkArgument( moduleName != null && !moduleName.isEmpty() );
186
187         Predicate<Module> filter = new Predicate<Module>() {
188             @Override
189             public boolean apply( final Module m ) {
190                 return Objects.equal( m.getName(), moduleName );
191             }
192         };
193
194         Iterable<Module> modules = Iterables.filter( schema.getModules(), filter );
195         return this.filterLatestModule( modules );
196     }
197
198     private Module filterLatestModule( final Iterable<Module> modules ) {
199         Module latestModule = modules.iterator().hasNext() ? modules.iterator().next() : null;
200         for( final Module module : modules ) {
201             if( module.getRevision().after( latestModule.getRevision() ) ) {
202                 latestModule = module;
203             }
204         }
205         return latestModule;
206     }
207
208     public Module findModuleByName( final String moduleName ) {
209         this.checkPreconditions();
210         Preconditions.checkArgument( moduleName != null && !moduleName.isEmpty() );
211         return this.getLatestModule( globalSchema, moduleName );
212     }
213
214     public Module findModuleByName( final MountInstance mountPoint, final String moduleName ) {
215         Preconditions.checkArgument( moduleName != null && mountPoint != null );
216
217         final SchemaContext mountPointSchema = mountPoint.getSchemaContext();
218         return mountPointSchema == null ? null : this.getLatestModule( mountPointSchema, moduleName );
219     }
220
221     public Module findModuleByNamespace( final URI namespace ) {
222         this.checkPreconditions();
223         Preconditions.checkArgument( namespace != null );
224
225         final Set<Module> moduleSchemas = globalSchema.findModuleByNamespace( namespace );
226         return moduleSchemas == null ? null : this.filterLatestModule( moduleSchemas );
227     }
228
229     public Module findModuleByNamespace( final MountInstance mountPoint, final URI namespace ) {
230         Preconditions.checkArgument( namespace != null && mountPoint != null );
231
232         final SchemaContext mountPointSchema = mountPoint.getSchemaContext();
233         Set<Module> moduleSchemas = mountPointSchema == null ? null :
234             mountPointSchema.findModuleByNamespace( namespace );
235         return moduleSchemas == null ? null : this.filterLatestModule( moduleSchemas );
236     }
237
238     public Module findModuleByNameAndRevision( final QName module ) {
239         this.checkPreconditions();
240         Preconditions.checkArgument( module != null && module.getLocalName() != null &&
241                 module.getRevision() != null );
242
243         return globalSchema.findModuleByName( module.getLocalName(), module.getRevision() );
244     }
245
246     public Module findModuleByNameAndRevision( final MountInstance mountPoint, final QName module ) {
247         this.checkPreconditions();
248         Preconditions.checkArgument( module != null && module.getLocalName() != null &&
249                 module.getRevision() != null && mountPoint != null );
250
251         SchemaContext schemaContext = mountPoint.getSchemaContext();
252         return schemaContext == null ? null :
253             schemaContext.findModuleByName( module.getLocalName(), module.getRevision() );
254     }
255
256     public DataNodeContainer getDataNodeContainerFor( final InstanceIdentifier path ) {
257         this.checkPreconditions();
258
259         final List<PathArgument> elements = path.getPath();
260         PathArgument head = elements.iterator().next();
261         final QName startQName = head.getNodeType();
262         final Module initialModule = globalSchema.findModuleByNamespaceAndRevision(
263                 startQName.getNamespace(), startQName.getRevision() );
264         DataNodeContainer node = initialModule;
265         for( final PathArgument element : elements ) {
266             QName _nodeType = element.getNodeType();
267             final DataSchemaNode potentialNode = ControllerContext.childByQName( node, _nodeType );
268             if( potentialNode == null || !this.isListOrContainer( potentialNode ) ) {
269                 return null;
270             }
271             node = (DataNodeContainer) potentialNode;
272         }
273
274         return node;
275     }
276
277     public String toFullRestconfIdentifier( final InstanceIdentifier path ) {
278         this.checkPreconditions();
279
280         final List<PathArgument> elements = path.getPath();
281         final StringBuilder builder = new StringBuilder();
282         PathArgument head = elements.iterator().next();
283         final QName startQName = head.getNodeType();
284         final Module initialModule = globalSchema.findModuleByNamespaceAndRevision(
285                 startQName.getNamespace(), startQName.getRevision() );
286         DataNodeContainer node = initialModule;
287         for( final PathArgument element : elements ) {
288             QName _nodeType = element.getNodeType();
289             final DataSchemaNode potentialNode = ControllerContext.childByQName( node, _nodeType );
290             if( !this.isListOrContainer( potentialNode ) ) {
291                 return null;
292             }
293             node = ((DataNodeContainer) potentialNode);
294             builder.append( this.convertToRestconfIdentifier( element, node ) );
295         }
296
297         return builder.toString();
298     }
299
300     public String findModuleNameByNamespace( final URI namespace ) {
301         this.checkPreconditions();
302
303         String moduleName = this.uriToModuleName.get( namespace );
304         if( moduleName == null ) {
305             final Module module = this.findModuleByNamespace( namespace );
306             if( module != null ) {
307                 moduleName = module.getName();
308                 this.uriToModuleName.put( namespace, moduleName );
309             }
310         }
311
312         return moduleName;
313     }
314
315     public String findModuleNameByNamespace( final MountInstance mountPoint, final URI namespace ) {
316         final Module module = this.findModuleByNamespace( mountPoint, namespace );
317         return module == null ? null : module.getName();
318     }
319
320     public URI findNamespaceByModuleName( final String moduleName ) {
321         URI namespace = this.moduleNameToUri.get( moduleName );
322         if( namespace == null ) {
323             Module module = this.findModuleByName( moduleName );
324             if( module != null ) {
325                 URI _namespace = module.getNamespace();
326                 namespace = _namespace;
327                 this.uriToModuleName.put( namespace, moduleName );
328             }
329         }
330         return namespace;
331     }
332
333     public URI findNamespaceByModuleName( final MountInstance mountPoint, final String moduleName ) {
334         final Module module = this.findModuleByName( mountPoint, moduleName );
335         return module == null ? null : module.getNamespace();
336     }
337
338     public Set<Module> getAllModules( final MountInstance mountPoint ) {
339         this.checkPreconditions();
340
341         SchemaContext schemaContext = mountPoint == null ? null : mountPoint.getSchemaContext();
342         return schemaContext == null ? null : schemaContext.getModules();
343     }
344
345     public Set<Module> getAllModules() {
346         this.checkPreconditions();
347         return globalSchema.getModules();
348     }
349
350     public CharSequence toRestconfIdentifier( final QName qname ) {
351         this.checkPreconditions();
352
353         String module = this.uriToModuleName.get( qname.getNamespace() );
354         if( module == null ) {
355             final Module moduleSchema = globalSchema.findModuleByNamespaceAndRevision(
356                     qname.getNamespace(), qname.getRevision() );
357             if( moduleSchema == null ) {
358                 return null;
359             }
360
361             this.uriToModuleName.put( qname.getNamespace(), moduleSchema.getName() );
362             module = moduleSchema.getName();
363         }
364
365         StringBuilder builder = new StringBuilder();
366         builder.append( module );
367         builder.append( ":" );
368         builder.append( qname.getLocalName() );
369         return builder.toString();
370     }
371
372     public CharSequence toRestconfIdentifier( final MountInstance mountPoint, final QName qname ) {
373         if( mountPoint == null ) {
374             return null;
375         }
376
377         SchemaContext schemaContext = mountPoint.getSchemaContext();
378
379         final Module moduleSchema = schemaContext.findModuleByNamespaceAndRevision(
380                 qname.getNamespace(), qname.getRevision() );
381         if( moduleSchema == null ) {
382             return null;
383         }
384
385         StringBuilder builder = new StringBuilder();
386         builder.append( moduleSchema.getName() );
387         builder.append( ":" );
388         builder.append( qname.getLocalName() );
389         return builder.toString();
390     }
391
392     public Module getRestconfModule() {
393         return findModuleByNameAndRevision( Draft02.RestConfModule.IETF_RESTCONF_QNAME );
394     }
395
396     public DataSchemaNode getRestconfModuleErrorsSchemaNode() {
397         Module restconfModule = getRestconfModule();
398         if( restconfModule == null ) {
399             return null;
400         }
401
402         Set<GroupingDefinition> groupings = restconfModule.getGroupings();
403
404         final Predicate<GroupingDefinition> filter = new Predicate<GroupingDefinition>() {
405             @Override
406             public boolean apply(final GroupingDefinition g) {
407                 return Objects.equal(g.getQName().getLocalName(),
408                         Draft02.RestConfModule.ERRORS_GROUPING_SCHEMA_NODE);
409             }
410         };
411
412         Iterable<GroupingDefinition> filteredGroups = Iterables.filter(groupings, filter);
413
414         final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
415
416         List<DataSchemaNode> instanceDataChildrenByName =
417                 this.findInstanceDataChildrenByName(restconfGrouping,
418                         Draft02.RestConfModule.ERRORS_CONTAINER_SCHEMA_NODE);
419         return Iterables.getFirst(instanceDataChildrenByName, null);
420     }
421
422     public DataSchemaNode getRestconfModuleRestConfSchemaNode( final Module inRestconfModule,
423             final String schemaNodeName ) {
424         Module restconfModule = inRestconfModule;
425         if( restconfModule == null ) {
426             restconfModule = getRestconfModule();
427         }
428
429         if( restconfModule == null ) {
430             return null;
431         }
432
433         Set<GroupingDefinition> groupings = restconfModule.getGroupings();
434
435         final Predicate<GroupingDefinition> filter = new Predicate<GroupingDefinition>() {
436             @Override
437             public boolean apply(final GroupingDefinition g) {
438                 return Objects.equal(g.getQName().getLocalName(),
439                         Draft02.RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE);
440             }
441         };
442
443         Iterable<GroupingDefinition> filteredGroups = Iterables.filter(groupings, filter);
444
445         final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
446
447         List<DataSchemaNode> instanceDataChildrenByName =
448                 this.findInstanceDataChildrenByName(restconfGrouping,
449                         Draft02.RestConfModule.RESTCONF_CONTAINER_SCHEMA_NODE);
450         final DataSchemaNode restconfContainer = Iterables.getFirst(instanceDataChildrenByName, null);
451
452         if (Objects.equal(schemaNodeName, Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE)) {
453             List<DataSchemaNode> instances =
454                     this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
455                             Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE);
456             return Iterables.getFirst(instances, null);
457         }
458         else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE)) {
459             List<DataSchemaNode> instances =
460                     this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
461                             Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
462             return Iterables.getFirst(instances, null);
463         }
464         else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE)) {
465             List<DataSchemaNode> instances =
466                     this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
467                             Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
468             final DataSchemaNode modules = Iterables.getFirst(instances, null);
469             instances = this.findInstanceDataChildrenByName(((DataNodeContainer) modules),
470                     Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
471             return Iterables.getFirst(instances, null);
472         }
473         else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE)) {
474             List<DataSchemaNode> instances =
475                     this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
476                             Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
477             return Iterables.getFirst(instances, null);
478         }
479         else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE)) {
480             List<DataSchemaNode> instances =
481                     this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
482                             Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
483             final DataSchemaNode modules = Iterables.getFirst(instances, null);
484             instances = this.findInstanceDataChildrenByName(((DataNodeContainer) modules),
485                     Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
486             return Iterables.getFirst(instances, null);
487         }
488         else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE)) {
489             List<DataSchemaNode> instances =
490                     this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
491                             Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
492             return Iterables.getFirst(instances, null);
493         }
494
495         return null;
496     }
497
498     private static DataSchemaNode childByQName( final ChoiceNode container, final QName name ) {
499         for( final ChoiceCaseNode caze : container.getCases() ) {
500             final DataSchemaNode ret = ControllerContext.childByQName( caze, name );
501             if( ret != null ) {
502                 return ret;
503             }
504         }
505
506         return null;
507     }
508
509     private static DataSchemaNode childByQName( final ChoiceCaseNode container, final QName name ) {
510         return container.getDataChildByName( name );
511     }
512
513     private static DataSchemaNode childByQName( final ContainerSchemaNode container, final QName name ) {
514         return ControllerContext.dataNodeChildByQName( container, name );
515     }
516
517     private static DataSchemaNode childByQName( final ListSchemaNode container, final QName name ) {
518         return ControllerContext.dataNodeChildByQName( container, name );
519     }
520
521     private static DataSchemaNode childByQName( final Module container, final QName name ) {
522         return ControllerContext.dataNodeChildByQName( container, name );
523     }
524
525     private static DataSchemaNode childByQName( final DataSchemaNode container, final QName name ) {
526         return null;
527     }
528
529     private static DataSchemaNode dataNodeChildByQName( final DataNodeContainer container, final QName name ) {
530         DataSchemaNode ret = container.getDataChildByName( name );
531         if( ret == null ) {
532             for( final DataSchemaNode node : container.getChildNodes() ) {
533                 if( (node instanceof ChoiceCaseNode) ) {
534                     final ChoiceCaseNode caseNode = ((ChoiceCaseNode) node);
535                     DataSchemaNode childByQName = ControllerContext.childByQName( caseNode, name );
536                     if( childByQName != null ) {
537                         return childByQName;
538                     }
539                 }
540             }
541         }
542         return ret;
543     }
544
545     private String toUriString( final Object object ) throws UnsupportedEncodingException {
546         return object == null ? "" :
547             URLEncoder.encode( object.toString(), ControllerContext.URI_ENCODING_CHAR_SET );
548     }
549
550     private InstanceIdWithSchemaNode collectPathArguments( final InstanceIdentifierBuilder builder,
551             final List<String> strings, final DataNodeContainer parentNode, final MountInstance mountPoint,
552             final boolean returnJustMountPoint ) {
553         Preconditions.<List<String>> checkNotNull( strings );
554
555         if( parentNode == null ) {
556             return null;
557         }
558
559         if( strings.isEmpty() ) {
560             return new InstanceIdWithSchemaNode( builder.toInstance(),
561                     ((DataSchemaNode) parentNode), mountPoint );
562         }
563
564         String head = strings.iterator().next();
565         final String nodeName = this.toNodeName( head );
566         final String moduleName = ControllerContext.toModuleName( head );
567
568         DataSchemaNode targetNode = null;
569         if( !Strings.isNullOrEmpty( moduleName ) ) {
570             if( Objects.equal( moduleName, ControllerContext.MOUNT_MODULE ) &&
571                     Objects.equal( nodeName, ControllerContext.MOUNT_NODE ) ) {
572                 if( mountPoint != null ) {
573                     throw new RestconfDocumentedException(
574                             "Restconf supports just one mount point in URI.",
575                             ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED );
576                 }
577
578                 if( mountService == null ) {
579                     throw new RestconfDocumentedException(
580                             "MountService was not found. Finding behind mount points does not work.",
581                             ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED );
582                 }
583
584                 final InstanceIdentifier partialPath = builder.toInstance();
585                 final MountInstance mount = mountService.getMountPoint( partialPath );
586                 if( mount == null ) {
587                     LOG.debug( "Instance identifier to missing mount point: {}", partialPath );
588                     throw new RestconfDocumentedException(
589                             "Mount point does not exist.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
590                 }
591
592                 final SchemaContext mountPointSchema = mount.getSchemaContext();
593                 if( mountPointSchema == null ) {
594                     throw new RestconfDocumentedException(
595                             "Mount point does not contain any schema with modules.",
596                             ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT );
597                 }
598
599                 if( returnJustMountPoint ) {
600                     InstanceIdentifier instance = InstanceIdentifier.builder().toInstance();
601                     return new InstanceIdWithSchemaNode( instance, mountPointSchema, mount );
602                 }
603
604                 if( strings.size() == 1 ) {
605                     InstanceIdentifier instance = InstanceIdentifier.builder().toInstance();
606                     return new InstanceIdWithSchemaNode( instance, mountPointSchema, mount );
607                 }
608
609                 final String moduleNameBehindMountPoint = toModuleName(  strings.get( 1 ) );
610                 if( moduleNameBehindMountPoint == null ) {
611                     throw new RestconfDocumentedException(
612                             "First node after mount point in URI has to be in format \"moduleName:nodeName\"",
613                             ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
614                 }
615
616                 final Module moduleBehindMountPoint = this.getLatestModule( mountPointSchema,
617                         moduleNameBehindMountPoint );
618                 if( moduleBehindMountPoint == null ) {
619                     throw new RestconfDocumentedException(
620                             "\"" +moduleName + "\" module does not exist in mount point.",
621                             ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
622                 }
623
624                 List<String> subList = strings.subList( 1, strings.size() );
625                 return this.collectPathArguments( InstanceIdentifier.builder(), subList, moduleBehindMountPoint,
626                         mount, returnJustMountPoint );
627             }
628
629             Module module = null;
630             if( mountPoint == null ) {
631                 module = this.getLatestModule( globalSchema, moduleName );
632                 if( module == null ) {
633                     throw new RestconfDocumentedException(
634                             "\"" + moduleName + "\" module does not exist.",
635                             ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
636                 }
637             }
638             else {
639                 SchemaContext schemaContext = mountPoint.getSchemaContext();
640                 module = schemaContext == null ? null :
641                     this.getLatestModule( schemaContext, moduleName );
642                 if( module == null ) {
643                     throw new RestconfDocumentedException(
644                             "\"" + moduleName + "\" module does not exist in mount point.",
645                             ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
646                 }
647             }
648
649             targetNode = this.findInstanceDataChildByNameAndNamespace(
650                     parentNode, nodeName, module.getNamespace() );
651             if( targetNode == null ) {
652                 throw new RestconfDocumentedException(
653                         "URI has bad format. Possible reasons:\n" +
654                                 " 1. \"" + head + "\" was not found in parent data node.\n" +
655                                 " 2. \"" + head + "\" is behind mount point. Then it should be in format \"/" +
656                                 MOUNT + "/" + head + "\".", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
657             }
658         } else {
659             final List<DataSchemaNode> potentialSchemaNodes =
660                     this.findInstanceDataChildrenByName( parentNode, nodeName );
661             if( potentialSchemaNodes.size() > 1 ) {
662                 final StringBuilder strBuilder = new StringBuilder();
663                 for( final DataSchemaNode potentialNodeSchema : potentialSchemaNodes ) {
664                     strBuilder.append( "   " )
665                     .append( potentialNodeSchema.getQName().getNamespace() )
666                     .append( "\n" );
667                 }
668
669                 throw new RestconfDocumentedException(
670                         "URI has bad format. Node \"" + nodeName +
671                         "\" is added as augment from more than one module. " +
672                         "Therefore the node must have module name and it has to be in format \"moduleName:nodeName\"." +
673                         "\nThe node is added as augment from modules with namespaces:\n" +
674                         strBuilder.toString(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
675             }
676
677             if( potentialSchemaNodes.isEmpty() ) {
678                 throw new RestconfDocumentedException(
679                         "\"" + nodeName + "\" in URI was not found in parent data node",
680                         ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
681             }
682
683             targetNode = potentialSchemaNodes.iterator().next();
684         }
685
686         if( !this.isListOrContainer( targetNode ) ) {
687             throw new RestconfDocumentedException(
688                     "URI has bad format. Node \"" + head + "\" must be Container or List yang type.",
689                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
690         }
691
692         int consumed = 1;
693         if( (targetNode instanceof ListSchemaNode) ) {
694             final ListSchemaNode listNode = ((ListSchemaNode) targetNode);
695             final int keysSize = listNode.getKeyDefinition().size();
696             if( (strings.size() - consumed) < keysSize ) {
697                 throw new RestconfDocumentedException(
698                         "Missing key for list \"" + listNode.getQName().getLocalName() + "\".",
699                         ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
700             }
701
702             final List<String> uriKeyValues = strings.subList( consumed, consumed + keysSize );
703             final HashMap<QName, Object> keyValues = new HashMap<QName, Object>();
704             int i = 0;
705             for( final QName key : listNode.getKeyDefinition() ) {
706                 {
707                     final String uriKeyValue = uriKeyValues.get( i );
708                     if( uriKeyValue.equals( NULL_VALUE ) ) {
709                         throw new RestconfDocumentedException(
710                                 "URI has bad format. List \"" + listNode.getQName().getLocalName() +
711                                 "\" cannot contain \"null\" value as a key.",
712                                 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
713                     }
714
715                     this.addKeyValue( keyValues, listNode.getDataChildByName( key ),
716                             uriKeyValue, mountPoint );
717                     i++;
718                 }
719             }
720
721             consumed = consumed + i;
722             builder.nodeWithKey( targetNode.getQName(), keyValues );
723         }
724         else {
725             builder.node( targetNode.getQName() );
726         }
727
728         if( (targetNode instanceof DataNodeContainer) ) {
729             final List<String> remaining = strings.subList( consumed, strings.size() );
730             return this.collectPathArguments( builder, remaining,
731                     ((DataNodeContainer) targetNode), mountPoint, returnJustMountPoint );
732         }
733
734         return new InstanceIdWithSchemaNode( builder.toInstance(), targetNode, mountPoint );
735     }
736
737     public DataSchemaNode findInstanceDataChildByNameAndNamespace( final DataNodeContainer container,
738             final String name, final URI namespace ) {
739         Preconditions.<URI> checkNotNull( namespace );
740
741         final List<DataSchemaNode> potentialSchemaNodes = this.findInstanceDataChildrenByName( container, name );
742
743         Predicate<DataSchemaNode> filter = new Predicate<DataSchemaNode>() {
744             @Override
745             public boolean apply( final DataSchemaNode node ) {
746                 return Objects.equal( node.getQName().getNamespace(), namespace );
747             }
748         };
749
750         Iterable<DataSchemaNode> result = Iterables.filter( potentialSchemaNodes, filter );
751         return Iterables.getFirst( result, null );
752     }
753
754     public List<DataSchemaNode> findInstanceDataChildrenByName( final DataNodeContainer container,
755             final String name ) {
756         Preconditions.<DataNodeContainer> checkNotNull( container );
757         Preconditions.<String> checkNotNull( name );
758
759         List<DataSchemaNode> instantiatedDataNodeContainers = new ArrayList<DataSchemaNode>();
760         this.collectInstanceDataNodeContainers( instantiatedDataNodeContainers, container, name );
761         return instantiatedDataNodeContainers;
762     }
763
764     private void collectInstanceDataNodeContainers( final List<DataSchemaNode> potentialSchemaNodes,
765             final DataNodeContainer container, final String name ) {
766
767         Set<DataSchemaNode> childNodes = container.getChildNodes();
768
769         Predicate<DataSchemaNode> filter = new Predicate<DataSchemaNode>() {
770             @Override
771             public boolean apply( final DataSchemaNode node ) {
772                 return Objects.equal( node.getQName().getLocalName(), name );
773             }
774         };
775
776         Iterable<DataSchemaNode> nodes = Iterables.filter( childNodes, filter );
777
778         // Can't combine this loop with the filter above because the filter is lazily-applied
779         // by Iterables.filter.
780         for( final DataSchemaNode potentialNode : nodes ) {
781             if( this.isInstantiatedDataSchema( potentialNode ) ) {
782                 potentialSchemaNodes.add( potentialNode );
783             }
784         }
785
786         Iterable<ChoiceNode> choiceNodes = Iterables.<ChoiceNode> filter( container.getChildNodes(),
787                 ChoiceNode.class );
788
789         final Function<ChoiceNode, Set<ChoiceCaseNode>> choiceFunction =
790                 new Function<ChoiceNode, Set<ChoiceCaseNode>>() {
791             @Override
792             public Set<ChoiceCaseNode> apply( final ChoiceNode node ) {
793                 return node.getCases();
794             }
795         };
796
797         Iterable<Set<ChoiceCaseNode>> map = Iterables.<ChoiceNode, Set<ChoiceCaseNode>> transform(
798                 choiceNodes, choiceFunction );
799
800         final Iterable<ChoiceCaseNode> allCases = Iterables.<ChoiceCaseNode> concat( map );
801         for( final ChoiceCaseNode caze : allCases ) {
802             this.collectInstanceDataNodeContainers( potentialSchemaNodes, caze, name );
803         }
804     }
805
806     public boolean isInstantiatedDataSchema( final DataSchemaNode node ) {
807         return node instanceof LeafSchemaNode || node instanceof LeafListSchemaNode ||
808                node instanceof ContainerSchemaNode || node instanceof ListSchemaNode ||
809                node instanceof AnyXmlSchemaNode;
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 }