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 java.io.UnsupportedEncodingException;
12 import java.net.URLDecoder;
13 import java.net.URLEncoder;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collections;
17 import java.util.Comparator;
18 import java.util.HashMap;
19 import java.util.List;
22 import java.util.concurrent.ConcurrentHashMap;
24 import javax.ws.rs.core.Response.Status;
26 import org.opendaylight.controller.sal.core.api.mount.MountInstance;
27 import org.opendaylight.controller.sal.core.api.mount.MountService;
28 import org.opendaylight.controller.sal.rest.api.Draft02;
29 import org.opendaylight.controller.sal.rest.impl.RestUtil;
30 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
31 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
32 import org.opendaylight.yangtools.concepts.Codec;
33 import org.opendaylight.yangtools.yang.common.QName;
34 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder;
36 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
38 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
39 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
40 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
41 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
43 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
45 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.Module;
49 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
50 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
51 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
52 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
53 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
57 import com.google.common.base.Function;
58 import com.google.common.base.Objects;
59 import com.google.common.base.Optional;
60 import com.google.common.base.Preconditions;
61 import com.google.common.base.Predicate;
62 import com.google.common.base.Splitter;
63 import com.google.common.base.Strings;
64 import com.google.common.collect.BiMap;
65 import com.google.common.collect.FluentIterable;
66 import com.google.common.collect.HashBiMap;
67 import com.google.common.collect.Iterables;
68 import com.google.common.collect.Lists;
70 public class ControllerContext implements SchemaContextListener {
71 private final static Logger LOG = LoggerFactory.getLogger( ControllerContext.class );
73 private final static ControllerContext INSTANCE = new ControllerContext();
75 private final static String NULL_VALUE = "null";
77 private final static String MOUNT_MODULE = "yang-ext";
79 private final static String MOUNT_NODE = "mount";
81 public final static String MOUNT = "yang-ext:mount";
83 private final static String URI_ENCODING_CHAR_SET = "ISO-8859-1";
85 private final BiMap<URI, String> uriToModuleName = HashBiMap.<URI, String> create();
87 private final Map<String, URI> moduleNameToUri = uriToModuleName.inverse();
89 private final Map<QName, RpcDefinition> qnameToRpc = new ConcurrentHashMap<>();
91 private volatile SchemaContext globalSchema;
92 private volatile MountService mountService;
94 public void setGlobalSchema( final SchemaContext globalSchema ) {
95 this.globalSchema = globalSchema;
98 public void setMountService( final MountService mountService ) {
99 this.mountService = mountService;
102 private ControllerContext() {
105 public static ControllerContext getInstance() {
106 return ControllerContext.INSTANCE;
109 private void checkPreconditions() {
110 if( globalSchema == null ) {
111 throw new RestconfDocumentedException( Status.SERVICE_UNAVAILABLE );
115 public void setSchemas( final SchemaContext schemas ) {
116 this.onGlobalContextUpdated( schemas );
119 public InstanceIdWithSchemaNode toInstanceIdentifier( final String restconfInstance ) {
120 return this.toIdentifier( restconfInstance, false );
123 public InstanceIdWithSchemaNode toMountPointIdentifier( final String restconfInstance ) {
124 return this.toIdentifier( restconfInstance, true );
127 private InstanceIdWithSchemaNode toIdentifier( final String restconfInstance,
128 final boolean toMountPointIdentifier ) {
129 this.checkPreconditions();
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() ) {
139 String first = pathArgs.iterator().next();
140 final String startModule = ControllerContext.toModuleName( first );
141 if( startModule == null ) {
142 throw new RestconfDocumentedException(
143 "First node in URI has to be in format \"moduleName:nodeName\"",
144 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
147 InstanceIdentifierBuilder builder = InstanceIdentifier.builder();
148 Module latestModule = this.getLatestModule( globalSchema, startModule );
149 InstanceIdWithSchemaNode iiWithSchemaNode = this.collectPathArguments( builder, pathArgs,
150 latestModule, null, toMountPointIdentifier );
152 if( iiWithSchemaNode == null ) {
153 throw new RestconfDocumentedException(
154 "URI has bad format", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
157 return iiWithSchemaNode;
160 private List<String> omitFirstAndLastEmptyString( final List<String> list ) {
161 if( list.isEmpty() ) {
165 String head = list.iterator().next();
166 if( head.isEmpty() ) {
170 if( list.isEmpty() ) {
174 String last = list.get( list.size() - 1 );
175 if( last.isEmpty() ) {
176 list.remove( list.size() - 1 );
182 private Module getLatestModule( final SchemaContext schema, final String moduleName ) {
183 Preconditions.checkArgument( schema != null );
184 Preconditions.checkArgument( moduleName != null && !moduleName.isEmpty() );
186 Predicate<Module> filter = new Predicate<Module>() {
188 public boolean apply( final Module m ) {
189 return Objects.equal( m.getName(), moduleName );
193 Iterable<Module> modules = Iterables.filter( schema.getModules(), filter );
194 return this.filterLatestModule( modules );
197 private Module filterLatestModule( final Iterable<Module> modules ) {
198 Module latestModule = modules.iterator().hasNext() ? modules.iterator().next() : null;
199 for( final Module module : modules ) {
200 if( module.getRevision().after( latestModule.getRevision() ) ) {
201 latestModule = module;
207 public Module findModuleByName( final String moduleName ) {
208 this.checkPreconditions();
209 Preconditions.checkArgument( moduleName != null && !moduleName.isEmpty() );
210 return this.getLatestModule( globalSchema, moduleName );
213 public Module findModuleByName( final MountInstance mountPoint, final String moduleName ) {
214 Preconditions.checkArgument( moduleName != null && mountPoint != null );
216 final SchemaContext mountPointSchema = mountPoint.getSchemaContext();
217 return mountPointSchema == null ? null : this.getLatestModule( mountPointSchema, moduleName );
220 public Module findModuleByNamespace( final URI namespace ) {
221 this.checkPreconditions();
222 Preconditions.checkArgument( namespace != null );
224 final Set<Module> moduleSchemas = globalSchema.findModuleByNamespace( namespace );
225 return moduleSchemas == null ? null : this.filterLatestModule( moduleSchemas );
228 public Module findModuleByNamespace( final MountInstance mountPoint, final URI namespace ) {
229 Preconditions.checkArgument( namespace != null && mountPoint != null );
231 final SchemaContext mountPointSchema = mountPoint.getSchemaContext();
232 Set<Module> moduleSchemas = mountPointSchema == null ? null :
233 mountPointSchema.findModuleByNamespace( namespace );
234 return moduleSchemas == null ? null : this.filterLatestModule( moduleSchemas );
237 public Module findModuleByNameAndRevision( final QName module ) {
238 this.checkPreconditions();
239 Preconditions.checkArgument( module != null && module.getLocalName() != null &&
240 module.getRevision() != null );
242 return globalSchema.findModuleByName( module.getLocalName(), module.getRevision() );
245 public Module findModuleByNameAndRevision( final MountInstance mountPoint, final QName module ) {
246 this.checkPreconditions();
247 Preconditions.checkArgument( module != null && module.getLocalName() != null &&
248 module.getRevision() != null && mountPoint != null );
250 SchemaContext schemaContext = mountPoint.getSchemaContext();
251 return schemaContext == null ? null :
252 schemaContext.findModuleByName( module.getLocalName(), module.getRevision() );
255 public DataNodeContainer getDataNodeContainerFor( final InstanceIdentifier path ) {
256 this.checkPreconditions();
258 final List<PathArgument> elements = path.getPath();
259 PathArgument head = elements.iterator().next();
260 final QName startQName = head.getNodeType();
261 final Module initialModule = globalSchema.findModuleByNamespaceAndRevision(
262 startQName.getNamespace(), startQName.getRevision() );
263 DataNodeContainer node = initialModule;
264 for( final PathArgument element : elements ) {
265 QName _nodeType = element.getNodeType();
266 final DataSchemaNode potentialNode = ControllerContext.childByQName( node, _nodeType );
267 if( potentialNode == null || !this.isListOrContainer( potentialNode ) ) {
270 node = (DataNodeContainer) potentialNode;
276 public String toFullRestconfIdentifier( final InstanceIdentifier path ) {
277 this.checkPreconditions();
279 final List<PathArgument> elements = path.getPath();
280 final StringBuilder builder = new StringBuilder();
281 PathArgument head = elements.iterator().next();
282 final QName startQName = head.getNodeType();
283 final Module initialModule = globalSchema.findModuleByNamespaceAndRevision(
284 startQName.getNamespace(), startQName.getRevision() );
285 DataNodeContainer node = initialModule;
286 for( final PathArgument element : elements ) {
287 QName _nodeType = element.getNodeType();
288 final DataSchemaNode potentialNode = ControllerContext.childByQName( node, _nodeType );
289 if( !this.isListOrContainer( potentialNode ) ) {
292 node = ((DataNodeContainer) potentialNode);
293 builder.append( this.convertToRestconfIdentifier( element, node ) );
296 return builder.toString();
299 public String findModuleNameByNamespace( final URI namespace ) {
300 this.checkPreconditions();
302 String moduleName = this.uriToModuleName.get( namespace );
303 if( moduleName == null ) {
304 final Module module = this.findModuleByNamespace( namespace );
305 if( module != null ) {
306 moduleName = module.getName();
307 this.uriToModuleName.put( namespace, moduleName );
314 public String findModuleNameByNamespace( final MountInstance mountPoint, final URI namespace ) {
315 final Module module = this.findModuleByNamespace( mountPoint, namespace );
316 return module == null ? null : module.getName();
319 public URI findNamespaceByModuleName( final String moduleName ) {
320 URI namespace = this.moduleNameToUri.get( moduleName );
321 if( namespace == null ) {
322 Module module = this.findModuleByName( moduleName );
323 if( module != null ) {
324 URI _namespace = module.getNamespace();
325 namespace = _namespace;
326 this.uriToModuleName.put( namespace, moduleName );
332 public URI findNamespaceByModuleName( final MountInstance mountPoint, final String moduleName ) {
333 final Module module = this.findModuleByName( mountPoint, moduleName );
334 return module == null ? null : module.getNamespace();
337 public Set<Module> getAllModules( final MountInstance mountPoint ) {
338 this.checkPreconditions();
340 SchemaContext schemaContext = mountPoint == null ? null : mountPoint.getSchemaContext();
341 return schemaContext == null ? null : schemaContext.getModules();
344 public Set<Module> getAllModules() {
345 this.checkPreconditions();
346 return globalSchema.getModules();
349 public CharSequence toRestconfIdentifier( final QName qname ) {
350 this.checkPreconditions();
352 String module = this.uriToModuleName.get( qname.getNamespace() );
353 if( module == null ) {
354 final Module moduleSchema = globalSchema.findModuleByNamespaceAndRevision(
355 qname.getNamespace(), qname.getRevision() );
356 if( moduleSchema == null ) {
360 this.uriToModuleName.put( qname.getNamespace(), moduleSchema.getName() );
361 module = moduleSchema.getName();
364 StringBuilder builder = new StringBuilder();
365 builder.append( module );
366 builder.append( ":" );
367 builder.append( qname.getLocalName() );
368 return builder.toString();
371 public CharSequence toRestconfIdentifier( final MountInstance mountPoint, final QName qname ) {
372 if( mountPoint == null ) {
376 SchemaContext schemaContext = mountPoint.getSchemaContext();
378 final Module moduleSchema = schemaContext.findModuleByNamespaceAndRevision(
379 qname.getNamespace(), qname.getRevision() );
380 if( moduleSchema == null ) {
384 StringBuilder builder = new StringBuilder();
385 builder.append( moduleSchema.getName() );
386 builder.append( ":" );
387 builder.append( qname.getLocalName() );
388 return builder.toString();
391 public Module getRestconfModule() {
392 return findModuleByNameAndRevision( Draft02.RestConfModule.IETF_RESTCONF_QNAME );
395 public DataSchemaNode getRestconfModuleErrorsSchemaNode() {
396 Module restconfModule = getRestconfModule();
397 if( restconfModule == null ) {
401 Set<GroupingDefinition> groupings = restconfModule.getGroupings();
403 final Predicate<GroupingDefinition> filter = new Predicate<GroupingDefinition>() {
405 public boolean apply(final GroupingDefinition g) {
406 return Objects.equal(g.getQName().getLocalName(),
407 Draft02.RestConfModule.ERRORS_GROUPING_SCHEMA_NODE);
411 Iterable<GroupingDefinition> filteredGroups = Iterables.filter(groupings, filter);
413 final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
415 List<DataSchemaNode> instanceDataChildrenByName =
416 this.findInstanceDataChildrenByName(restconfGrouping,
417 Draft02.RestConfModule.ERRORS_CONTAINER_SCHEMA_NODE);
418 return Iterables.getFirst(instanceDataChildrenByName, null);
421 public DataSchemaNode getRestconfModuleRestConfSchemaNode( final Module inRestconfModule,
422 final String schemaNodeName ) {
423 Module restconfModule = inRestconfModule;
424 if( restconfModule == null ) {
425 restconfModule = getRestconfModule();
428 if( restconfModule == null ) {
432 Set<GroupingDefinition> groupings = restconfModule.getGroupings();
434 final Predicate<GroupingDefinition> filter = new Predicate<GroupingDefinition>() {
436 public boolean apply(final GroupingDefinition g) {
437 return Objects.equal(g.getQName().getLocalName(),
438 Draft02.RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE);
442 Iterable<GroupingDefinition> filteredGroups = Iterables.filter(groupings, filter);
444 final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
446 List<DataSchemaNode> instanceDataChildrenByName =
447 this.findInstanceDataChildrenByName(restconfGrouping,
448 Draft02.RestConfModule.RESTCONF_CONTAINER_SCHEMA_NODE);
449 final DataSchemaNode restconfContainer = Iterables.getFirst(instanceDataChildrenByName, null);
451 if (Objects.equal(schemaNodeName, Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE)) {
452 List<DataSchemaNode> instances =
453 this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
454 Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE);
455 return Iterables.getFirst(instances, null);
457 else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE)) {
458 List<DataSchemaNode> instances =
459 this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
460 Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
461 return Iterables.getFirst(instances, null);
463 else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE)) {
464 List<DataSchemaNode> instances =
465 this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
466 Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
467 final DataSchemaNode modules = Iterables.getFirst(instances, null);
468 instances = this.findInstanceDataChildrenByName(((DataNodeContainer) modules),
469 Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
470 return Iterables.getFirst(instances, null);
472 else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE)) {
473 List<DataSchemaNode> instances =
474 this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
475 Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
476 return Iterables.getFirst(instances, null);
478 else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE)) {
479 List<DataSchemaNode> instances =
480 this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
481 Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
482 final DataSchemaNode modules = Iterables.getFirst(instances, null);
483 instances = this.findInstanceDataChildrenByName(((DataNodeContainer) modules),
484 Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
485 return Iterables.getFirst(instances, null);
487 else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE)) {
488 List<DataSchemaNode> instances =
489 this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
490 Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
491 return Iterables.getFirst(instances, null);
497 private static DataSchemaNode childByQName( final ChoiceNode container, final QName name ) {
498 for( final ChoiceCaseNode caze : container.getCases() ) {
499 final DataSchemaNode ret = ControllerContext.childByQName( caze, name );
508 private static DataSchemaNode childByQName( final ChoiceCaseNode container, final QName name ) {
509 return container.getDataChildByName( name );
512 private static DataSchemaNode childByQName( final ContainerSchemaNode container, final QName name ) {
513 return ControllerContext.dataNodeChildByQName( container, name );
516 private static DataSchemaNode childByQName( final ListSchemaNode container, final QName name ) {
517 return ControllerContext.dataNodeChildByQName( container, name );
520 private static DataSchemaNode childByQName( final Module container, final QName name ) {
521 return ControllerContext.dataNodeChildByQName( container, name );
524 private static DataSchemaNode childByQName( final DataSchemaNode container, final QName name ) {
528 private static DataSchemaNode dataNodeChildByQName( final DataNodeContainer container, final QName name ) {
529 DataSchemaNode ret = container.getDataChildByName( name );
531 for( final DataSchemaNode node : container.getChildNodes() ) {
532 if( (node instanceof ChoiceCaseNode) ) {
533 final ChoiceCaseNode caseNode = ((ChoiceCaseNode) node);
534 DataSchemaNode childByQName = ControllerContext.childByQName( caseNode, name );
535 if( childByQName != null ) {
544 private String toUriString( final Object object ) throws UnsupportedEncodingException {
545 return object == null ? "" :
546 URLEncoder.encode( object.toString(), ControllerContext.URI_ENCODING_CHAR_SET );
549 private InstanceIdWithSchemaNode collectPathArguments( final InstanceIdentifierBuilder builder,
550 final List<String> strings, final DataNodeContainer parentNode, final MountInstance mountPoint,
551 final boolean returnJustMountPoint ) {
552 Preconditions.<List<String>> checkNotNull( strings );
554 if( parentNode == null ) {
558 if( strings.isEmpty() ) {
559 return new InstanceIdWithSchemaNode( builder.toInstance(),
560 ((DataSchemaNode) parentNode), mountPoint );
563 String head = strings.iterator().next();
564 final String nodeName = this.toNodeName( head );
565 final String moduleName = ControllerContext.toModuleName( head );
567 DataSchemaNode targetNode = null;
568 if( !Strings.isNullOrEmpty( moduleName ) ) {
569 if( Objects.equal( moduleName, ControllerContext.MOUNT_MODULE ) &&
570 Objects.equal( nodeName, ControllerContext.MOUNT_NODE ) ) {
571 if( mountPoint != null ) {
572 throw new RestconfDocumentedException(
573 "Restconf supports just one mount point in URI.",
574 ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED );
577 if( mountService == null ) {
578 throw new RestconfDocumentedException(
579 "MountService was not found. Finding behind mount points does not work.",
580 ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED );
583 final InstanceIdentifier partialPath = builder.toInstance();
584 final MountInstance mount = mountService.getMountPoint( partialPath );
585 if( mount == null ) {
586 LOG.debug( "Instance identifier to missing mount point: {}", partialPath );
587 throw new RestconfDocumentedException(
588 "Mount point does not exist.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
591 final SchemaContext mountPointSchema = mount.getSchemaContext();
592 if( mountPointSchema == null ) {
593 throw new RestconfDocumentedException(
594 "Mount point does not contain any schema with modules.",
595 ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT );
598 if( returnJustMountPoint ) {
599 InstanceIdentifier instance = InstanceIdentifier.builder().toInstance();
600 return new InstanceIdWithSchemaNode( instance, mountPointSchema, mount );
603 if( strings.size() == 1 ) {
604 InstanceIdentifier instance = InstanceIdentifier.builder().toInstance();
605 return new InstanceIdWithSchemaNode( instance, mountPointSchema, mount );
608 final String moduleNameBehindMountPoint = toModuleName( strings.get( 1 ) );
609 if( moduleNameBehindMountPoint == null ) {
610 throw new RestconfDocumentedException(
611 "First node after mount point in URI has to be in format \"moduleName:nodeName\"",
612 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
615 final Module moduleBehindMountPoint = this.getLatestModule( mountPointSchema,
616 moduleNameBehindMountPoint );
617 if( moduleBehindMountPoint == null ) {
618 throw new RestconfDocumentedException(
619 "\"" +moduleName + "\" module does not exist in mount point.",
620 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
623 List<String> subList = strings.subList( 1, strings.size() );
624 return this.collectPathArguments( InstanceIdentifier.builder(), subList, moduleBehindMountPoint,
625 mount, returnJustMountPoint );
628 Module module = null;
629 if( mountPoint == null ) {
630 module = this.getLatestModule( globalSchema, moduleName );
631 if( module == null ) {
632 throw new RestconfDocumentedException(
633 "\"" + moduleName + "\" module does not exist.",
634 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
638 SchemaContext schemaContext = mountPoint.getSchemaContext();
639 module = schemaContext == null ? null :
640 this.getLatestModule( schemaContext, moduleName );
641 if( module == null ) {
642 throw new RestconfDocumentedException(
643 "\"" + moduleName + "\" module does not exist in mount point.",
644 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
648 targetNode = this.findInstanceDataChildByNameAndNamespace(
649 parentNode, nodeName, module.getNamespace() );
650 if( targetNode == null ) {
651 throw new RestconfDocumentedException(
652 "URI has bad format. Possible reasons:\n" +
653 " 1. \"" + head + "\" was not found in parent data node.\n" +
654 " 2. \"" + head + "\" is behind mount point. Then it should be in format \"/" +
655 MOUNT + "/" + head + "\".", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
658 final List<DataSchemaNode> potentialSchemaNodes =
659 this.findInstanceDataChildrenByName( parentNode, nodeName );
660 if( potentialSchemaNodes.size() > 1 ) {
661 final StringBuilder strBuilder = new StringBuilder();
662 for( final DataSchemaNode potentialNodeSchema : potentialSchemaNodes ) {
663 strBuilder.append( " " )
664 .append( potentialNodeSchema.getQName().getNamespace() )
668 throw new RestconfDocumentedException(
669 "URI has bad format. Node \"" + nodeName +
670 "\" is added as augment from more than one module. " +
671 "Therefore the node must have module name and it has to be in format \"moduleName:nodeName\"." +
672 "\nThe node is added as augment from modules with namespaces:\n" +
673 strBuilder.toString(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
676 if( potentialSchemaNodes.isEmpty() ) {
677 throw new RestconfDocumentedException(
678 "\"" + nodeName + "\" in URI was not found in parent data node",
679 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
682 targetNode = potentialSchemaNodes.iterator().next();
685 if( !this.isListOrContainer( targetNode ) ) {
686 throw new RestconfDocumentedException(
687 "URI has bad format. Node \"" + head + "\" must be Container or List yang type.",
688 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
692 if( (targetNode instanceof ListSchemaNode) ) {
693 final ListSchemaNode listNode = ((ListSchemaNode) targetNode);
694 final int keysSize = listNode.getKeyDefinition().size();
695 if( (strings.size() - consumed) < keysSize ) {
696 throw new RestconfDocumentedException(
697 "Missing key for list \"" + listNode.getQName().getLocalName() + "\".",
698 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
701 final List<String> uriKeyValues = strings.subList( consumed, consumed + keysSize );
702 final HashMap<QName, Object> keyValues = new HashMap<QName, Object>();
704 for( final QName key : listNode.getKeyDefinition() ) {
706 final String uriKeyValue = uriKeyValues.get( i );
707 if( uriKeyValue.equals( NULL_VALUE ) ) {
708 throw new RestconfDocumentedException(
709 "URI has bad format. List \"" + listNode.getQName().getLocalName() +
710 "\" cannot contain \"null\" value as a key.",
711 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
714 this.addKeyValue( keyValues, listNode.getDataChildByName( key ),
715 uriKeyValue, mountPoint );
720 consumed = consumed + i;
721 builder.nodeWithKey( targetNode.getQName(), keyValues );
724 builder.node( targetNode.getQName() );
727 if( (targetNode instanceof DataNodeContainer) ) {
728 final List<String> remaining = strings.subList( consumed, strings.size() );
729 return this.collectPathArguments( builder, remaining,
730 ((DataNodeContainer) targetNode), mountPoint, returnJustMountPoint );
733 return new InstanceIdWithSchemaNode( builder.toInstance(), targetNode, mountPoint );
736 public DataSchemaNode findInstanceDataChildByNameAndNamespace( final DataNodeContainer container,
737 final String name, final URI namespace ) {
738 Preconditions.<URI> checkNotNull( namespace );
740 final List<DataSchemaNode> potentialSchemaNodes = this.findInstanceDataChildrenByName( container, name );
742 Predicate<DataSchemaNode> filter = new Predicate<DataSchemaNode>() {
744 public boolean apply( final DataSchemaNode node ) {
745 return Objects.equal( node.getQName().getNamespace(), namespace );
749 Iterable<DataSchemaNode> result = Iterables.filter( potentialSchemaNodes, filter );
750 return Iterables.getFirst( result, null );
753 public List<DataSchemaNode> findInstanceDataChildrenByName( final DataNodeContainer container,
754 final String name ) {
755 Preconditions.<DataNodeContainer> checkNotNull( container );
756 Preconditions.<String> checkNotNull( name );
758 List<DataSchemaNode> instantiatedDataNodeContainers = new ArrayList<DataSchemaNode>();
759 this.collectInstanceDataNodeContainers( instantiatedDataNodeContainers, container, name );
760 return instantiatedDataNodeContainers;
763 private void collectInstanceDataNodeContainers( final List<DataSchemaNode> potentialSchemaNodes,
764 final DataNodeContainer container, final String name ) {
766 Set<DataSchemaNode> childNodes = container.getChildNodes();
768 Predicate<DataSchemaNode> filter = new Predicate<DataSchemaNode>() {
770 public boolean apply( final DataSchemaNode node ) {
771 return Objects.equal( node.getQName().getLocalName(), name );
775 Iterable<DataSchemaNode> nodes = Iterables.filter( childNodes, filter );
777 // Can't combine this loop with the filter above because the filter is lazily-applied
778 // by Iterables.filter.
779 for( final DataSchemaNode potentialNode : nodes ) {
780 if( this.isInstantiatedDataSchema( potentialNode ) ) {
781 potentialSchemaNodes.add( potentialNode );
785 Iterable<ChoiceNode> choiceNodes = Iterables.<ChoiceNode> filter( container.getChildNodes(),
788 final Function<ChoiceNode, Set<ChoiceCaseNode>> choiceFunction =
789 new Function<ChoiceNode, Set<ChoiceCaseNode>>() {
791 public Set<ChoiceCaseNode> apply( final ChoiceNode node ) {
792 return node.getCases();
796 Iterable<Set<ChoiceCaseNode>> map = Iterables.<ChoiceNode, Set<ChoiceCaseNode>> transform(
797 choiceNodes, choiceFunction );
799 final Iterable<ChoiceCaseNode> allCases = Iterables.<ChoiceCaseNode> concat( map );
800 for( final ChoiceCaseNode caze : allCases ) {
801 this.collectInstanceDataNodeContainers( potentialSchemaNodes, caze, name );
805 public boolean isInstantiatedDataSchema( final DataSchemaNode node ) {
806 return node instanceof LeafSchemaNode || node instanceof LeafListSchemaNode ||
807 node instanceof ContainerSchemaNode || node instanceof ListSchemaNode;
810 private void addKeyValue( final HashMap<QName, Object> map, final DataSchemaNode node,
811 final String uriValue, final MountInstance mountPoint ) {
812 Preconditions.<String> checkNotNull( uriValue );
813 Preconditions.checkArgument( (node instanceof LeafSchemaNode) );
815 final String urlDecoded = urlPathArgDecode( uriValue );
816 final TypeDefinition<? extends Object> typedef = ((LeafSchemaNode) node).getType();
817 Codec<Object, Object> codec = RestCodec.from( typedef, mountPoint );
819 Object decoded = codec == null ? null : codec.deserialize( urlDecoded );
820 String additionalInfo = "";
821 if( decoded == null ) {
822 TypeDefinition<? extends Object> baseType = RestUtil.resolveBaseTypeFrom( typedef );
823 if( (baseType instanceof IdentityrefTypeDefinition) ) {
824 decoded = this.toQName( urlDecoded );
825 additionalInfo = "For key which is of type identityref it should be in format module_name:identity_name.";
829 if( decoded == null ) {
830 throw new RestconfDocumentedException(
831 uriValue + " from URI can't be resolved. " + additionalInfo,
832 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
835 map.put( node.getQName(), decoded );
838 private static String toModuleName( final String str ) {
839 Preconditions.<String> checkNotNull( str );
840 if( str.contains( ":" ) ) {
841 final String[] args = str.split( ":" );
842 if( args.length == 2 ) {
849 private String toNodeName( final String str ) {
850 if( str.contains( ":" ) ) {
851 final String[] args = str.split( ":" );
852 if( args.length == 2 ) {
859 private QName toQName( final String name ) {
860 final String module = toModuleName( name );
861 final String node = this.toNodeName( name );
862 Set<Module> modules = globalSchema.getModules();
864 final Comparator<Module> comparator = new Comparator<Module>() {
866 public int compare( final Module o1, final Module o2 ) {
867 return o1.getRevision().compareTo( o2.getRevision() );
871 List<Module> sorted = new ArrayList<Module>( modules );
872 Collections.<Module> sort( new ArrayList<Module>( modules ), comparator );
874 final Function<Module, QName> transform = new Function<Module, QName>() {
876 public QName apply( final Module m ) {
877 return QName.create( m.getNamespace(), m.getRevision(), m.getName() );
881 final Predicate<QName> findFirst = new Predicate<QName>() {
883 public boolean apply( final QName qn ) {
884 return Objects.equal( module, qn.getLocalName() );
888 Optional<QName> namespace = FluentIterable.from( sorted )
889 .transform( transform )
890 .firstMatch( findFirst );
891 return namespace.isPresent() ? QName.create( namespace.get(), node ) : null;
894 private boolean isListOrContainer( final DataSchemaNode node ) {
895 return node instanceof ListSchemaNode || node instanceof ContainerSchemaNode;
898 public RpcDefinition getRpcDefinition( final String name ) {
899 final QName validName = this.toQName( name );
900 return validName == null ? null : this.qnameToRpc.get( validName );
904 public void onGlobalContextUpdated( final SchemaContext context ) {
905 if( context != null ) {
906 this.qnameToRpc.clear();
907 this.setGlobalSchema( context );
908 Set<RpcDefinition> _operations = context.getOperations();
909 for( final RpcDefinition operation : _operations ) {
911 this.qnameToRpc.put( operation.getQName(), operation );
917 public List<String> urlPathArgsDecode( final List<String> strings ) {
919 List<String> decodedPathArgs = new ArrayList<String>();
920 for( final String pathArg : strings ) {
921 String _decode = URLDecoder.decode( pathArg, URI_ENCODING_CHAR_SET );
922 decodedPathArgs.add( _decode );
924 return decodedPathArgs;
926 catch( UnsupportedEncodingException e ) {
927 throw new RestconfDocumentedException(
928 "Invalid URL path '" + strings + "': " + e.getMessage(),
929 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
933 public String urlPathArgDecode( final String pathArg ) {
934 if( pathArg != null ) {
936 return URLDecoder.decode( pathArg, URI_ENCODING_CHAR_SET );
938 catch( UnsupportedEncodingException e ) {
939 throw new RestconfDocumentedException(
940 "Invalid URL path arg '" + pathArg + "': " + e.getMessage(),
941 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
948 private CharSequence convertToRestconfIdentifier( final PathArgument argument,
949 final DataNodeContainer node ) {
950 if( argument instanceof NodeIdentifier && node instanceof ContainerSchemaNode ) {
951 return convertToRestconfIdentifier( (NodeIdentifier) argument, (ContainerSchemaNode) node );
953 else if( argument instanceof NodeIdentifierWithPredicates && node instanceof ListSchemaNode ) {
954 return convertToRestconfIdentifier( (NodeIdentifierWithPredicates) argument, (ListSchemaNode) node );
956 else if( argument != null && node != null ) {
957 throw new IllegalArgumentException(
958 "Conversion of generic path argument is not supported" );
961 throw new IllegalArgumentException( "Unhandled parameter types: "
962 + Arrays.<Object> asList( argument, node ).toString() );
966 private CharSequence convertToRestconfIdentifier( final NodeIdentifier argument,
967 final ContainerSchemaNode node ) {
968 StringBuilder builder = new StringBuilder();
969 builder.append( "/" );
970 QName nodeType = argument.getNodeType();
971 builder.append( this.toRestconfIdentifier( nodeType ) );
972 return builder.toString();
975 private CharSequence convertToRestconfIdentifier( final NodeIdentifierWithPredicates argument,
976 final ListSchemaNode node ) {
977 QName nodeType = argument.getNodeType();
978 final CharSequence nodeIdentifier = this.toRestconfIdentifier( nodeType );
979 final Map<QName, Object> keyValues = argument.getKeyValues();
981 StringBuilder builder = new StringBuilder();
982 builder.append( "/" );
983 builder.append( nodeIdentifier );
984 builder.append( "/" );
986 List<QName> keyDefinition = node.getKeyDefinition();
987 boolean hasElements = false;
988 for( final QName key : keyDefinition ) {
993 builder.append( "/" );
997 builder.append( this.toUriString( keyValues.get( key ) ) );
998 } catch( UnsupportedEncodingException e ) {
999 LOG.error( "Error parsing URI: " + keyValues.get( key ), e );
1004 return builder.toString();
1007 private static DataSchemaNode childByQName( final Object container, final QName name ) {
1008 if( container instanceof ChoiceCaseNode ) {
1009 return childByQName( (ChoiceCaseNode) container, name );
1011 else if( container instanceof ChoiceNode ) {
1012 return childByQName( (ChoiceNode) container, name );
1014 else if( container instanceof ContainerSchemaNode ) {
1015 return childByQName( (ContainerSchemaNode) container, name );
1017 else if( container instanceof ListSchemaNode ) {
1018 return childByQName( (ListSchemaNode) container, name );
1020 else if( container instanceof DataSchemaNode ) {
1021 return childByQName( (DataSchemaNode) container, name );
1023 else if( container instanceof Module ) {
1024 return childByQName( (Module) container, name );
1027 throw new IllegalArgumentException( "Unhandled parameter types: "
1028 + Arrays.<Object> asList( container, name ).toString() );