2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.sal.restconf.impl;
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;
23 import java.io.UnsupportedEncodingException;
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;
35 import java.util.concurrent.ConcurrentHashMap;
37 import javax.ws.rs.core.Response.Status;
39 import org.opendaylight.controller.sal.core.api.mount.MountInstance;
40 import org.opendaylight.controller.sal.core.api.mount.MountService;
41 import org.opendaylight.controller.sal.rest.api.Draft02;
42 import org.opendaylight.controller.sal.rest.impl.RestUtil;
43 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
44 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
45 import org.opendaylight.yangtools.concepts.Codec;
46 import org.opendaylight.yangtools.yang.common.QName;
47 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
48 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder;
49 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
50 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
51 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
52 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
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.GroupingDefinition;
59 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
60 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
61 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
62 import org.opendaylight.yangtools.yang.model.api.Module;
63 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
64 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
65 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
66 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
67 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
71 public class ControllerContext implements SchemaContextListener {
72 private final static Logger LOG = LoggerFactory.getLogger( ControllerContext.class );
74 private final static ControllerContext INSTANCE = new ControllerContext();
76 private final static String NULL_VALUE = "null";
78 private final static String MOUNT_MODULE = "yang-ext";
80 private final static String MOUNT_NODE = "mount";
82 public final static String MOUNT = "yang-ext:mount";
84 private final static String URI_ENCODING_CHAR_SET = "ISO-8859-1";
86 private final BiMap<URI, String> uriToModuleName = HashBiMap.<URI, String> create();
88 private final Map<String, URI> moduleNameToUri = uriToModuleName.inverse();
90 private final Map<QName, RpcDefinition> qnameToRpc = new ConcurrentHashMap<>();
92 private volatile SchemaContext globalSchema;
93 private volatile MountService mountService;
95 public void setGlobalSchema( final SchemaContext globalSchema ) {
96 this.globalSchema = globalSchema;
99 public void setMountService( final MountService mountService ) {
100 this.mountService = mountService;
103 private ControllerContext() {
106 public static ControllerContext getInstance() {
107 return ControllerContext.INSTANCE;
110 private void checkPreconditions() {
111 if( globalSchema == null ) {
112 throw new RestconfDocumentedException( Status.SERVICE_UNAVAILABLE );
116 public void setSchemas( final SchemaContext schemas ) {
117 this.onGlobalContextUpdated( schemas );
120 public InstanceIdWithSchemaNode toInstanceIdentifier( final String restconfInstance ) {
121 return this.toIdentifier( restconfInstance, false );
124 public InstanceIdWithSchemaNode toMountPointIdentifier( final String restconfInstance ) {
125 return this.toIdentifier( restconfInstance, true );
128 private InstanceIdWithSchemaNode toIdentifier( final String restconfInstance,
129 final boolean toMountPointIdentifier ) {
130 this.checkPreconditions();
132 Iterable<String> split = Splitter.on( "/" ).split( restconfInstance );
133 final ArrayList<String> encodedPathArgs = Lists.<String> newArrayList( split );
134 final List<String> pathArgs = this.urlPathArgsDecode( encodedPathArgs );
135 this.omitFirstAndLastEmptyString( pathArgs );
136 if( pathArgs.isEmpty() ) {
140 String first = pathArgs.iterator().next();
141 final String startModule = ControllerContext.toModuleName( first );
142 if( startModule == null ) {
143 throw new RestconfDocumentedException(
144 "First node in URI has to be in format \"moduleName:nodeName\"",
145 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
148 InstanceIdentifierBuilder builder = InstanceIdentifier.builder();
149 Module latestModule = this.getLatestModule( globalSchema, startModule );
150 InstanceIdWithSchemaNode iiWithSchemaNode = this.collectPathArguments( builder, pathArgs,
151 latestModule, null, toMountPointIdentifier );
153 if( iiWithSchemaNode == null ) {
154 throw new RestconfDocumentedException(
155 "URI has bad format", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
158 return iiWithSchemaNode;
161 private List<String> omitFirstAndLastEmptyString( final List<String> list ) {
162 if( list.isEmpty() ) {
166 String head = list.iterator().next();
167 if( head.isEmpty() ) {
171 if( list.isEmpty() ) {
175 String last = list.get( list.size() - 1 );
176 if( last.isEmpty() ) {
177 list.remove( list.size() - 1 );
183 private Module getLatestModule( final SchemaContext schema, final String moduleName ) {
184 Preconditions.checkArgument( schema != null );
185 Preconditions.checkArgument( moduleName != null && !moduleName.isEmpty() );
187 Predicate<Module> filter = new Predicate<Module>() {
189 public boolean apply( final Module m ) {
190 return Objects.equal( m.getName(), moduleName );
194 Iterable<Module> modules = Iterables.filter( schema.getModules(), filter );
195 return this.filterLatestModule( modules );
198 private Module filterLatestModule( final Iterable<Module> modules ) {
199 Module latestModule = modules.iterator().hasNext() ? modules.iterator().next() : null;
200 for( final Module module : modules ) {
201 if( module.getRevision().after( latestModule.getRevision() ) ) {
202 latestModule = module;
208 public Module findModuleByName( final String moduleName ) {
209 this.checkPreconditions();
210 Preconditions.checkArgument( moduleName != null && !moduleName.isEmpty() );
211 return this.getLatestModule( globalSchema, moduleName );
214 public Module findModuleByName( final MountInstance mountPoint, final String moduleName ) {
215 Preconditions.checkArgument( moduleName != null && mountPoint != null );
217 final SchemaContext mountPointSchema = mountPoint.getSchemaContext();
218 return mountPointSchema == null ? null : this.getLatestModule( mountPointSchema, moduleName );
221 public Module findModuleByNamespace( final URI namespace ) {
222 this.checkPreconditions();
223 Preconditions.checkArgument( namespace != null );
225 final Set<Module> moduleSchemas = globalSchema.findModuleByNamespace( namespace );
226 return moduleSchemas == null ? null : this.filterLatestModule( moduleSchemas );
229 public Module findModuleByNamespace( final MountInstance mountPoint, final URI namespace ) {
230 Preconditions.checkArgument( namespace != null && mountPoint != null );
232 final SchemaContext mountPointSchema = mountPoint.getSchemaContext();
233 Set<Module> moduleSchemas = mountPointSchema == null ? null :
234 mountPointSchema.findModuleByNamespace( namespace );
235 return moduleSchemas == null ? null : this.filterLatestModule( moduleSchemas );
238 public Module findModuleByNameAndRevision( final QName module ) {
239 this.checkPreconditions();
240 Preconditions.checkArgument( module != null && module.getLocalName() != null &&
241 module.getRevision() != null );
243 return globalSchema.findModuleByName( module.getLocalName(), module.getRevision() );
246 public Module findModuleByNameAndRevision( final MountInstance mountPoint, final QName module ) {
247 this.checkPreconditions();
248 Preconditions.checkArgument( module != null && module.getLocalName() != null &&
249 module.getRevision() != null && mountPoint != null );
251 SchemaContext schemaContext = mountPoint.getSchemaContext();
252 return schemaContext == null ? null :
253 schemaContext.findModuleByName( module.getLocalName(), module.getRevision() );
256 public DataNodeContainer getDataNodeContainerFor( final InstanceIdentifier path ) {
257 this.checkPreconditions();
259 final Iterable<PathArgument> elements = path.getPathArguments();
260 PathArgument head = elements.iterator().next();
261 final QName startQName = head.getNodeType();
262 final Module initialModule = globalSchema.findModuleByNamespaceAndRevision(
263 startQName.getNamespace(), startQName.getRevision() );
264 DataNodeContainer node = initialModule;
265 for( final PathArgument element : elements ) {
266 QName _nodeType = element.getNodeType();
267 final DataSchemaNode potentialNode = ControllerContext.childByQName( node, _nodeType );
268 if( potentialNode == null || !this.isListOrContainer( potentialNode ) ) {
271 node = (DataNodeContainer) potentialNode;
277 public String toFullRestconfIdentifier( final InstanceIdentifier path ) {
278 this.checkPreconditions();
280 final Iterable<PathArgument> elements = path.getPathArguments();
281 final StringBuilder builder = new StringBuilder();
282 PathArgument head = elements.iterator().next();
283 final QName startQName = head.getNodeType();
284 final Module initialModule = globalSchema.findModuleByNamespaceAndRevision(
285 startQName.getNamespace(), startQName.getRevision() );
286 DataNodeContainer node = initialModule;
287 for( final PathArgument element : elements ) {
288 QName _nodeType = element.getNodeType();
289 final DataSchemaNode potentialNode = ControllerContext.childByQName( node, _nodeType );
290 if( !this.isListOrContainer( potentialNode ) ) {
293 node = ((DataNodeContainer) potentialNode);
294 builder.append( this.convertToRestconfIdentifier( element, node ) );
297 return builder.toString();
300 public String findModuleNameByNamespace( final URI namespace ) {
301 this.checkPreconditions();
303 String moduleName = this.uriToModuleName.get( namespace );
304 if( moduleName == null ) {
305 final Module module = this.findModuleByNamespace( namespace );
306 if( module != null ) {
307 moduleName = module.getName();
308 this.uriToModuleName.put( namespace, moduleName );
315 public String findModuleNameByNamespace( final MountInstance mountPoint, final URI namespace ) {
316 final Module module = this.findModuleByNamespace( mountPoint, namespace );
317 return module == null ? null : module.getName();
320 public URI findNamespaceByModuleName( final String moduleName ) {
321 URI namespace = this.moduleNameToUri.get( moduleName );
322 if( namespace == null ) {
323 Module module = this.findModuleByName( moduleName );
324 if( module != null ) {
325 URI _namespace = module.getNamespace();
326 namespace = _namespace;
327 this.uriToModuleName.put( namespace, moduleName );
333 public URI findNamespaceByModuleName( final MountInstance mountPoint, final String moduleName ) {
334 final Module module = this.findModuleByName( mountPoint, moduleName );
335 return module == null ? null : module.getNamespace();
338 public Set<Module> getAllModules( final MountInstance mountPoint ) {
339 this.checkPreconditions();
341 SchemaContext schemaContext = mountPoint == null ? null : mountPoint.getSchemaContext();
342 return schemaContext == null ? null : schemaContext.getModules();
345 public Set<Module> getAllModules() {
346 this.checkPreconditions();
347 return globalSchema.getModules();
350 public CharSequence toRestconfIdentifier( final QName qname ) {
351 this.checkPreconditions();
353 String module = this.uriToModuleName.get( qname.getNamespace() );
354 if( module == null ) {
355 final Module moduleSchema = globalSchema.findModuleByNamespaceAndRevision(
356 qname.getNamespace(), qname.getRevision() );
357 if( moduleSchema == null ) {
361 this.uriToModuleName.put( qname.getNamespace(), moduleSchema.getName() );
362 module = moduleSchema.getName();
365 StringBuilder builder = new StringBuilder();
366 builder.append( module );
367 builder.append( ":" );
368 builder.append( qname.getLocalName() );
369 return builder.toString();
372 public CharSequence toRestconfIdentifier( final MountInstance mountPoint, final QName qname ) {
373 if( mountPoint == null ) {
377 SchemaContext schemaContext = mountPoint.getSchemaContext();
379 final Module moduleSchema = schemaContext.findModuleByNamespaceAndRevision(
380 qname.getNamespace(), qname.getRevision() );
381 if( moduleSchema == null ) {
385 StringBuilder builder = new StringBuilder();
386 builder.append( moduleSchema.getName() );
387 builder.append( ":" );
388 builder.append( qname.getLocalName() );
389 return builder.toString();
392 public Module getRestconfModule() {
393 return findModuleByNameAndRevision( Draft02.RestConfModule.IETF_RESTCONF_QNAME );
396 public DataSchemaNode getRestconfModuleErrorsSchemaNode() {
397 Module restconfModule = getRestconfModule();
398 if( restconfModule == null ) {
402 Set<GroupingDefinition> groupings = restconfModule.getGroupings();
404 final Predicate<GroupingDefinition> filter = new Predicate<GroupingDefinition>() {
406 public boolean apply(final GroupingDefinition g) {
407 return Objects.equal(g.getQName().getLocalName(),
408 Draft02.RestConfModule.ERRORS_GROUPING_SCHEMA_NODE);
412 Iterable<GroupingDefinition> filteredGroups = Iterables.filter(groupings, filter);
414 final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
416 List<DataSchemaNode> instanceDataChildrenByName =
417 this.findInstanceDataChildrenByName(restconfGrouping,
418 Draft02.RestConfModule.ERRORS_CONTAINER_SCHEMA_NODE);
419 return Iterables.getFirst(instanceDataChildrenByName, null);
422 public DataSchemaNode getRestconfModuleRestConfSchemaNode( final Module inRestconfModule,
423 final String schemaNodeName ) {
424 Module restconfModule = inRestconfModule;
425 if( restconfModule == null ) {
426 restconfModule = getRestconfModule();
429 if( restconfModule == null ) {
433 Set<GroupingDefinition> groupings = restconfModule.getGroupings();
435 final Predicate<GroupingDefinition> filter = new Predicate<GroupingDefinition>() {
437 public boolean apply(final GroupingDefinition g) {
438 return Objects.equal(g.getQName().getLocalName(),
439 Draft02.RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE);
443 Iterable<GroupingDefinition> filteredGroups = Iterables.filter(groupings, filter);
445 final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
447 List<DataSchemaNode> instanceDataChildrenByName =
448 this.findInstanceDataChildrenByName(restconfGrouping,
449 Draft02.RestConfModule.RESTCONF_CONTAINER_SCHEMA_NODE);
450 final DataSchemaNode restconfContainer = Iterables.getFirst(instanceDataChildrenByName, null);
452 if (Objects.equal(schemaNodeName, Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE)) {
453 List<DataSchemaNode> instances =
454 this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
455 Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE);
456 return Iterables.getFirst(instances, null);
458 else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE)) {
459 List<DataSchemaNode> instances =
460 this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
461 Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
462 return Iterables.getFirst(instances, null);
464 else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE)) {
465 List<DataSchemaNode> instances =
466 this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
467 Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
468 final DataSchemaNode modules = Iterables.getFirst(instances, null);
469 instances = this.findInstanceDataChildrenByName(((DataNodeContainer) modules),
470 Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
471 return Iterables.getFirst(instances, null);
473 else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE)) {
474 List<DataSchemaNode> instances =
475 this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
476 Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
477 return Iterables.getFirst(instances, null);
479 else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE)) {
480 List<DataSchemaNode> instances =
481 this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
482 Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
483 final DataSchemaNode modules = Iterables.getFirst(instances, null);
484 instances = this.findInstanceDataChildrenByName(((DataNodeContainer) modules),
485 Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
486 return Iterables.getFirst(instances, null);
488 else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE)) {
489 List<DataSchemaNode> instances =
490 this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
491 Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
492 return Iterables.getFirst(instances, null);
498 private static DataSchemaNode childByQName( final ChoiceNode container, final QName name ) {
499 for( final ChoiceCaseNode caze : container.getCases() ) {
500 final DataSchemaNode ret = ControllerContext.childByQName( caze, name );
509 private static DataSchemaNode childByQName( final ChoiceCaseNode container, final QName name ) {
510 return container.getDataChildByName( name );
513 private static DataSchemaNode childByQName( final ContainerSchemaNode container, final QName name ) {
514 return ControllerContext.dataNodeChildByQName( container, name );
517 private static DataSchemaNode childByQName( final ListSchemaNode container, final QName name ) {
518 return ControllerContext.dataNodeChildByQName( container, name );
521 private static DataSchemaNode childByQName( final Module container, final QName name ) {
522 return ControllerContext.dataNodeChildByQName( container, name );
525 private static DataSchemaNode childByQName( final DataSchemaNode container, final QName name ) {
529 private static DataSchemaNode dataNodeChildByQName( final DataNodeContainer container, final QName name ) {
530 DataSchemaNode ret = container.getDataChildByName( name );
532 for( final DataSchemaNode node : container.getChildNodes() ) {
533 if( (node instanceof ChoiceCaseNode) ) {
534 final ChoiceCaseNode caseNode = ((ChoiceCaseNode) node);
535 DataSchemaNode childByQName = ControllerContext.childByQName( caseNode, name );
536 if( childByQName != null ) {
545 private String toUriString( final Object object ) throws UnsupportedEncodingException {
546 return object == null ? "" :
547 URLEncoder.encode( object.toString(), ControllerContext.URI_ENCODING_CHAR_SET );
550 private InstanceIdWithSchemaNode collectPathArguments( final InstanceIdentifierBuilder builder,
551 final List<String> strings, final DataNodeContainer parentNode, final MountInstance mountPoint,
552 final boolean returnJustMountPoint ) {
553 Preconditions.<List<String>> checkNotNull( strings );
555 if( parentNode == null ) {
559 if( strings.isEmpty() ) {
560 return new InstanceIdWithSchemaNode( builder.toInstance(),
561 ((DataSchemaNode) parentNode), mountPoint );
564 String head = strings.iterator().next();
565 final String nodeName = this.toNodeName( head );
566 final String moduleName = ControllerContext.toModuleName( head );
568 DataSchemaNode targetNode = null;
569 if( !Strings.isNullOrEmpty( moduleName ) ) {
570 if( Objects.equal( moduleName, ControllerContext.MOUNT_MODULE ) &&
571 Objects.equal( nodeName, ControllerContext.MOUNT_NODE ) ) {
572 if( mountPoint != null ) {
573 throw new RestconfDocumentedException(
574 "Restconf supports just one mount point in URI.",
575 ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED );
578 if( mountService == null ) {
579 throw new RestconfDocumentedException(
580 "MountService was not found. Finding behind mount points does not work.",
581 ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED );
584 final InstanceIdentifier partialPath = builder.toInstance();
585 final MountInstance mount = mountService.getMountPoint( partialPath );
586 if( mount == null ) {
587 LOG.debug( "Instance identifier to missing mount point: {}", partialPath );
588 throw new RestconfDocumentedException(
589 "Mount point does not exist.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
592 final SchemaContext mountPointSchema = mount.getSchemaContext();
593 if( mountPointSchema == null ) {
594 throw new RestconfDocumentedException(
595 "Mount point does not contain any schema with modules.",
596 ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT );
599 if( returnJustMountPoint ) {
600 InstanceIdentifier instance = InstanceIdentifier.builder().toInstance();
601 return new InstanceIdWithSchemaNode( instance, mountPointSchema, mount );
604 if( strings.size() == 1 ) {
605 InstanceIdentifier instance = InstanceIdentifier.builder().toInstance();
606 return new InstanceIdWithSchemaNode( instance, mountPointSchema, mount );
609 final String moduleNameBehindMountPoint = toModuleName( strings.get( 1 ) );
610 if( moduleNameBehindMountPoint == null ) {
611 throw new RestconfDocumentedException(
612 "First node after mount point in URI has to be in format \"moduleName:nodeName\"",
613 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
616 final Module moduleBehindMountPoint = this.getLatestModule( mountPointSchema,
617 moduleNameBehindMountPoint );
618 if( moduleBehindMountPoint == null ) {
619 throw new RestconfDocumentedException(
620 "\"" +moduleName + "\" module does not exist in mount point.",
621 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
624 List<String> subList = strings.subList( 1, strings.size() );
625 return this.collectPathArguments( InstanceIdentifier.builder(), subList, moduleBehindMountPoint,
626 mount, returnJustMountPoint );
629 Module module = null;
630 if( mountPoint == null ) {
631 module = this.getLatestModule( globalSchema, moduleName );
632 if( module == null ) {
633 throw new RestconfDocumentedException(
634 "\"" + moduleName + "\" module does not exist.",
635 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
639 SchemaContext schemaContext = mountPoint.getSchemaContext();
640 module = schemaContext == null ? null :
641 this.getLatestModule( schemaContext, moduleName );
642 if( module == null ) {
643 throw new RestconfDocumentedException(
644 "\"" + moduleName + "\" module does not exist in mount point.",
645 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
649 targetNode = this.findInstanceDataChildByNameAndNamespace(
650 parentNode, nodeName, module.getNamespace() );
651 if( targetNode == null ) {
652 throw new RestconfDocumentedException(
653 "URI has bad format. Possible reasons:\n" +
654 " 1. \"" + head + "\" was not found in parent data node.\n" +
655 " 2. \"" + head + "\" is behind mount point. Then it should be in format \"/" +
656 MOUNT + "/" + head + "\".", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
659 final List<DataSchemaNode> potentialSchemaNodes =
660 this.findInstanceDataChildrenByName( parentNode, nodeName );
661 if( potentialSchemaNodes.size() > 1 ) {
662 final StringBuilder strBuilder = new StringBuilder();
663 for( final DataSchemaNode potentialNodeSchema : potentialSchemaNodes ) {
664 strBuilder.append( " " )
665 .append( potentialNodeSchema.getQName().getNamespace() )
669 throw new RestconfDocumentedException(
670 "URI has bad format. Node \"" + nodeName +
671 "\" is added as augment from more than one module. " +
672 "Therefore the node must have module name and it has to be in format \"moduleName:nodeName\"." +
673 "\nThe node is added as augment from modules with namespaces:\n" +
674 strBuilder.toString(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
677 if( potentialSchemaNodes.isEmpty() ) {
678 throw new RestconfDocumentedException(
679 "\"" + nodeName + "\" in URI was not found in parent data node",
680 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
683 targetNode = potentialSchemaNodes.iterator().next();
686 if( !this.isListOrContainer( targetNode ) ) {
687 throw new RestconfDocumentedException(
688 "URI has bad format. Node \"" + head + "\" must be Container or List yang type.",
689 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
693 if( (targetNode instanceof ListSchemaNode) ) {
694 final ListSchemaNode listNode = ((ListSchemaNode) targetNode);
695 final int keysSize = listNode.getKeyDefinition().size();
696 if( (strings.size() - consumed) < keysSize ) {
697 throw new RestconfDocumentedException(
698 "Missing key for list \"" + listNode.getQName().getLocalName() + "\".",
699 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
702 final List<String> uriKeyValues = strings.subList( consumed, consumed + keysSize );
703 final HashMap<QName, Object> keyValues = new HashMap<QName, Object>();
705 for( final QName key : listNode.getKeyDefinition() ) {
707 final String uriKeyValue = uriKeyValues.get( i );
708 if( uriKeyValue.equals( NULL_VALUE ) ) {
709 throw new RestconfDocumentedException(
710 "URI has bad format. List \"" + listNode.getQName().getLocalName() +
711 "\" cannot contain \"null\" value as a key.",
712 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
715 this.addKeyValue( keyValues, listNode.getDataChildByName( key ),
716 uriKeyValue, mountPoint );
721 consumed = consumed + i;
722 builder.nodeWithKey( targetNode.getQName(), keyValues );
725 builder.node( targetNode.getQName() );
728 if( (targetNode instanceof DataNodeContainer) ) {
729 final List<String> remaining = strings.subList( consumed, strings.size() );
730 return this.collectPathArguments( builder, remaining,
731 ((DataNodeContainer) targetNode), mountPoint, returnJustMountPoint );
734 return new InstanceIdWithSchemaNode( builder.toInstance(), targetNode, mountPoint );
737 public DataSchemaNode findInstanceDataChildByNameAndNamespace( final DataNodeContainer container,
738 final String name, final URI namespace ) {
739 Preconditions.<URI> checkNotNull( namespace );
741 final List<DataSchemaNode> potentialSchemaNodes = this.findInstanceDataChildrenByName( container, name );
743 Predicate<DataSchemaNode> filter = new Predicate<DataSchemaNode>() {
745 public boolean apply( final DataSchemaNode node ) {
746 return Objects.equal( node.getQName().getNamespace(), namespace );
750 Iterable<DataSchemaNode> result = Iterables.filter( potentialSchemaNodes, filter );
751 return Iterables.getFirst( result, null );
754 public List<DataSchemaNode> findInstanceDataChildrenByName( final DataNodeContainer container,
755 final String name ) {
756 Preconditions.<DataNodeContainer> checkNotNull( container );
757 Preconditions.<String> checkNotNull( name );
759 List<DataSchemaNode> instantiatedDataNodeContainers = new ArrayList<DataSchemaNode>();
760 this.collectInstanceDataNodeContainers( instantiatedDataNodeContainers, container, name );
761 return instantiatedDataNodeContainers;
764 private void collectInstanceDataNodeContainers( final List<DataSchemaNode> potentialSchemaNodes,
765 final DataNodeContainer container, final String name ) {
767 Set<DataSchemaNode> childNodes = container.getChildNodes();
769 Predicate<DataSchemaNode> filter = new Predicate<DataSchemaNode>() {
771 public boolean apply( final DataSchemaNode node ) {
772 return Objects.equal( node.getQName().getLocalName(), name );
776 Iterable<DataSchemaNode> nodes = Iterables.filter( childNodes, filter );
778 // Can't combine this loop with the filter above because the filter is lazily-applied
779 // by Iterables.filter.
780 for( final DataSchemaNode potentialNode : nodes ) {
781 if( this.isInstantiatedDataSchema( potentialNode ) ) {
782 potentialSchemaNodes.add( potentialNode );
786 Iterable<ChoiceNode> choiceNodes = Iterables.<ChoiceNode> filter( container.getChildNodes(),
789 final Function<ChoiceNode, Set<ChoiceCaseNode>> choiceFunction =
790 new Function<ChoiceNode, Set<ChoiceCaseNode>>() {
792 public Set<ChoiceCaseNode> apply( final ChoiceNode node ) {
793 return node.getCases();
797 Iterable<Set<ChoiceCaseNode>> map = Iterables.<ChoiceNode, Set<ChoiceCaseNode>> transform(
798 choiceNodes, choiceFunction );
800 final Iterable<ChoiceCaseNode> allCases = Iterables.<ChoiceCaseNode> concat( map );
801 for( final ChoiceCaseNode caze : allCases ) {
802 this.collectInstanceDataNodeContainers( potentialSchemaNodes, caze, name );
806 public boolean isInstantiatedDataSchema( final DataSchemaNode node ) {
807 return node instanceof LeafSchemaNode || node instanceof LeafListSchemaNode ||
808 node instanceof ContainerSchemaNode || node instanceof ListSchemaNode ||
809 node instanceof AnyXmlSchemaNode;
812 private void addKeyValue( final HashMap<QName, Object> map, final DataSchemaNode node,
813 final String uriValue, final MountInstance mountPoint ) {
814 Preconditions.<String> checkNotNull( uriValue );
815 Preconditions.checkArgument( (node instanceof LeafSchemaNode) );
817 final String urlDecoded = urlPathArgDecode( uriValue );
818 final TypeDefinition<? extends Object> typedef = ((LeafSchemaNode) node).getType();
819 Codec<Object, Object> codec = RestCodec.from( typedef, mountPoint );
821 Object decoded = codec == null ? null : codec.deserialize( urlDecoded );
822 String additionalInfo = "";
823 if( decoded == null ) {
824 TypeDefinition<? extends Object> baseType = RestUtil.resolveBaseTypeFrom( typedef );
825 if( (baseType instanceof IdentityrefTypeDefinition) ) {
826 decoded = this.toQName( urlDecoded );
827 additionalInfo = "For key which is of type identityref it should be in format module_name:identity_name.";
831 if( decoded == null ) {
832 throw new RestconfDocumentedException(
833 uriValue + " from URI can't be resolved. " + additionalInfo,
834 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
837 map.put( node.getQName(), decoded );
840 private static String toModuleName( final String str ) {
841 Preconditions.<String> checkNotNull( str );
842 if( str.contains( ":" ) ) {
843 final String[] args = str.split( ":" );
844 if( args.length == 2 ) {
851 private String toNodeName( final String str ) {
852 if( str.contains( ":" ) ) {
853 final String[] args = str.split( ":" );
854 if( args.length == 2 ) {
861 private QName toQName( final String name ) {
862 final String module = toModuleName( name );
863 final String node = this.toNodeName( name );
864 Set<Module> modules = globalSchema.getModules();
866 final Comparator<Module> comparator = new Comparator<Module>() {
868 public int compare( final Module o1, final Module o2 ) {
869 return o1.getRevision().compareTo( o2.getRevision() );
873 List<Module> sorted = new ArrayList<Module>( modules );
874 Collections.<Module> sort( new ArrayList<Module>( modules ), comparator );
876 final Function<Module, QName> transform = new Function<Module, QName>() {
878 public QName apply( final Module m ) {
879 return QName.create( m.getNamespace(), m.getRevision(), m.getName() );
883 final Predicate<QName> findFirst = new Predicate<QName>() {
885 public boolean apply( final QName qn ) {
886 return Objects.equal( module, qn.getLocalName() );
890 Optional<QName> namespace = FluentIterable.from( sorted )
891 .transform( transform )
892 .firstMatch( findFirst );
893 return namespace.isPresent() ? QName.create( namespace.get(), node ) : null;
896 private boolean isListOrContainer( final DataSchemaNode node ) {
897 return node instanceof ListSchemaNode || node instanceof ContainerSchemaNode;
900 public RpcDefinition getRpcDefinition( final String name ) {
901 final QName validName = this.toQName( name );
902 return validName == null ? null : this.qnameToRpc.get( validName );
906 public void onGlobalContextUpdated( final SchemaContext context ) {
907 if( context != null ) {
908 this.qnameToRpc.clear();
909 this.setGlobalSchema( context );
910 Set<RpcDefinition> _operations = context.getOperations();
911 for( final RpcDefinition operation : _operations ) {
913 this.qnameToRpc.put( operation.getQName(), operation );
919 public List<String> urlPathArgsDecode( final List<String> strings ) {
921 List<String> decodedPathArgs = new ArrayList<String>();
922 for( final String pathArg : strings ) {
923 String _decode = URLDecoder.decode( pathArg, URI_ENCODING_CHAR_SET );
924 decodedPathArgs.add( _decode );
926 return decodedPathArgs;
928 catch( UnsupportedEncodingException e ) {
929 throw new RestconfDocumentedException(
930 "Invalid URL path '" + strings + "': " + e.getMessage(),
931 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
935 public String urlPathArgDecode( final String pathArg ) {
936 if( pathArg != null ) {
938 return URLDecoder.decode( pathArg, URI_ENCODING_CHAR_SET );
940 catch( UnsupportedEncodingException e ) {
941 throw new RestconfDocumentedException(
942 "Invalid URL path arg '" + pathArg + "': " + e.getMessage(),
943 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
950 private CharSequence convertToRestconfIdentifier( final PathArgument argument,
951 final DataNodeContainer node ) {
952 if( argument instanceof NodeIdentifier && node instanceof ContainerSchemaNode ) {
953 return convertToRestconfIdentifier( (NodeIdentifier) argument, (ContainerSchemaNode) node );
955 else if( argument instanceof NodeIdentifierWithPredicates && node instanceof ListSchemaNode ) {
956 return convertToRestconfIdentifier( (NodeIdentifierWithPredicates) argument, (ListSchemaNode) node );
958 else if( argument != null && node != null ) {
959 throw new IllegalArgumentException(
960 "Conversion of generic path argument is not supported" );
963 throw new IllegalArgumentException( "Unhandled parameter types: "
964 + Arrays.<Object> asList( argument, node ).toString() );
968 private CharSequence convertToRestconfIdentifier( final NodeIdentifier argument,
969 final ContainerSchemaNode node ) {
970 StringBuilder builder = new StringBuilder();
971 builder.append( "/" );
972 QName nodeType = argument.getNodeType();
973 builder.append( this.toRestconfIdentifier( nodeType ) );
974 return builder.toString();
977 private CharSequence convertToRestconfIdentifier( final NodeIdentifierWithPredicates argument,
978 final ListSchemaNode node ) {
979 QName nodeType = argument.getNodeType();
980 final CharSequence nodeIdentifier = this.toRestconfIdentifier( nodeType );
981 final Map<QName, Object> keyValues = argument.getKeyValues();
983 StringBuilder builder = new StringBuilder();
984 builder.append( "/" );
985 builder.append( nodeIdentifier );
986 builder.append( "/" );
988 List<QName> keyDefinition = node.getKeyDefinition();
989 boolean hasElements = false;
990 for( final QName key : keyDefinition ) {
995 builder.append( "/" );
999 builder.append( this.toUriString( keyValues.get( key ) ) );
1000 } catch( UnsupportedEncodingException e ) {
1001 LOG.error( "Error parsing URI: " + keyValues.get( key ), e );
1006 return builder.toString();
1009 private static DataSchemaNode childByQName( final Object container, final QName name ) {
1010 if( container instanceof ChoiceCaseNode ) {
1011 return childByQName( (ChoiceCaseNode) container, name );
1013 else if( container instanceof ChoiceNode ) {
1014 return childByQName( (ChoiceNode) container, name );
1016 else if( container instanceof ContainerSchemaNode ) {
1017 return childByQName( (ContainerSchemaNode) container, name );
1019 else if( container instanceof ListSchemaNode ) {
1020 return childByQName( (ListSchemaNode) container, name );
1022 else if( container instanceof DataSchemaNode ) {
1023 return childByQName( (DataSchemaNode) container, name );
1025 else if( container instanceof Module ) {
1026 return childByQName( (Module) container, name );
1029 throw new IllegalArgumentException( "Unhandled parameter types: "
1030 + Arrays.<Object> asList( container, name ).toString() );