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, final boolean toMountPointIdentifier) {
132 this.checkPreconditions();
134 final List<String> pathArgs = urlPathArgsDecode(SLASH_SPLITTER.split(restconfInstance));
135 omitFirstAndLastEmptyString(pathArgs);
136 if (pathArgs.isEmpty()) {
140 String first = pathArgs.iterator().next();
141 final String startModule = ControllerContext.toModuleName(first);
142 if (startModule == null) {
143 throw new RestconfDocumentedException("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, latestModule, null,
150 toMountPointIdentifier);
152 if (iiWithSchemaNode == null) {
153 throw new RestconfDocumentedException("URI has bad format", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
156 return iiWithSchemaNode;
159 private static List<String> omitFirstAndLastEmptyString(final List<String> list) {
160 if (list.isEmpty()) {
164 String head = list.iterator().next();
165 if (head.isEmpty()) {
169 if (list.isEmpty()) {
173 String last = list.get(list.size() - 1);
174 if (last.isEmpty()) {
175 list.remove(list.size() - 1);
181 private Module getLatestModule(final SchemaContext schema, final String moduleName) {
182 Preconditions.checkArgument(schema != null);
183 Preconditions.checkArgument(moduleName != null && !moduleName.isEmpty());
185 Predicate<Module> filter = new Predicate<Module>() {
187 public boolean apply(final Module m) {
188 return Objects.equal(m.getName(), moduleName);
192 Iterable<Module> modules = Iterables.filter(schema.getModules(), filter);
193 return this.filterLatestModule(modules);
196 private Module filterLatestModule(final Iterable<Module> modules) {
197 Module latestModule = modules.iterator().hasNext() ? modules.iterator().next() : null;
198 for (final Module module : modules) {
199 if (module.getRevision().after(latestModule.getRevision())) {
200 latestModule = module;
206 public Module findModuleByName(final String moduleName) {
207 this.checkPreconditions();
208 Preconditions.checkArgument(moduleName != null && !moduleName.isEmpty());
209 return this.getLatestModule(globalSchema, moduleName);
212 public Module findModuleByName(final MountInstance mountPoint, final String moduleName) {
213 Preconditions.checkArgument(moduleName != null && mountPoint != null);
215 final SchemaContext mountPointSchema = mountPoint.getSchemaContext();
216 return mountPointSchema == null ? null : this.getLatestModule(mountPointSchema, moduleName);
219 public Module findModuleByNamespace(final URI namespace) {
220 this.checkPreconditions();
221 Preconditions.checkArgument(namespace != null);
223 final Set<Module> moduleSchemas = globalSchema.findModuleByNamespace(namespace);
224 return moduleSchemas == null ? null : this.filterLatestModule(moduleSchemas);
227 public Module findModuleByNamespace(final MountInstance mountPoint, final URI namespace) {
228 Preconditions.checkArgument(namespace != null && mountPoint != null);
230 final SchemaContext mountPointSchema = mountPoint.getSchemaContext();
231 Set<Module> moduleSchemas = mountPointSchema == null ? null : mountPointSchema.findModuleByNamespace(namespace);
232 return moduleSchemas == null ? null : this.filterLatestModule(moduleSchemas);
235 public Module findModuleByNameAndRevision(final QName module) {
236 this.checkPreconditions();
237 Preconditions.checkArgument(module != null && module.getLocalName() != null && module.getRevision() != null);
239 return globalSchema.findModuleByName(module.getLocalName(), module.getRevision());
242 public Module findModuleByNameAndRevision(final MountInstance mountPoint, final QName module) {
243 this.checkPreconditions();
244 Preconditions.checkArgument(module != null && module.getLocalName() != null && module.getRevision() != null
245 && mountPoint != null);
247 SchemaContext schemaContext = mountPoint.getSchemaContext();
248 return schemaContext == null ? null : schemaContext.findModuleByName(module.getLocalName(),
249 module.getRevision());
252 public DataNodeContainer getDataNodeContainerFor(final InstanceIdentifier path) {
253 this.checkPreconditions();
255 final Iterable<PathArgument> elements = path.getPathArguments();
256 PathArgument head = elements.iterator().next();
257 final QName startQName = head.getNodeType();
258 final Module initialModule = globalSchema.findModuleByNamespaceAndRevision(startQName.getNamespace(),
259 startQName.getRevision());
260 DataNodeContainer node = initialModule;
261 for (final PathArgument element : elements) {
262 QName _nodeType = element.getNodeType();
263 final DataSchemaNode potentialNode = ControllerContext.childByQName(node, _nodeType);
264 if (potentialNode == null || !this.isListOrContainer(potentialNode)) {
267 node = (DataNodeContainer) potentialNode;
273 public String toFullRestconfIdentifier(final InstanceIdentifier path) {
274 this.checkPreconditions();
276 final Iterable<PathArgument> elements = path.getPathArguments();
277 final StringBuilder builder = new StringBuilder();
278 PathArgument head = elements.iterator().next();
279 final QName startQName = head.getNodeType();
280 final Module initialModule = globalSchema.findModuleByNamespaceAndRevision(startQName.getNamespace(),
281 startQName.getRevision());
282 DataNodeContainer node = initialModule;
283 for (final PathArgument element : elements) {
284 QName _nodeType = element.getNodeType();
285 final DataSchemaNode potentialNode = ControllerContext.childByQName(node, _nodeType);
286 if (!this.isListOrContainer(potentialNode)) {
289 node = ((DataNodeContainer) potentialNode);
290 builder.append(this.convertToRestconfIdentifier(element, node));
293 return builder.toString();
296 public String findModuleNameByNamespace(final URI namespace) {
297 this.checkPreconditions();
299 String moduleName = this.uriToModuleName.get(namespace);
300 if (moduleName == null) {
301 final Module module = this.findModuleByNamespace(namespace);
302 if (module != null) {
303 moduleName = module.getName();
304 this.uriToModuleName.put(namespace, moduleName);
311 public String findModuleNameByNamespace(final MountInstance mountPoint, final URI namespace) {
312 final Module module = this.findModuleByNamespace(mountPoint, namespace);
313 return module == null ? null : module.getName();
316 public URI findNamespaceByModuleName(final String moduleName) {
317 URI namespace = this.moduleNameToUri.get(moduleName);
318 if (namespace == null) {
319 Module module = this.findModuleByName(moduleName);
320 if (module != null) {
321 URI _namespace = module.getNamespace();
322 namespace = _namespace;
323 this.uriToModuleName.put(namespace, moduleName);
329 public URI findNamespaceByModuleName(final MountInstance mountPoint, final String moduleName) {
330 final Module module = this.findModuleByName(mountPoint, moduleName);
331 return module == null ? null : module.getNamespace();
334 public Set<Module> getAllModules(final MountInstance mountPoint) {
335 this.checkPreconditions();
337 SchemaContext schemaContext = mountPoint == null ? null : mountPoint.getSchemaContext();
338 return schemaContext == null ? null : schemaContext.getModules();
341 public Set<Module> getAllModules() {
342 this.checkPreconditions();
343 return globalSchema.getModules();
346 public CharSequence toRestconfIdentifier(final QName qname) {
347 this.checkPreconditions();
349 String module = this.uriToModuleName.get(qname.getNamespace());
350 if (module == null) {
351 final Module moduleSchema = globalSchema.findModuleByNamespaceAndRevision(qname.getNamespace(),
352 qname.getRevision());
353 if (moduleSchema == null) {
357 this.uriToModuleName.put(qname.getNamespace(), moduleSchema.getName());
358 module = moduleSchema.getName();
361 StringBuilder builder = new StringBuilder();
362 builder.append(module);
364 builder.append(qname.getLocalName());
365 return builder.toString();
368 public CharSequence toRestconfIdentifier(final MountInstance mountPoint, final QName qname) {
369 if (mountPoint == null) {
373 SchemaContext schemaContext = mountPoint.getSchemaContext();
375 final Module moduleSchema = schemaContext.findModuleByNamespaceAndRevision(qname.getNamespace(),
376 qname.getRevision());
377 if (moduleSchema == null) {
381 StringBuilder builder = new StringBuilder();
382 builder.append(moduleSchema.getName());
384 builder.append(qname.getLocalName());
385 return builder.toString();
388 public Module getRestconfModule() {
389 return findModuleByNameAndRevision(Draft02.RestConfModule.IETF_RESTCONF_QNAME);
392 public DataSchemaNode getRestconfModuleErrorsSchemaNode() {
393 Module restconfModule = getRestconfModule();
394 if (restconfModule == null) {
398 Set<GroupingDefinition> groupings = restconfModule.getGroupings();
400 final Predicate<GroupingDefinition> filter = new Predicate<GroupingDefinition>() {
402 public boolean apply(final GroupingDefinition g) {
403 return Objects.equal(g.getQName().getLocalName(), Draft02.RestConfModule.ERRORS_GROUPING_SCHEMA_NODE);
407 Iterable<GroupingDefinition> filteredGroups = Iterables.filter(groupings, filter);
409 final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
411 List<DataSchemaNode> instanceDataChildrenByName = this.findInstanceDataChildrenByName(restconfGrouping,
412 Draft02.RestConfModule.ERRORS_CONTAINER_SCHEMA_NODE);
413 return Iterables.getFirst(instanceDataChildrenByName, null);
416 public DataSchemaNode getRestconfModuleRestConfSchemaNode(final Module inRestconfModule, final String schemaNodeName) {
417 Module restconfModule = inRestconfModule;
418 if (restconfModule == null) {
419 restconfModule = getRestconfModule();
422 if (restconfModule == null) {
426 Set<GroupingDefinition> groupings = restconfModule.getGroupings();
428 final Predicate<GroupingDefinition> filter = new Predicate<GroupingDefinition>() {
430 public boolean apply(final GroupingDefinition g) {
431 return Objects.equal(g.getQName().getLocalName(), Draft02.RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE);
435 Iterable<GroupingDefinition> filteredGroups = Iterables.filter(groupings, filter);
437 final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
439 List<DataSchemaNode> instanceDataChildrenByName = this.findInstanceDataChildrenByName(restconfGrouping,
440 Draft02.RestConfModule.RESTCONF_CONTAINER_SCHEMA_NODE);
441 final DataSchemaNode restconfContainer = Iterables.getFirst(instanceDataChildrenByName, null);
443 if (Objects.equal(schemaNodeName, Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE)) {
444 List<DataSchemaNode> instances = this.findInstanceDataChildrenByName(
445 ((DataNodeContainer) restconfContainer), Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE);
446 return Iterables.getFirst(instances, null);
447 } else if (Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE)) {
448 List<DataSchemaNode> instances = this.findInstanceDataChildrenByName(
449 ((DataNodeContainer) restconfContainer), Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
450 return Iterables.getFirst(instances, null);
451 } else if (Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE)) {
452 List<DataSchemaNode> instances = this.findInstanceDataChildrenByName(
453 ((DataNodeContainer) restconfContainer), Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
454 final DataSchemaNode modules = Iterables.getFirst(instances, null);
455 instances = this.findInstanceDataChildrenByName(((DataNodeContainer) modules),
456 Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
457 return Iterables.getFirst(instances, null);
458 } else if (Objects.equal(schemaNodeName, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE)) {
459 List<DataSchemaNode> instances = this.findInstanceDataChildrenByName(
460 ((DataNodeContainer) restconfContainer), Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
461 return Iterables.getFirst(instances, null);
462 } else if (Objects.equal(schemaNodeName, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE)) {
463 List<DataSchemaNode> instances = this.findInstanceDataChildrenByName(
464 ((DataNodeContainer) restconfContainer), Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
465 final DataSchemaNode modules = Iterables.getFirst(instances, null);
466 instances = this.findInstanceDataChildrenByName(((DataNodeContainer) modules),
467 Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
468 return Iterables.getFirst(instances, null);
469 } else if (Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE)) {
470 List<DataSchemaNode> instances = this.findInstanceDataChildrenByName(
471 ((DataNodeContainer) restconfContainer), Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
472 return Iterables.getFirst(instances, null);
478 private static DataSchemaNode childByQName(final ChoiceNode container, final QName name) {
479 for (final ChoiceCaseNode caze : container.getCases()) {
480 final DataSchemaNode ret = ControllerContext.childByQName(caze, name);
489 private static DataSchemaNode childByQName(final ChoiceCaseNode container, final QName name) {
490 return container.getDataChildByName(name);
493 private static DataSchemaNode childByQName(final ContainerSchemaNode container, final QName name) {
494 return ControllerContext.dataNodeChildByQName(container, name);
497 private static DataSchemaNode childByQName(final ListSchemaNode container, final QName name) {
498 return ControllerContext.dataNodeChildByQName(container, name);
501 private static DataSchemaNode childByQName(final Module container, final QName name) {
502 return ControllerContext.dataNodeChildByQName(container, name);
505 private static DataSchemaNode childByQName(final DataSchemaNode container, final QName name) {
509 private static DataSchemaNode dataNodeChildByQName(final DataNodeContainer container, final QName name) {
510 DataSchemaNode ret = container.getDataChildByName(name);
512 for (final DataSchemaNode node : container.getChildNodes()) {
513 if ((node instanceof ChoiceCaseNode)) {
514 final ChoiceCaseNode caseNode = ((ChoiceCaseNode) node);
515 DataSchemaNode childByQName = ControllerContext.childByQName(caseNode, name);
516 if (childByQName != null) {
525 private String toUriString(final Object object) throws UnsupportedEncodingException {
526 return object == null ? "" : URLEncoder.encode(object.toString(), ControllerContext.URI_ENCODING_CHAR_SET);
529 private InstanceIdWithSchemaNode collectPathArguments(final InstanceIdentifierBuilder builder,
530 final List<String> strings, final DataNodeContainer parentNode, final MountInstance mountPoint,
531 final boolean returnJustMountPoint) {
532 Preconditions.<List<String>> checkNotNull(strings);
534 if (parentNode == null) {
538 if (strings.isEmpty()) {
539 return new InstanceIdWithSchemaNode(builder.toInstance(), ((DataSchemaNode) parentNode), mountPoint);
542 String head = strings.iterator().next();
543 final String nodeName = toNodeName(head);
544 final String moduleName = ControllerContext.toModuleName(head);
546 DataSchemaNode targetNode = null;
547 if (!Strings.isNullOrEmpty(moduleName)) {
548 if (Objects.equal(moduleName, ControllerContext.MOUNT_MODULE)
549 && Objects.equal(nodeName, ControllerContext.MOUNT_NODE)) {
550 if (mountPoint != null) {
551 throw new RestconfDocumentedException("Restconf supports just one mount point in URI.",
552 ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED);
555 if (mountService == null) {
556 throw new RestconfDocumentedException(
557 "MountService was not found. Finding behind mount points does not work.",
558 ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED);
561 final InstanceIdentifier partialPath = builder.toInstance();
562 final MountInstance mount = mountService.getMountPoint(partialPath);
564 LOG.debug("Instance identifier to missing mount point: {}", partialPath);
565 throw new RestconfDocumentedException("Mount point does not exist.", ErrorType.PROTOCOL,
566 ErrorTag.UNKNOWN_ELEMENT);
569 final SchemaContext mountPointSchema = mount.getSchemaContext();
570 if (mountPointSchema == null) {
571 throw new RestconfDocumentedException("Mount point does not contain any schema with modules.",
572 ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT);
575 if (returnJustMountPoint) {
576 InstanceIdentifier instance = InstanceIdentifier.builder().toInstance();
577 return new InstanceIdWithSchemaNode(instance, mountPointSchema, mount);
580 if (strings.size() == 1) {
581 InstanceIdentifier instance = InstanceIdentifier.builder().toInstance();
582 return new InstanceIdWithSchemaNode(instance, mountPointSchema, mount);
585 final String moduleNameBehindMountPoint = toModuleName(strings.get(1));
586 if (moduleNameBehindMountPoint == null) {
587 throw new RestconfDocumentedException(
588 "First node after mount point in URI has to be in format \"moduleName:nodeName\"",
589 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
592 final Module moduleBehindMountPoint = this
593 .getLatestModule(mountPointSchema, moduleNameBehindMountPoint);
594 if (moduleBehindMountPoint == null) {
595 throw new RestconfDocumentedException("\"" + moduleName
596 + "\" module does not exist in mount point.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
599 List<String> subList = strings.subList(1, strings.size());
600 return this.collectPathArguments(InstanceIdentifier.builder(), subList, moduleBehindMountPoint, mount,
601 returnJustMountPoint);
604 Module module = null;
605 if (mountPoint == null) {
606 module = this.getLatestModule(globalSchema, moduleName);
607 if (module == null) {
608 throw new RestconfDocumentedException("\"" + moduleName + "\" module does not exist.",
609 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
612 SchemaContext schemaContext = mountPoint.getSchemaContext();
613 module = schemaContext == null ? null : this.getLatestModule(schemaContext, moduleName);
614 if (module == null) {
615 throw new RestconfDocumentedException("\"" + moduleName
616 + "\" module does not exist in mount point.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
620 targetNode = this.findInstanceDataChildByNameAndNamespace(parentNode, nodeName, module.getNamespace());
621 if (targetNode == null) {
622 throw new RestconfDocumentedException("URI has bad format. Possible reasons:\n" + " 1. \"" + head
623 + "\" was not found in parent data node.\n" + " 2. \"" + head
624 + "\" is behind mount point. Then it should be in format \"/" + MOUNT + "/" + head + "\".",
625 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
628 final List<DataSchemaNode> potentialSchemaNodes = this.findInstanceDataChildrenByName(parentNode, nodeName);
629 if (potentialSchemaNodes.size() > 1) {
630 final StringBuilder strBuilder = new StringBuilder();
631 for (final DataSchemaNode potentialNodeSchema : potentialSchemaNodes) {
632 strBuilder.append(" ").append(potentialNodeSchema.getQName().getNamespace()).append("\n");
635 throw new RestconfDocumentedException(
636 "URI has bad format. Node \""
638 + "\" is added as augment from more than one module. "
639 + "Therefore the node must have module name and it has to be in format \"moduleName:nodeName\"."
640 + "\nThe node is added as augment from modules with namespaces:\n"
641 + strBuilder.toString(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
644 if (potentialSchemaNodes.isEmpty()) {
645 throw new RestconfDocumentedException("\"" + nodeName + "\" in URI was not found in parent data node",
646 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
649 targetNode = potentialSchemaNodes.iterator().next();
652 if (!this.isListOrContainer(targetNode)) {
653 throw new RestconfDocumentedException("URI has bad format. Node \"" + head
654 + "\" must be Container or List yang type.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
658 if ((targetNode instanceof ListSchemaNode)) {
659 final ListSchemaNode listNode = ((ListSchemaNode) targetNode);
660 final int keysSize = listNode.getKeyDefinition().size();
661 if ((strings.size() - consumed) < keysSize) {
662 throw new RestconfDocumentedException("Missing key for list \"" + listNode.getQName().getLocalName()
663 + "\".", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
666 final List<String> uriKeyValues = strings.subList(consumed, consumed + keysSize);
667 final HashMap<QName, Object> keyValues = new HashMap<QName, Object>();
669 for (final QName key : listNode.getKeyDefinition()) {
671 final String uriKeyValue = uriKeyValues.get(i);
672 if (uriKeyValue.equals(NULL_VALUE)) {
673 throw new RestconfDocumentedException("URI has bad format. List \""
674 + listNode.getQName().getLocalName() + "\" cannot contain \"null\" value as a key.",
675 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
678 this.addKeyValue(keyValues, listNode.getDataChildByName(key), uriKeyValue, mountPoint);
683 consumed = consumed + i;
684 builder.nodeWithKey(targetNode.getQName(), keyValues);
686 builder.node(targetNode.getQName());
689 if ((targetNode instanceof DataNodeContainer)) {
690 final List<String> remaining = strings.subList(consumed, strings.size());
691 return this.collectPathArguments(builder, remaining, ((DataNodeContainer) targetNode), mountPoint,
692 returnJustMountPoint);
695 return new InstanceIdWithSchemaNode(builder.toInstance(), targetNode, mountPoint);
698 public DataSchemaNode findInstanceDataChildByNameAndNamespace(final DataNodeContainer container, final String name,
699 final URI namespace) {
700 Preconditions.<URI> checkNotNull(namespace);
702 final List<DataSchemaNode> potentialSchemaNodes = this.findInstanceDataChildrenByName(container, name);
704 Predicate<DataSchemaNode> filter = new Predicate<DataSchemaNode>() {
706 public boolean apply(final DataSchemaNode node) {
707 return Objects.equal(node.getQName().getNamespace(), namespace);
711 Iterable<DataSchemaNode> result = Iterables.filter(potentialSchemaNodes, filter);
712 return Iterables.getFirst(result, null);
715 public List<DataSchemaNode> findInstanceDataChildrenByName(final DataNodeContainer container, final String name) {
716 Preconditions.<DataNodeContainer> checkNotNull(container);
717 Preconditions.<String> checkNotNull(name);
719 List<DataSchemaNode> instantiatedDataNodeContainers = new ArrayList<DataSchemaNode>();
720 this.collectInstanceDataNodeContainers(instantiatedDataNodeContainers, container, name);
721 return instantiatedDataNodeContainers;
724 private void collectInstanceDataNodeContainers(final List<DataSchemaNode> potentialSchemaNodes,
725 final DataNodeContainer container, final String name) {
727 Predicate<DataSchemaNode> filter = new Predicate<DataSchemaNode>() {
729 public boolean apply(final DataSchemaNode node) {
730 return Objects.equal(node.getQName().getLocalName(), name);
734 Iterable<DataSchemaNode> nodes = Iterables.filter(container.getChildNodes(), filter);
736 // Can't combine this loop with the filter above because the filter is
737 // lazily-applied by Iterables.filter.
738 for (final DataSchemaNode potentialNode : nodes) {
739 if (this.isInstantiatedDataSchema(potentialNode)) {
740 potentialSchemaNodes.add(potentialNode);
744 Iterable<ChoiceNode> choiceNodes = Iterables.<ChoiceNode> filter(container.getChildNodes(), ChoiceNode.class);
746 final Function<ChoiceNode, Set<ChoiceCaseNode>> choiceFunction = new Function<ChoiceNode, Set<ChoiceCaseNode>>() {
748 public Set<ChoiceCaseNode> apply(final ChoiceNode node) {
749 return node.getCases();
753 Iterable<Set<ChoiceCaseNode>> map = Iterables.<ChoiceNode, Set<ChoiceCaseNode>> transform(choiceNodes,
756 final Iterable<ChoiceCaseNode> allCases = Iterables.<ChoiceCaseNode> concat(map);
757 for (final ChoiceCaseNode caze : allCases) {
758 this.collectInstanceDataNodeContainers(potentialSchemaNodes, caze, name);
762 public boolean isInstantiatedDataSchema(final DataSchemaNode node) {
763 return node instanceof LeafSchemaNode || node instanceof LeafListSchemaNode
764 || node instanceof ContainerSchemaNode || node instanceof ListSchemaNode
765 || node instanceof AnyXmlSchemaNode;
768 private void addKeyValue(final HashMap<QName, Object> map, final DataSchemaNode node, final String uriValue,
769 final MountInstance mountPoint) {
770 Preconditions.<String> checkNotNull(uriValue);
771 Preconditions.checkArgument((node instanceof LeafSchemaNode));
773 final String urlDecoded = urlPathArgDecode(uriValue);
774 final TypeDefinition<? extends Object> typedef = ((LeafSchemaNode) node).getType();
775 Codec<Object, Object> codec = RestCodec.from(typedef, mountPoint);
777 Object decoded = codec == null ? null : codec.deserialize(urlDecoded);
778 String additionalInfo = "";
779 if (decoded == null) {
780 TypeDefinition<? extends Object> baseType = RestUtil.resolveBaseTypeFrom(typedef);
781 if ((baseType instanceof IdentityrefTypeDefinition)) {
782 decoded = this.toQName(urlDecoded);
783 additionalInfo = "For key which is of type identityref it should be in format module_name:identity_name.";
787 if (decoded == null) {
788 throw new RestconfDocumentedException(uriValue + " from URI can't be resolved. " + additionalInfo,
789 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
792 map.put(node.getQName(), decoded);
795 private static String toModuleName(final String str) {
796 Preconditions.<String> checkNotNull(str);
797 if (str.indexOf(':') != -1) {
798 final Iterable<String> args = COLON_SPLITTER.split(str);
799 if (Iterables.size(args) == 2) {
800 return args.iterator().next();
806 private static String toNodeName(final String str) {
807 if (str.indexOf(':') != -1) {
808 final Iterable<String> args = COLON_SPLITTER.split(str);
809 if (Iterables.size(args) == 2) {
810 return Iterables.get(args, 1);
816 private QName toQName(final String name) {
817 final String module = toModuleName(name);
818 final String node = toNodeName(name);
819 Set<Module> modules = globalSchema.getModules();
821 final Comparator<Module> comparator = new Comparator<Module>() {
823 public int compare(final Module o1, final Module o2) {
824 return o1.getRevision().compareTo(o2.getRevision());
828 List<Module> sorted = new ArrayList<Module>(modules);
829 Collections.<Module> sort(new ArrayList<Module>(modules), comparator);
831 final Function<Module, QName> transform = new Function<Module, QName>() {
833 public QName apply(final Module m) {
834 return QName.create(m.getNamespace(), m.getRevision(), m.getName());
838 final Predicate<QName> findFirst = new Predicate<QName>() {
840 public boolean apply(final QName qn) {
841 return Objects.equal(module, qn.getLocalName());
845 Optional<QName> namespace = FluentIterable.from(sorted).transform(transform).firstMatch(findFirst);
846 return namespace.isPresent() ? QName.create(namespace.get(), node) : null;
849 private boolean isListOrContainer(final DataSchemaNode node) {
850 return node instanceof ListSchemaNode || node instanceof ContainerSchemaNode;
853 public RpcDefinition getRpcDefinition(final String name) {
854 final QName validName = this.toQName(name);
855 return validName == null ? null : this.qnameToRpc.get(validName);
859 public void onGlobalContextUpdated(final SchemaContext context) {
860 if (context != null) {
861 this.qnameToRpc.clear();
862 this.setGlobalSchema(context);
863 Set<RpcDefinition> _operations = context.getOperations();
864 for (final RpcDefinition operation : _operations) {
866 this.qnameToRpc.put(operation.getQName(), operation);
872 public static List<String> urlPathArgsDecode(final Iterable<String> strings) {
874 List<String> decodedPathArgs = new ArrayList<String>();
875 for (final String pathArg : strings) {
876 String _decode = URLDecoder.decode(pathArg, URI_ENCODING_CHAR_SET);
877 decodedPathArgs.add(_decode);
879 return decodedPathArgs;
880 } catch (UnsupportedEncodingException e) {
881 throw new RestconfDocumentedException("Invalid URL path '" + strings + "': " + e.getMessage(),
882 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
886 public String urlPathArgDecode(final String pathArg) {
887 if (pathArg != null) {
889 return URLDecoder.decode(pathArg, URI_ENCODING_CHAR_SET);
890 } catch (UnsupportedEncodingException e) {
891 throw new RestconfDocumentedException("Invalid URL path arg '" + pathArg + "': " + e.getMessage(),
892 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
899 private CharSequence convertToRestconfIdentifier(final PathArgument argument, final DataNodeContainer node) {
900 if (argument instanceof NodeIdentifier && node instanceof ContainerSchemaNode) {
901 return convertToRestconfIdentifier((NodeIdentifier) argument, (ContainerSchemaNode) node);
902 } else if (argument instanceof NodeIdentifierWithPredicates && node instanceof ListSchemaNode) {
903 return convertToRestconfIdentifier((NodeIdentifierWithPredicates) argument, (ListSchemaNode) node);
904 } else if (argument != null && node != null) {
905 throw new IllegalArgumentException("Conversion of generic path argument is not supported");
907 throw new IllegalArgumentException("Unhandled parameter types: "
908 + Arrays.<Object> asList(argument, node).toString());
912 private CharSequence convertToRestconfIdentifier(final NodeIdentifier argument, final ContainerSchemaNode node) {
913 StringBuilder builder = new StringBuilder();
915 QName nodeType = argument.getNodeType();
916 builder.append(this.toRestconfIdentifier(nodeType));
917 return builder.toString();
920 private CharSequence convertToRestconfIdentifier(final NodeIdentifierWithPredicates argument,
921 final ListSchemaNode node) {
922 QName nodeType = argument.getNodeType();
923 final CharSequence nodeIdentifier = this.toRestconfIdentifier(nodeType);
924 final Map<QName, Object> keyValues = argument.getKeyValues();
926 StringBuilder builder = new StringBuilder();
928 builder.append(nodeIdentifier);
931 List<QName> keyDefinition = node.getKeyDefinition();
932 boolean hasElements = false;
933 for (final QName key : keyDefinition) {
941 builder.append(this.toUriString(keyValues.get(key)));
942 } catch (UnsupportedEncodingException e) {
943 LOG.error("Error parsing URI: {}", keyValues.get(key), e);
948 return builder.toString();
951 private static DataSchemaNode childByQName(final Object container, final QName name) {
952 if (container instanceof ChoiceCaseNode) {
953 return childByQName((ChoiceCaseNode) container, name);
954 } else if (container instanceof ChoiceNode) {
955 return childByQName((ChoiceNode) container, name);
956 } else if (container instanceof ContainerSchemaNode) {
957 return childByQName((ContainerSchemaNode) container, name);
958 } else if (container instanceof ListSchemaNode) {
959 return childByQName((ListSchemaNode) container, name);
960 } else if (container instanceof DataSchemaNode) {
961 return childByQName((DataSchemaNode) container, name);
962 } else if (container instanceof Module) {
963 return childByQName((Module) container, name);
965 throw new IllegalArgumentException("Unhandled parameter types: "
966 + Arrays.<Object> asList(container, name).toString());