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