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;
22 import java.io.UnsupportedEncodingException;
24 import java.net.URLDecoder;
25 import java.net.URLEncoder;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.Comparator;
30 import java.util.HashMap;
31 import java.util.List;
34 import java.util.concurrent.ConcurrentHashMap;
36 import javax.ws.rs.core.Response.Status;
38 import org.opendaylight.controller.sal.core.api.mount.MountInstance;
39 import org.opendaylight.controller.sal.core.api.mount.MountService;
40 import org.opendaylight.controller.sal.rest.api.Draft02;
41 import org.opendaylight.controller.sal.rest.impl.RestUtil;
42 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
43 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
44 import org.opendaylight.yangtools.concepts.Codec;
45 import org.opendaylight.yangtools.yang.common.QName;
46 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
47 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder;
48 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
49 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
50 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
51 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
52 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
53 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
54 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
55 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
56 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
57 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
58 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
59 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
60 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
61 import org.opendaylight.yangtools.yang.model.api.Module;
62 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
63 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
64 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
65 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
66 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
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 static final Splitter SLASH_SPLITTER = Splitter.on('/');
87 private static final Splitter COLON_SPLITTER = Splitter.on(':');
89 private final BiMap<URI, String> uriToModuleName = HashBiMap.<URI, String> create();
91 private final Map<String, URI> moduleNameToUri = uriToModuleName.inverse();
93 private final Map<QName, RpcDefinition> qnameToRpc = new ConcurrentHashMap<>();
95 private volatile SchemaContext globalSchema;
96 private volatile MountService mountService;
98 public void setGlobalSchema( final SchemaContext globalSchema ) {
99 this.globalSchema = globalSchema;
102 public void setMountService( final MountService mountService ) {
103 this.mountService = mountService;
106 private ControllerContext() {
109 public static ControllerContext getInstance() {
110 return ControllerContext.INSTANCE;
113 private void checkPreconditions() {
114 if( globalSchema == null ) {
115 throw new RestconfDocumentedException( Status.SERVICE_UNAVAILABLE );
119 public void setSchemas( final SchemaContext schemas ) {
120 this.onGlobalContextUpdated( schemas );
123 public InstanceIdWithSchemaNode toInstanceIdentifier( final String restconfInstance ) {
124 return this.toIdentifier( restconfInstance, false );
127 public InstanceIdWithSchemaNode toMountPointIdentifier( final String restconfInstance ) {
128 return this.toIdentifier( restconfInstance, true );
131 private InstanceIdWithSchemaNode toIdentifier( final String restconfInstance,
132 final boolean toMountPointIdentifier ) {
133 this.checkPreconditions();
135 final List<String> pathArgs = urlPathArgsDecode( SLASH_SPLITTER.split( restconfInstance ) );
136 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 static 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( final 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 Iterable<PathArgument> elements = path.getPathArguments();
261 PathArgument head = elements.iterator().next();
262 final QName startQName = head.getNodeType();
263 final Module initialModule = globalSchema.findModuleByNamespaceAndRevision(
264 startQName.getNamespace(), startQName.getRevision() );
265 DataNodeContainer node = initialModule;
266 for( final PathArgument element : elements ) {
267 QName _nodeType = element.getNodeType();
268 final DataSchemaNode potentialNode = ControllerContext.childByQName( node, _nodeType );
269 if( potentialNode == null || !this.isListOrContainer( potentialNode ) ) {
272 node = (DataNodeContainer) potentialNode;
278 public String toFullRestconfIdentifier( final InstanceIdentifier path ) {
279 this.checkPreconditions();
281 final Iterable<PathArgument> elements = path.getPathArguments();
282 final StringBuilder builder = new StringBuilder();
283 PathArgument head = elements.iterator().next();
284 final QName startQName = head.getNodeType();
285 final Module initialModule = globalSchema.findModuleByNamespaceAndRevision(
286 startQName.getNamespace(), startQName.getRevision() );
287 DataNodeContainer node = initialModule;
288 for( final PathArgument element : elements ) {
289 QName _nodeType = element.getNodeType();
290 final DataSchemaNode potentialNode = ControllerContext.childByQName( node, _nodeType );
291 if( !this.isListOrContainer( potentialNode ) ) {
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( final Module inRestconfModule,
424 final 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 = 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( final 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( final 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 ||
810 node instanceof AnyXmlSchemaNode;
813 private void addKeyValue( final HashMap<QName, Object> map, final DataSchemaNode node,
814 final String uriValue, final MountInstance mountPoint ) {
815 Preconditions.<String> checkNotNull( uriValue );
816 Preconditions.checkArgument( (node instanceof LeafSchemaNode) );
818 final String urlDecoded = urlPathArgDecode( uriValue );
819 final TypeDefinition<? extends Object> typedef = ((LeafSchemaNode) node).getType();
820 Codec<Object, Object> codec = RestCodec.from( typedef, mountPoint );
822 Object decoded = codec == null ? null : codec.deserialize( urlDecoded );
823 String additionalInfo = "";
824 if( decoded == null ) {
825 TypeDefinition<? extends Object> baseType = RestUtil.resolveBaseTypeFrom( typedef );
826 if( (baseType instanceof IdentityrefTypeDefinition) ) {
827 decoded = this.toQName( urlDecoded );
828 additionalInfo = "For key which is of type identityref it should be in format module_name:identity_name.";
832 if( decoded == null ) {
833 throw new RestconfDocumentedException(
834 uriValue + " from URI can't be resolved. " + additionalInfo,
835 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
838 map.put( node.getQName(), decoded );
841 private static String toModuleName( final String str ) {
842 Preconditions.<String> checkNotNull( str );
843 if( str.indexOf( ':' ) != -1 ) {
844 final Iterable<String> args = COLON_SPLITTER.split( str );
845 if( Iterables.size( args ) == 2 ) {
846 return args.iterator().next();
852 private static String toNodeName( final String str ) {
853 if( str.indexOf( ':' ) != -1 ) {
854 final Iterable<String> args = COLON_SPLITTER.split( str );
855 if( Iterables.size( args ) == 2 ) {
856 return Iterables.get( args, 1 );
862 private QName toQName( final String name ) {
863 final String module = toModuleName( name );
864 final String node = toNodeName( name );
865 Set<Module> modules = globalSchema.getModules();
867 final Comparator<Module> comparator = new Comparator<Module>() {
869 public int compare( final Module o1, final Module o2 ) {
870 return o1.getRevision().compareTo( o2.getRevision() );
874 List<Module> sorted = new ArrayList<Module>( modules );
875 Collections.<Module> sort( new ArrayList<Module>( modules ), comparator );
877 final Function<Module, QName> transform = new Function<Module, QName>() {
879 public QName apply( final Module m ) {
880 return QName.create( m.getNamespace(), m.getRevision(), m.getName() );
884 final Predicate<QName> findFirst = new Predicate<QName>() {
886 public boolean apply( final QName qn ) {
887 return Objects.equal( module, qn.getLocalName() );
891 Optional<QName> namespace = FluentIterable.from( sorted )
892 .transform( transform )
893 .firstMatch( findFirst );
894 return namespace.isPresent() ? QName.create( namespace.get(), node ) : null;
897 private boolean isListOrContainer( final DataSchemaNode node ) {
898 return node instanceof ListSchemaNode || node instanceof ContainerSchemaNode;
901 public RpcDefinition getRpcDefinition( final String name ) {
902 final QName validName = this.toQName( name );
903 return validName == null ? null : this.qnameToRpc.get( validName );
907 public void onGlobalContextUpdated( final SchemaContext context ) {
908 if( context != null ) {
909 this.qnameToRpc.clear();
910 this.setGlobalSchema( context );
911 Set<RpcDefinition> _operations = context.getOperations();
912 for( final RpcDefinition operation : _operations ) {
914 this.qnameToRpc.put( operation.getQName(), operation );
920 public static List<String> urlPathArgsDecode( final Iterable<String> strings ) {
922 List<String> decodedPathArgs = new ArrayList<String>();
923 for( final String pathArg : strings ) {
924 String _decode = URLDecoder.decode( pathArg, URI_ENCODING_CHAR_SET );
925 decodedPathArgs.add( _decode );
927 return decodedPathArgs;
929 catch( UnsupportedEncodingException e ) {
930 throw new RestconfDocumentedException(
931 "Invalid URL path '" + strings + "': " + e.getMessage(),
932 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
936 public String urlPathArgDecode( final String pathArg ) {
937 if( pathArg != null ) {
939 return URLDecoder.decode( pathArg, URI_ENCODING_CHAR_SET );
941 catch( UnsupportedEncodingException e ) {
942 throw new RestconfDocumentedException(
943 "Invalid URL path arg '" + pathArg + "': " + e.getMessage(),
944 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
951 private CharSequence convertToRestconfIdentifier( final PathArgument argument,
952 final DataNodeContainer node ) {
953 if( argument instanceof NodeIdentifier && node instanceof ContainerSchemaNode ) {
954 return convertToRestconfIdentifier( (NodeIdentifier) argument, (ContainerSchemaNode) node );
956 else if( argument instanceof NodeIdentifierWithPredicates && node instanceof ListSchemaNode ) {
957 return convertToRestconfIdentifier( (NodeIdentifierWithPredicates) argument, (ListSchemaNode) node );
959 else if( argument != null && node != null ) {
960 throw new IllegalArgumentException(
961 "Conversion of generic path argument is not supported" );
964 throw new IllegalArgumentException( "Unhandled parameter types: "
965 + Arrays.<Object> asList( argument, node ).toString() );
969 private CharSequence convertToRestconfIdentifier( final NodeIdentifier argument,
970 final ContainerSchemaNode node ) {
971 StringBuilder builder = new StringBuilder();
972 builder.append( "/" );
973 QName nodeType = argument.getNodeType();
974 builder.append( this.toRestconfIdentifier( nodeType ) );
975 return builder.toString();
978 private CharSequence convertToRestconfIdentifier( final NodeIdentifierWithPredicates argument,
979 final ListSchemaNode node ) {
980 QName nodeType = argument.getNodeType();
981 final CharSequence nodeIdentifier = this.toRestconfIdentifier( nodeType );
982 final Map<QName, Object> keyValues = argument.getKeyValues();
984 StringBuilder builder = new StringBuilder();
985 builder.append( "/" );
986 builder.append( nodeIdentifier );
987 builder.append( "/" );
989 List<QName> keyDefinition = node.getKeyDefinition();
990 boolean hasElements = false;
991 for( final QName key : keyDefinition ) {
996 builder.append( "/" );
1000 builder.append( this.toUriString( keyValues.get( key ) ) );
1001 } catch( UnsupportedEncodingException e ) {
1002 LOG.error( "Error parsing URI: {}", keyValues.get( key ), e );
1007 return builder.toString();
1010 private static DataSchemaNode childByQName( final Object container, final QName name ) {
1011 if( container instanceof ChoiceCaseNode ) {
1012 return childByQName( (ChoiceCaseNode) container, name );
1014 else if( container instanceof ChoiceNode ) {
1015 return childByQName( (ChoiceNode) container, name );
1017 else if( container instanceof ContainerSchemaNode ) {
1018 return childByQName( (ContainerSchemaNode) container, name );
1020 else if( container instanceof ListSchemaNode ) {
1021 return childByQName( (ListSchemaNode) container, name );
1023 else if( container instanceof DataSchemaNode ) {
1024 return childByQName( (DataSchemaNode) container, name );
1026 else if( container instanceof Module ) {
1027 return childByQName( (Module) container, name );
1030 throw new IllegalArgumentException( "Unhandled parameter types: "
1031 + Arrays.<Object> asList( container, name ).toString() );