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

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.