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.InstanceIdWithSchemaNode;
44 import org.opendaylight.controller.sal.restconf.impl.RestCodec;
45 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
46 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
47 import org.opendaylight.yangtools.concepts.Codec;
48 import org.opendaylight.yangtools.yang.common.QName;
49 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
50 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder;
51 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
52 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
53 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
54 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
55 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
56 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
57 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
58 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
59 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
60 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
61 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
62 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
63 import org.opendaylight.yangtools.yang.model.api.Module;
64 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
65 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
66 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
67 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
68 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
69 import org.slf4j.Logger;
70 import org.slf4j.LoggerFactory;
72 public class ControllerContext implements SchemaContextListener {
73 private final static Logger LOG = LoggerFactory.getLogger( ControllerContext.class );
75 private final static ControllerContext INSTANCE = new ControllerContext();
77 private final static String NULL_VALUE = "null";
79 private final static String MOUNT_MODULE = "yang-ext";
81 private final static String MOUNT_NODE = "mount";
83 public final static String MOUNT = "yang-ext:mount";
85 private final static String URI_ENCODING_CHAR_SET = "ISO-8859-1";
87 private final BiMap<URI, String> uriToModuleName = HashBiMap.<URI, String> create();
89 private final Map<String, URI> moduleNameToUri = uriToModuleName.inverse();
91 private final Map<QName, RpcDefinition> qnameToRpc = new ConcurrentHashMap<>();
93 private volatile SchemaContext globalSchema;
94 private volatile MountService mountService;
96 public void setGlobalSchema( final SchemaContext globalSchema ) {
97 this.globalSchema = globalSchema;
100 public void setMountService( final MountService mountService ) {
101 this.mountService = mountService;
104 private ControllerContext() {
107 public static ControllerContext getInstance() {
108 return ControllerContext.INSTANCE;
111 private void checkPreconditions() {
112 if( globalSchema == null ) {
113 throw new RestconfDocumentedException( Status.SERVICE_UNAVAILABLE );
117 public void setSchemas( final SchemaContext schemas ) {
118 this.onGlobalContextUpdated( schemas );
121 public InstanceIdWithSchemaNode toInstanceIdentifier( final String restconfInstance ) {
122 return this.toIdentifier( restconfInstance, false );
125 public InstanceIdWithSchemaNode toMountPointIdentifier( final String restconfInstance ) {
126 return this.toIdentifier( restconfInstance, true );
129 private InstanceIdWithSchemaNode toIdentifier( final String restconfInstance,
130 final boolean toMountPointIdentifier ) {
131 this.checkPreconditions();
133 Iterable<String> split = Splitter.on( "/" ).split( restconfInstance );
134 final ArrayList<String> encodedPathArgs = Lists.<String> newArrayList( split );
135 final List<String> pathArgs = this.urlPathArgsDecode( encodedPathArgs );
136 this.omitFirstAndLastEmptyString( pathArgs );
137 if( pathArgs.isEmpty() ) {
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 );
149 InstanceIdentifierBuilder builder = InstanceIdentifier.builder();
150 Module latestModule = this.getLatestModule( globalSchema, startModule );
151 InstanceIdWithSchemaNode iiWithSchemaNode = this.collectPathArguments( builder, pathArgs,
152 latestModule, null, toMountPointIdentifier );
154 if( iiWithSchemaNode == null ) {
155 throw new RestconfDocumentedException(
156 "URI has bad format", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
159 return iiWithSchemaNode;
162 private List<String> omitFirstAndLastEmptyString( final List<String> list ) {
163 if( list.isEmpty() ) {
167 String head = list.iterator().next();
168 if( head.isEmpty() ) {
172 if( list.isEmpty() ) {
176 String last = list.get( list.size() - 1 );
177 if( last.isEmpty() ) {
178 list.remove( list.size() - 1 );
184 private Module getLatestModule( final SchemaContext schema, final String moduleName ) {
185 Preconditions.checkArgument( schema != null );
186 Preconditions.checkArgument( moduleName != null && !moduleName.isEmpty() );
188 Predicate<Module> filter = new Predicate<Module>() {
190 public boolean apply( Module m ) {
191 return Objects.equal( m.getName(), moduleName );
195 Iterable<Module> modules = Iterables.filter( schema.getModules(), filter );
196 return this.filterLatestModule( modules );
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;
209 public Module findModuleByName( final String moduleName ) {
210 this.checkPreconditions();
211 Preconditions.checkArgument( moduleName != null && !moduleName.isEmpty() );
212 return this.getLatestModule( globalSchema, moduleName );
215 public Module findModuleByName( final MountInstance mountPoint, final String moduleName ) {
216 Preconditions.checkArgument( moduleName != null && mountPoint != null );
218 final SchemaContext mountPointSchema = mountPoint.getSchemaContext();
219 return mountPointSchema == null ? null : this.getLatestModule( mountPointSchema, moduleName );
222 public Module findModuleByNamespace( final URI namespace ) {
223 this.checkPreconditions();
224 Preconditions.checkArgument( namespace != null );
226 final Set<Module> moduleSchemas = globalSchema.findModuleByNamespace( namespace );
227 return moduleSchemas == null ? null : this.filterLatestModule( moduleSchemas );
230 public Module findModuleByNamespace( final MountInstance mountPoint, final URI namespace ) {
231 Preconditions.checkArgument( namespace != null && mountPoint != null );
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 );
239 public Module findModuleByNameAndRevision( final QName module ) {
240 this.checkPreconditions();
241 Preconditions.checkArgument( module != null && module.getLocalName() != null &&
242 module.getRevision() != null );
244 return globalSchema.findModuleByName( module.getLocalName(), module.getRevision() );
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 );
252 SchemaContext schemaContext = mountPoint.getSchemaContext();
253 return schemaContext == null ? null :
254 schemaContext.findModuleByName( module.getLocalName(), module.getRevision() );
257 public DataNodeContainer getDataNodeContainerFor( final InstanceIdentifier path ) {
258 this.checkPreconditions();
260 final List<PathArgument> elements = path.getPath();
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 ) ) {
272 node = (DataNodeContainer) potentialNode;
278 public String toFullRestconfIdentifier( final InstanceIdentifier path ) {
279 this.checkPreconditions();
281 final List<PathArgument> elements = path.getPath();
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 ) ) {
294 node = ((DataNodeContainer) potentialNode);
295 builder.append( this.convertToRestconfIdentifier( element, node ) );
298 return builder.toString();
301 public String findModuleNameByNamespace( final URI namespace ) {
302 this.checkPreconditions();
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 );
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();
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 );
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();
339 public Set<Module> getAllModules( final MountInstance mountPoint ) {
340 this.checkPreconditions();
342 SchemaContext schemaContext = mountPoint == null ? null : mountPoint.getSchemaContext();
343 return schemaContext == null ? null : schemaContext.getModules();
346 public Set<Module> getAllModules() {
347 this.checkPreconditions();
348 return globalSchema.getModules();
351 public CharSequence toRestconfIdentifier( final QName qname ) {
352 this.checkPreconditions();
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 ) {
362 this.uriToModuleName.put( qname.getNamespace(), moduleSchema.getName() );
363 module = moduleSchema.getName();
366 StringBuilder builder = new StringBuilder();
367 builder.append( module );
368 builder.append( ":" );
369 builder.append( qname.getLocalName() );
370 return builder.toString();
373 public CharSequence toRestconfIdentifier( final MountInstance mountPoint, final QName qname ) {
374 if( mountPoint == null ) {
378 SchemaContext schemaContext = mountPoint.getSchemaContext();
380 final Module moduleSchema = schemaContext.findModuleByNamespaceAndRevision(
381 qname.getNamespace(), qname.getRevision() );
382 if( moduleSchema == null ) {
386 StringBuilder builder = new StringBuilder();
387 builder.append( moduleSchema.getName() );
388 builder.append( ":" );
389 builder.append( qname.getLocalName() );
390 return builder.toString();
393 public Module getRestconfModule() {
394 return findModuleByNameAndRevision( Draft02.RestConfModule.IETF_RESTCONF_QNAME );
397 public DataSchemaNode getRestconfModuleErrorsSchemaNode() {
398 Module restconfModule = getRestconfModule();
399 if( restconfModule == null ) {
403 Set<GroupingDefinition> groupings = restconfModule.getGroupings();
405 final Predicate<GroupingDefinition> filter = new Predicate<GroupingDefinition>() {
407 public boolean apply(final GroupingDefinition g) {
408 return Objects.equal(g.getQName().getLocalName(),
409 Draft02.RestConfModule.ERRORS_GROUPING_SCHEMA_NODE);
413 Iterable<GroupingDefinition> filteredGroups = Iterables.filter(groupings, filter);
415 final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
417 List<DataSchemaNode> instanceDataChildrenByName =
418 this.findInstanceDataChildrenByName(restconfGrouping,
419 Draft02.RestConfModule.ERRORS_CONTAINER_SCHEMA_NODE);
420 return Iterables.getFirst(instanceDataChildrenByName, null);
423 public DataSchemaNode getRestconfModuleRestConfSchemaNode( Module inRestconfModule,
424 String schemaNodeName ) {
425 Module restconfModule = inRestconfModule;
426 if( restconfModule == null ) {
427 restconfModule = getRestconfModule();
430 if( restconfModule == null ) {
434 Set<GroupingDefinition> groupings = restconfModule.getGroupings();
436 final Predicate<GroupingDefinition> filter = new Predicate<GroupingDefinition>() {
438 public boolean apply(final GroupingDefinition g) {
439 return Objects.equal(g.getQName().getLocalName(),
440 Draft02.RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE);
444 Iterable<GroupingDefinition> filteredGroups = Iterables.filter(groupings, filter);
446 final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
448 List<DataSchemaNode> instanceDataChildrenByName =
449 this.findInstanceDataChildrenByName(restconfGrouping,
450 Draft02.RestConfModule.RESTCONF_CONTAINER_SCHEMA_NODE);
451 final DataSchemaNode restconfContainer = Iterables.getFirst(instanceDataChildrenByName, null);
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);
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);
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);
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);
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);
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);
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 );
510 private static DataSchemaNode childByQName( final ChoiceCaseNode container, final QName name ) {
511 return container.getDataChildByName( name );
514 private static DataSchemaNode childByQName( final ContainerSchemaNode container, final QName name ) {
515 return ControllerContext.dataNodeChildByQName( container, name );
518 private static DataSchemaNode childByQName( final ListSchemaNode container, final QName name ) {
519 return ControllerContext.dataNodeChildByQName( container, name );
522 private static DataSchemaNode childByQName( final Module container, final QName name ) {
523 return ControllerContext.dataNodeChildByQName( container, name );
526 private static DataSchemaNode childByQName( final DataSchemaNode container, final QName name ) {
530 private static DataSchemaNode dataNodeChildByQName( final DataNodeContainer container, final QName name ) {
531 DataSchemaNode ret = container.getDataChildByName( name );
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 ) {
546 private String toUriString( final Object object ) throws UnsupportedEncodingException {
547 return object == null ? "" :
548 URLEncoder.encode( object.toString(), ControllerContext.URI_ENCODING_CHAR_SET );
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 );
556 if( parentNode == null ) {
560 if( strings.isEmpty() ) {
561 return new InstanceIdWithSchemaNode( builder.toInstance(),
562 ((DataSchemaNode) parentNode), mountPoint );
565 String head = strings.iterator().next();
566 final String nodeName = this.toNodeName( head );
567 final String moduleName = ControllerContext.toModuleName( head );
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 );
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 );
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 );
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 );
600 if( returnJustMountPoint ) {
601 InstanceIdentifier instance = InstanceIdentifier.builder().toInstance();
602 return new InstanceIdWithSchemaNode( instance, mountPointSchema, mount );
605 if( strings.size() == 1 ) {
606 InstanceIdentifier instance = InstanceIdentifier.builder().toInstance();
607 return new InstanceIdWithSchemaNode( instance, mountPointSchema, mount );
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 );
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 );
625 List<String> subList = strings.subList( 1, strings.size() );
626 return this.collectPathArguments( InstanceIdentifier.builder(), subList, moduleBehindMountPoint,
627 mount, returnJustMountPoint );
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 );
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 );
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 );
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() )
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 );
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 );
684 targetNode = potentialSchemaNodes.iterator().next();
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 );
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 for list \"" + listNode.getQName().getLocalName() + "\".",
700 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
703 final List<String> uriKeyValues = strings.subList( consumed, consumed + keysSize );
704 final HashMap<QName, Object> keyValues = new HashMap<QName, Object>();
706 for( final QName key : listNode.getKeyDefinition() ) {
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 );
716 this.addKeyValue( keyValues, listNode.getDataChildByName( key ),
717 uriKeyValue, mountPoint );
722 consumed = consumed + i;
723 builder.nodeWithKey( targetNode.getQName(), keyValues );
726 builder.node( targetNode.getQName() );
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 );
735 return new InstanceIdWithSchemaNode( builder.toInstance(), targetNode, mountPoint );
738 public DataSchemaNode findInstanceDataChildByNameAndNamespace( final DataNodeContainer container,
739 final String name, final URI namespace ) {
740 Preconditions.<URI> checkNotNull( namespace );
742 final List<DataSchemaNode> potentialSchemaNodes = this.findInstanceDataChildrenByName( container, name );
744 Predicate<DataSchemaNode> filter = new Predicate<DataSchemaNode>() {
746 public boolean apply( DataSchemaNode node ) {
747 return Objects.equal( node.getQName().getNamespace(), namespace );
751 Iterable<DataSchemaNode> result = Iterables.filter( potentialSchemaNodes, filter );
752 return Iterables.getFirst( result, null );
755 public List<DataSchemaNode> findInstanceDataChildrenByName( final DataNodeContainer container,
756 final String name ) {
757 Preconditions.<DataNodeContainer> checkNotNull( container );
758 Preconditions.<String> checkNotNull( name );
760 List<DataSchemaNode> instantiatedDataNodeContainers = new ArrayList<DataSchemaNode>();
761 this.collectInstanceDataNodeContainers( instantiatedDataNodeContainers, container, name );
762 return instantiatedDataNodeContainers;
765 private void collectInstanceDataNodeContainers( final List<DataSchemaNode> potentialSchemaNodes,
766 final DataNodeContainer container, final String name ) {
768 Set<DataSchemaNode> childNodes = container.getChildNodes();
770 Predicate<DataSchemaNode> filter = new Predicate<DataSchemaNode>() {
772 public boolean apply( DataSchemaNode node ) {
773 return Objects.equal( node.getQName().getLocalName(), name );
777 Iterable<DataSchemaNode> nodes = Iterables.filter( childNodes, filter );
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 );
787 Iterable<ChoiceNode> choiceNodes = Iterables.<ChoiceNode> filter( container.getChildNodes(),
790 final Function<ChoiceNode, Set<ChoiceCaseNode>> choiceFunction =
791 new Function<ChoiceNode, Set<ChoiceCaseNode>>() {
793 public Set<ChoiceCaseNode> apply( final ChoiceNode node ) {
794 return node.getCases();
798 Iterable<Set<ChoiceCaseNode>> map = Iterables.<ChoiceNode, Set<ChoiceCaseNode>> transform(
799 choiceNodes, choiceFunction );
801 final Iterable<ChoiceCaseNode> allCases = Iterables.<ChoiceCaseNode> concat( map );
802 for( final ChoiceCaseNode caze : allCases ) {
803 this.collectInstanceDataNodeContainers( potentialSchemaNodes, caze, name );
807 public boolean isInstantiatedDataSchema( final DataSchemaNode node ) {
808 return node instanceof LeafSchemaNode || node instanceof LeafListSchemaNode ||
809 node instanceof ContainerSchemaNode || node instanceof ListSchemaNode;
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() );