2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
3 * Copyright (c) 2014 Brocade Communication Systems, Inc.
5 * This program and the accompanying materials are made available under the
6 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.controller.sal.restconf.impl;
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.Predicates;
15 import com.google.common.base.Splitter;
16 import com.google.common.base.Strings;
17 import com.google.common.base.Throwables;
18 import com.google.common.collect.ImmutableList;
19 import com.google.common.collect.Iterables;
20 import com.google.common.collect.Lists;
21 import com.google.common.util.concurrent.CheckedFuture;
22 import com.google.common.util.concurrent.Futures;
23 import java.math.BigInteger;
25 import java.net.URISyntaxException;
26 import java.text.ParseException;
27 import java.text.SimpleDateFormat;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.Date;
32 import java.util.HashMap;
33 import java.util.Iterator;
34 import java.util.List;
37 import java.util.concurrent.CancellationException;
38 import java.util.concurrent.ExecutionException;
39 import javax.ws.rs.core.Response;
40 import javax.ws.rs.core.Response.ResponseBuilder;
41 import javax.ws.rs.core.Response.Status;
42 import javax.ws.rs.core.UriBuilder;
43 import javax.ws.rs.core.UriInfo;
44 import org.apache.commons.lang3.StringUtils;
45 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
46 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
47 import org.opendaylight.controller.md.sal.common.api.data.OptimisticLockFailedException;
48 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
49 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
50 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
51 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
52 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
53 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
54 import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
55 import org.opendaylight.controller.sal.rest.api.Draft02;
56 import org.opendaylight.controller.sal.rest.api.RestconfService;
57 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
58 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
59 import org.opendaylight.controller.sal.restconf.rpc.impl.BrokerRpcExecutor;
60 import org.opendaylight.controller.sal.restconf.rpc.impl.MountPointRpcExecutor;
61 import org.opendaylight.controller.sal.restconf.rpc.impl.RpcExecutor;
62 import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter;
63 import org.opendaylight.controller.sal.streams.listeners.Notificator;
64 import org.opendaylight.controller.sal.streams.websockets.WebSocketServer;
65 import org.opendaylight.yangtools.concepts.Codec;
66 import org.opendaylight.yangtools.yang.common.QName;
67 import org.opendaylight.yangtools.yang.common.QNameModule;
68 import org.opendaylight.yangtools.yang.common.RpcResult;
69 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
70 import org.opendaylight.yangtools.yang.data.api.Node;
71 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
72 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
73 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.InstanceIdentifierBuilder;
74 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
75 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
76 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
77 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
78 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
79 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
80 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
81 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
82 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
83 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModifiedNodeDoesNotExistException;
84 import org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.parser.CnSnToNormalizedNodeParserFactory;
85 import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
86 import org.opendaylight.yangtools.yang.data.impl.NodeFactory;
87 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
88 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
89 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
90 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
91 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder;
92 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
93 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
94 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
95 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
96 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
97 import org.opendaylight.yangtools.yang.model.api.FeatureDefinition;
98 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
99 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
100 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
101 import org.opendaylight.yangtools.yang.model.api.Module;
102 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
103 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
104 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
105 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
106 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
107 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
108 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
109 import org.opendaylight.yangtools.yang.model.util.EmptyType;
110 import org.opendaylight.yangtools.yang.model.util.ExtendedType;
111 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
112 import org.opendaylight.yangtools.yang.parser.builder.impl.ContainerSchemaNodeBuilder;
113 import org.opendaylight.yangtools.yang.parser.builder.impl.LeafSchemaNodeBuilder;
114 import org.slf4j.Logger;
115 import org.slf4j.LoggerFactory;
117 public class RestconfImpl implements RestconfService {
119 private enum UriParameters {
120 PRETTY_PRINT("prettyPrint"),
123 private String uriParameterName;
125 UriParameters(final String uriParameterName) {
126 this.uriParameterName = uriParameterName;
130 public String toString() {
131 return uriParameterName;
135 private static class TypeDef {
136 public final TypeDefinition<? extends Object> typedef;
137 public final QName qName;
139 TypeDef(final TypeDefinition<? extends Object> typedef, final QName qName) {
140 this.typedef = typedef;
145 private final static RestconfImpl INSTANCE = new RestconfImpl();
147 private static final int NOTIFICATION_PORT = 8181;
149 private static final int CHAR_NOT_FOUND = -1;
151 private final static String MOUNT_POINT_MODULE_NAME = "ietf-netconf";
153 private final static SimpleDateFormat REVISION_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
155 private final static String SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote";
157 private final static String SAL_REMOTE_RPC_SUBSRCIBE = "create-data-change-event-subscription";
159 private BrokerFacade broker;
161 private ControllerContext controllerContext;
163 private static final Logger LOG = LoggerFactory.getLogger(RestconfImpl.class);
165 private static final DataChangeScope DEFAULT_SCOPE = DataChangeScope.BASE;
167 private static final LogicalDatastoreType DEFAULT_DATASTORE = LogicalDatastoreType.CONFIGURATION;
169 private static final URI NAMESPACE_EVENT_SUBSCRIPTION_AUGMENT = URI.create("urn:sal:restconf:event:subscription");
171 private static final Date EVENT_SUBSCRIPTION_AUGMENT_REVISION;
173 private static final String DATASTORE_PARAM_NAME = "datastore";
175 private static final String SCOPE_PARAM_NAME = "scope";
177 private static final String NETCONF_BASE = "urn:ietf:params:xml:ns:netconf:base:1.0";
179 private static final String NETCONF_BASE_PAYLOAD_NAME = "data";
181 private static final QName NETCONF_BASE_QNAME;
185 EVENT_SUBSCRIPTION_AUGMENT_REVISION = new SimpleDateFormat("yyyy-MM-dd").parse("2014-07-08");
186 NETCONF_BASE_QNAME = QName.create(QNameModule.create(new URI(NETCONF_BASE), null), NETCONF_BASE_PAYLOAD_NAME );
187 } catch (final ParseException e) {
188 throw new RestconfDocumentedException(
189 "It wasn't possible to convert revision date of sal-remote-augment to date", ErrorType.APPLICATION,
190 ErrorTag.OPERATION_FAILED);
191 } catch (final URISyntaxException e) {
192 throw new RestconfDocumentedException(
193 "It wasn't possible to create instance of URI class with "+NETCONF_BASE+" URI", ErrorType.APPLICATION,
194 ErrorTag.OPERATION_FAILED);
198 public void setBroker(final BrokerFacade broker) {
199 this.broker = broker;
202 public void setControllerContext(final ControllerContext controllerContext) {
203 this.controllerContext = controllerContext;
206 private RestconfImpl() {
209 public static RestconfImpl getInstance() {
214 public NormalizedNodeContext getModules(final UriInfo uriInfo) {
215 final Set<Module> allModules = controllerContext.getAllModules();
216 final MapNode allModuleMap = makeModuleMapNode(allModules);
218 final SchemaContext schemaContext = controllerContext.getGlobalSchema();
220 final Module restconfModule = getRestconfModule();
221 final DataSchemaNode modulesSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
222 restconfModule, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
223 Preconditions.checkState(modulesSchemaNode instanceof ContainerSchemaNode);
225 final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> moduleContainerBuilder =
226 Builders.containerBuilder((ContainerSchemaNode) modulesSchemaNode);
227 moduleContainerBuilder.withChild(allModuleMap);
229 return new NormalizedNodeContext(new InstanceIdentifierContext(null, modulesSchemaNode,
230 null, schemaContext), moduleContainerBuilder.build());
234 * Valid only for mount point
237 public NormalizedNodeContext getModules(final String identifier, final UriInfo uriInfo) {
238 Preconditions.checkNotNull(identifier);
239 if ( ! identifier.contains(ControllerContext.MOUNT)) {
240 final String errMsg = "URI has bad format. If modules behind mount point should be showed,"
241 + " URI has to end with " + ControllerContext.MOUNT;
242 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
245 final InstanceIdentifierContext mountPointIdentifier = controllerContext.toMountPointIdentifier(identifier);
246 final DOMMountPoint mountPoint = mountPointIdentifier.getMountPoint();
247 final Set<Module> modules = controllerContext.getAllModules(mountPoint);
248 final SchemaContext schemaContext = mountPoint.getSchemaContext();
249 final MapNode mountPointModulesMap = makeModuleMapNode(modules);
251 final Module restconfModule = getRestconfModule();
252 final DataSchemaNode modulesSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
253 restconfModule, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
254 Preconditions.checkState(modulesSchemaNode instanceof ContainerSchemaNode);
256 final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> moduleContainerBuilder =
257 Builders.containerBuilder((ContainerSchemaNode) modulesSchemaNode);
258 moduleContainerBuilder.withChild(mountPointModulesMap);
260 return new NormalizedNodeContext(new InstanceIdentifierContext(null, modulesSchemaNode,
261 mountPoint, schemaContext), moduleContainerBuilder.build());
265 public NormalizedNodeContext getModule(final String identifier, final UriInfo uriInfo) {
266 Preconditions.checkNotNull(identifier);
267 final QName moduleNameAndRevision = getModuleNameAndRevision(identifier);
268 Module module = null;
269 DOMMountPoint mountPoint = null;
270 final SchemaContext schemaContext;
271 if (identifier.contains(ControllerContext.MOUNT)) {
272 final InstanceIdentifierContext mountPointIdentifier = controllerContext.toMountPointIdentifier(identifier);
273 mountPoint = mountPointIdentifier.getMountPoint();
274 module = controllerContext.findModuleByNameAndRevision(mountPoint, moduleNameAndRevision);
275 schemaContext = mountPoint.getSchemaContext();
277 module = controllerContext.findModuleByNameAndRevision(moduleNameAndRevision);
278 schemaContext = controllerContext.getGlobalSchema();
281 if (module == null) {
282 final String errMsg = "Module with name '" + moduleNameAndRevision.getLocalName()
283 + "' and revision '" + moduleNameAndRevision.getRevision() + "' was not found.";
284 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
287 final Module restconfModule = getRestconfModule();
288 final Set<Module> modules = Collections.singleton(module);
289 final MapNode moduleMap = makeModuleMapNode(modules);
291 final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
292 restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
293 Preconditions.checkState(moduleSchemaNode instanceof ListSchemaNode);
295 return new NormalizedNodeContext(new InstanceIdentifierContext(null, moduleSchemaNode, mountPoint,
296 schemaContext), moduleMap);
300 public NormalizedNodeContext getAvailableStreams(final UriInfo uriInfo) {
301 final SchemaContext schemaContext = controllerContext.getGlobalSchema();
302 final Set<String> availableStreams = Notificator.getStreamNames();
303 final Module restconfModule = getRestconfModule();
304 final DataSchemaNode streamSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(restconfModule,
305 Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
306 Preconditions.checkState(streamSchemaNode instanceof ListSchemaNode);
308 final CollectionNodeBuilder<MapEntryNode, MapNode> listStreamsBuilder = Builders
309 .mapBuilder((ListSchemaNode) streamSchemaNode);
311 for (final String streamName : availableStreams) {
312 listStreamsBuilder.withChild(toStreamEntryNode(streamName, streamSchemaNode));
315 final DataSchemaNode streamsContainerSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
316 restconfModule, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
317 Preconditions.checkState(streamsContainerSchemaNode instanceof ContainerSchemaNode);
319 final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> streamsContainerBuilder =
320 Builders.containerBuilder((ContainerSchemaNode) streamsContainerSchemaNode);
321 streamsContainerBuilder.withChild(listStreamsBuilder.build());
324 return new NormalizedNodeContext(new InstanceIdentifierContext(null, streamsContainerSchemaNode, null,
325 schemaContext), streamsContainerBuilder.build());
329 public NormalizedNodeContext getOperations(final UriInfo uriInfo) {
330 final Set<Module> allModules = controllerContext.getAllModules();
331 return operationsFromModulesToNormalizedContext(allModules, null);
335 public NormalizedNodeContext getOperations(final String identifier, final UriInfo uriInfo) {
336 Set<Module> modules = null;
337 DOMMountPoint mountPoint = null;
338 if (identifier.contains(ControllerContext.MOUNT)) {
339 final InstanceIdentifierContext mountPointIdentifier = controllerContext.toMountPointIdentifier(identifier);
340 mountPoint = mountPointIdentifier.getMountPoint();
341 modules = controllerContext.getAllModules(mountPoint);
344 final String errMsg = "URI has bad format. If operations behind mount point should be showed, URI has to end with ";
345 throw new RestconfDocumentedException(errMsg + ControllerContext.MOUNT, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
348 return operationsFromModulesToNormalizedContext(modules, mountPoint);
351 private NormalizedNodeContext operationsFromModulesToNormalizedContext(final Set<Module> modules,
352 final DOMMountPoint mountPoint) {
354 // FIXME find best way to change restconf-netconf yang schema for provide this functionality
355 final String errMsg = "We are not able support view operations functionality yet.";
356 throw new RestconfDocumentedException(errMsg, ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED);
360 * @deprecated method will be removed in Lithium release
363 private StructuredData operationsFromModulesToStructuredData(final Set<Module> modules,
364 final DOMMountPoint mountPoint, final boolean prettyPrint) {
365 final List<Node<?>> operationsAsData = new ArrayList<Node<?>>();
366 final Module restconfModule = getRestconfModule();
367 final DataSchemaNode operationsSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
368 restconfModule, Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE);
369 final QName qName = operationsSchemaNode.getQName();
370 final SchemaPath path = operationsSchemaNode.getPath();
371 final ContainerSchemaNodeBuilder containerSchemaNodeBuilder = new ContainerSchemaNodeBuilder(
372 Draft02.RestConfModule.NAME, 0, qName, path);
373 final ContainerSchemaNodeBuilder fakeOperationsSchemaNode = containerSchemaNodeBuilder;
374 for (final Module module : modules) {
375 final Set<RpcDefinition> rpcs = module.getRpcs();
376 for (final RpcDefinition rpc : rpcs) {
377 final QName rpcQName = rpc.getQName();
378 final SimpleNode<Object> immutableSimpleNode = NodeFactory.<Object> createImmutableSimpleNode(rpcQName, null,
380 operationsAsData.add(immutableSimpleNode);
382 final String name = module.getName();
383 final LeafSchemaNodeBuilder leafSchemaNodeBuilder = new LeafSchemaNodeBuilder(name, 0, rpcQName,
384 SchemaPath.create(true, QName.create("dummy")));
385 final LeafSchemaNodeBuilder fakeRpcSchemaNode = leafSchemaNodeBuilder;
386 fakeRpcSchemaNode.setAugmenting(true);
388 final EmptyType instance = EmptyType.getInstance();
389 fakeRpcSchemaNode.setType(instance);
390 fakeOperationsSchemaNode.addChildNode(fakeRpcSchemaNode.build());
394 final CompositeNode operationsNode = NodeFactory.createImmutableCompositeNode(qName, null, operationsAsData);
395 final ContainerSchemaNode schemaNode = fakeOperationsSchemaNode.build();
396 return new StructuredData(operationsNode, schemaNode, mountPoint, prettyPrint);
399 private Module getRestconfModule() {
400 final Module restconfModule = controllerContext.getRestconfModule();
401 if (restconfModule == null) {
402 throw new RestconfDocumentedException("ietf-restconf module was not found.", ErrorType.APPLICATION,
403 ErrorTag.OPERATION_NOT_SUPPORTED);
406 return restconfModule;
409 private QName getModuleNameAndRevision(final String identifier) {
410 final int mountIndex = identifier.indexOf(ControllerContext.MOUNT);
411 String moduleNameAndRevision = "";
412 if (mountIndex >= 0) {
413 moduleNameAndRevision = identifier.substring(mountIndex + ControllerContext.MOUNT.length());
415 moduleNameAndRevision = identifier;
418 final Splitter splitter = Splitter.on("/").omitEmptyStrings();
419 final Iterable<String> split = splitter.split(moduleNameAndRevision);
420 final List<String> pathArgs = Lists.<String> newArrayList(split);
421 if (pathArgs.size() < 2) {
422 throw new RestconfDocumentedException(
423 "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'", ErrorType.PROTOCOL,
424 ErrorTag.INVALID_VALUE);
428 final String moduleName = pathArgs.get(0);
429 final String revision = pathArgs.get(1);
430 final Date moduleRevision = REVISION_FORMAT.parse(revision);
431 return QName.create(null, moduleRevision, moduleName);
432 } catch (final ParseException e) {
433 throw new RestconfDocumentedException("URI has bad format. It should be \'moduleName/yyyy-MM-dd\'",
434 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
439 * @deprecated method will be removed for Lithium release
440 * so, please use toStreamEntryNode method
443 * @param streamSchemaNode
447 private CompositeNode toStreamCompositeNode(final String streamName, final DataSchemaNode streamSchemaNode) {
448 final List<Node<?>> streamNodeValues = new ArrayList<Node<?>>();
449 List<DataSchemaNode> instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
450 ((DataNodeContainer) streamSchemaNode), "name");
451 final DataSchemaNode nameSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
453 .add(NodeFactory.<String> createImmutableSimpleNode(nameSchemaNode.getQName(), null, streamName));
455 instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
456 ((DataNodeContainer) streamSchemaNode), "description");
457 final DataSchemaNode descriptionSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
458 streamNodeValues.add(NodeFactory.<String> createImmutableSimpleNode(descriptionSchemaNode.getQName(), null,
459 "DESCRIPTION_PLACEHOLDER"));
461 instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
462 ((DataNodeContainer) streamSchemaNode), "replay-support");
463 final DataSchemaNode replaySupportSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
464 streamNodeValues.add(NodeFactory.<Boolean> createImmutableSimpleNode(replaySupportSchemaNode.getQName(), null,
465 Boolean.valueOf(true)));
467 instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
468 ((DataNodeContainer) streamSchemaNode), "replay-log-creation-time");
469 final DataSchemaNode replayLogCreationTimeSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
470 streamNodeValues.add(NodeFactory.<String> createImmutableSimpleNode(replayLogCreationTimeSchemaNode.getQName(),
473 instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
474 ((DataNodeContainer) streamSchemaNode), "events");
475 final DataSchemaNode eventsSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
476 streamNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(eventsSchemaNode.getQName(), null, ""));
478 return NodeFactory.createImmutableCompositeNode(streamSchemaNode.getQName(), null, streamNodeValues);
482 * @deprecated method will be removed for Lithium release
483 * so, please use toModuleEntryNode method
486 * @param moduleSchemaNode
490 private CompositeNode toModuleCompositeNode(final Module module, final DataSchemaNode moduleSchemaNode) {
491 final List<Node<?>> moduleNodeValues = new ArrayList<Node<?>>();
492 List<DataSchemaNode> instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
493 ((DataNodeContainer) moduleSchemaNode), "name");
494 final DataSchemaNode nameSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
495 moduleNodeValues.add(NodeFactory.<String> createImmutableSimpleNode(nameSchemaNode.getQName(), null,
498 instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
499 ((DataNodeContainer) moduleSchemaNode), "revision");
500 final DataSchemaNode revisionSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
501 final Date _revision = module.getRevision();
502 moduleNodeValues.add(NodeFactory.<String> createImmutableSimpleNode(revisionSchemaNode.getQName(), null,
503 REVISION_FORMAT.format(_revision)));
505 instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
506 ((DataNodeContainer) moduleSchemaNode), "namespace");
507 final DataSchemaNode namespaceSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
508 moduleNodeValues.add(NodeFactory.<String> createImmutableSimpleNode(namespaceSchemaNode.getQName(), null,
509 module.getNamespace().toString()));
511 instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
512 ((DataNodeContainer) moduleSchemaNode), "feature");
513 final DataSchemaNode featureSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
514 for (final FeatureDefinition feature : module.getFeatures()) {
515 moduleNodeValues.add(NodeFactory.<String> createImmutableSimpleNode(featureSchemaNode.getQName(), null,
516 feature.getQName().getLocalName()));
519 return NodeFactory.createImmutableCompositeNode(moduleSchemaNode.getQName(), null, moduleNodeValues);
523 public Object getRoot() {
528 public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
529 final SchemaPath type = payload.getInstanceIdentifierContext().getSchemaNode().getPath();
530 final URI namespace = payload.getInstanceIdentifierContext().getSchemaNode().getQName().getNamespace();
531 final CheckedFuture<DOMRpcResult, DOMRpcException> response;
532 final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
533 final SchemaContext schemaContext;
534 if (identifier.contains(MOUNT_POINT_MODULE_NAME) && mountPoint != null) {
535 final Optional<DOMRpcService> mountRpcServices = mountPoint.getService(DOMRpcService.class);
536 if ( ! mountRpcServices.isPresent()) {
537 throw new RestconfDocumentedException("Rpc service is missing.");
539 schemaContext = mountPoint.getSchemaContext();
540 response = mountRpcServices.get().invokeRpc(type, payload.getData());
542 if (namespace.toString().equals(SAL_REMOTE_NAMESPACE)) {
543 response = invokeSalRemoteRpcSubscribeRPC(payload);
545 response = broker.invokeRpc(type, payload.getData());
547 schemaContext = controllerContext.getGlobalSchema();
550 final DOMRpcResult result = checkRpcResponse(response);
552 DataSchemaNode resultNodeSchema = null;
553 NormalizedNode<?, ?> resultData = null;
554 if (result != null && result.getResult() != null) {
555 resultData = result.getResult();
556 final ContainerSchemaNode rpcDataSchemaNode = SchemaContextUtil.getRpcDataSchema(schemaContext, type);
557 resultNodeSchema = rpcDataSchemaNode.getDataChildByName(result.getResult().getNodeType());
560 return new NormalizedNodeContext(new InstanceIdentifierContext(null, resultNodeSchema, mountPoint,
561 schemaContext), resultData);
564 private DOMRpcResult checkRpcResponse(final CheckedFuture<DOMRpcResult, DOMRpcException> response) {
565 if (response == null) {
569 final DOMRpcResult retValue = response.get();
570 if (retValue.getErrors() == null || retValue.getErrors().isEmpty()) {
573 throw new RestconfDocumentedException("RpcError message", null, retValue.getErrors());
575 catch (final InterruptedException e) {
576 throw new RestconfDocumentedException(
577 "The operation was interrupted while executing and did not complete.", ErrorType.RPC,
578 ErrorTag.PARTIAL_OPERATION);
580 catch (final ExecutionException e) {
581 Throwable cause = e.getCause();
582 if (cause instanceof CancellationException) {
583 throw new RestconfDocumentedException("The operation was cancelled while executing.", ErrorType.RPC,
584 ErrorTag.PARTIAL_OPERATION);
585 } else if (cause != null) {
586 while (cause.getCause() != null) {
587 cause = cause.getCause();
590 if (cause instanceof IllegalArgumentException) {
591 throw new RestconfDocumentedException(cause.getMessage(), ErrorType.PROTOCOL,
592 ErrorTag.INVALID_VALUE);
595 throw new RestconfDocumentedException("The operation encountered an unexpected error while executing.",
598 throw new RestconfDocumentedException("The operation encountered an unexpected error while executing.",
604 private void validateInput(final DataSchemaNode inputSchema, final NormalizedNodeContext payload) {
605 if (inputSchema != null && payload.getData() == null) {
606 // expected a non null payload
607 throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
608 } else if (inputSchema == null && payload.getData() != null) {
609 // did not expect any input
610 throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
614 // TODO: Validate "mandatory" and "config" values here??? Or should those be
616 // validate in a more central location inside MD-SAL core.
621 * @deprecated method will be removed for Lithium release
627 private void validateInput(final DataSchemaNode inputSchema, final Node<?> payload) {
628 if (inputSchema != null && payload == null) {
629 // expected a non null payload
630 throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
631 } else if (inputSchema == null && payload != null) {
632 // did not expect any input
633 throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
637 // TODO: Validate "mandatory" and "config" values here??? Or should those be
639 // validate in a more central location inside MD-SAL core.
643 private CheckedFuture<DOMRpcResult, DOMRpcException> invokeSalRemoteRpcSubscribeRPC(final NormalizedNodeContext payload) {
644 final ContainerNode value = (ContainerNode) payload.getData();
645 final QName rpcQName = payload.getInstanceIdentifierContext().getSchemaNode().getQName();
646 Optional<DataContainerChild<? extends PathArgument, ?>> path = value.getChild(new NodeIdentifier(
647 QName.create(payload.getInstanceIdentifierContext().getSchemaNode().getQName(), "path")));
648 final Object pathValue = path.isPresent() ? path.get().getValue() : null;
650 if (!(pathValue instanceof YangInstanceIdentifier)) {
651 throw new RestconfDocumentedException("Instance identifier was not normalized correctly.",
652 ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
655 final YangInstanceIdentifier pathIdentifier = ((YangInstanceIdentifier) pathValue);
656 String streamName = null;
657 if (!Iterables.isEmpty(pathIdentifier.getPathArguments())) {
658 final String fullRestconfIdentifier = controllerContext.toFullRestconfIdentifier(pathIdentifier, null);
660 LogicalDatastoreType datastore = parseEnumTypeParameter(value, LogicalDatastoreType.class,
661 DATASTORE_PARAM_NAME);
662 datastore = datastore == null ? DEFAULT_DATASTORE : datastore;
664 DataChangeScope scope = parseEnumTypeParameter(value, DataChangeScope.class, SCOPE_PARAM_NAME);
665 scope = scope == null ? DEFAULT_SCOPE : scope;
667 streamName = Notificator.createStreamNameFromUri(fullRestconfIdentifier + "/datastore=" + datastore
668 + "/scope=" + scope);
671 if (Strings.isNullOrEmpty(streamName)) {
672 throw new RestconfDocumentedException(
673 "Path is empty or contains value node which is not Container or List build-in type.",
674 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
677 QName outputQname = QName.create(rpcQName, "output");
678 QName streamNameQname = QName.create(rpcQName, "stream-name");
680 ContainerNode output = ImmutableContainerNodeBuilder.create().withNodeIdentifier(new NodeIdentifier(outputQname))
681 .withChild(ImmutableNodes.leafNode(streamNameQname, streamName)).build();
683 if (!Notificator.existListenerFor(streamName)) {
684 YangInstanceIdentifier normalizedPathIdentifier = controllerContext.toNormalized(pathIdentifier);
685 Notificator.createListener(normalizedPathIdentifier, streamName);
688 DOMRpcResult defaultDOMRpcResult = new DefaultDOMRpcResult(output);
690 return Futures.immediateCheckedFuture(defaultDOMRpcResult);
694 public NormalizedNodeContext invokeRpc(final String identifier, final String noPayload, final UriInfo uriInfo) {
695 if (StringUtils.isNotBlank(noPayload)) {
696 throw new RestconfDocumentedException("Content must be empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
699 String identifierEncoded = null;
700 DOMMountPoint mountPoint = null;
701 final SchemaContext schemaContext;
702 if (identifier.contains(ControllerContext.MOUNT)) {
703 // mounted RPC call - look up mount instance.
704 final InstanceIdentifierContext mountPointId = controllerContext.toMountPointIdentifier(identifier);
705 mountPoint = mountPointId.getMountPoint();
706 schemaContext = mountPoint.getSchemaContext();
707 final int startOfRemoteRpcName = identifier.lastIndexOf(ControllerContext.MOUNT)
708 + ControllerContext.MOUNT.length() + 1;
709 final String remoteRpcName = identifier.substring(startOfRemoteRpcName);
710 identifierEncoded = remoteRpcName;
712 } else if (identifier.indexOf("/") != CHAR_NOT_FOUND) {
713 final String slashErrorMsg = String.format("Identifier %n%s%ncan\'t contain slash "
714 + "character (/).%nIf slash is part of identifier name then use %%2F placeholder.", identifier);
715 throw new RestconfDocumentedException(slashErrorMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
717 identifierEncoded = identifier;
718 schemaContext = controllerContext.getGlobalSchema();
721 final String identifierDecoded = controllerContext.urlPathArgDecode(identifierEncoded);
723 RpcDefinition rpc = null;
724 if (mountPoint == null) {
725 rpc = controllerContext.getRpcDefinition(identifierDecoded);
727 rpc = findRpc(mountPoint.getSchemaContext(), identifierDecoded);
731 throw new RestconfDocumentedException("RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT);
734 if (rpc.getInput() != null) {
735 // FIXME : find a correct Error from specification
736 throw new IllegalStateException("RPC " + rpc + " needs input value!");
739 final CheckedFuture<DOMRpcResult, DOMRpcException> response;
740 if (mountPoint != null) {
741 final Optional<DOMRpcService> mountRpcServices = mountPoint.getService(DOMRpcService.class);
742 if ( ! mountRpcServices.isPresent()) {
743 throw new RestconfDocumentedException("Rpc service is missing.");
745 response = mountRpcServices.get().invokeRpc(rpc.getPath(), null);
747 response = broker.invokeRpc(rpc.getPath(), null);
750 final DOMRpcResult result = checkRpcResponse(response);
752 DataSchemaNode resultNodeSchema = null;
753 NormalizedNode<?, ?> resultData = null;
754 if (result != null && result.getResult() != null) {
755 resultData = result.getResult();
756 final ContainerSchemaNode rpcDataSchemaNode =
757 SchemaContextUtil.getRpcDataSchema(schemaContext, rpc.getOutput().getPath());
758 resultNodeSchema = rpcDataSchemaNode.getDataChildByName(result.getResult().getNodeType());
761 return new NormalizedNodeContext(new InstanceIdentifierContext(null, resultNodeSchema, mountPoint,
762 schemaContext), resultData);
765 private RpcExecutor resolveIdentifierInInvokeRpc(final String identifier) {
766 String identifierEncoded = null;
767 DOMMountPoint mountPoint = null;
768 if (identifier.contains(ControllerContext.MOUNT)) {
769 // mounted RPC call - look up mount instance.
770 final InstanceIdentifierContext mountPointId = controllerContext.toMountPointIdentifier(identifier);
771 mountPoint = mountPointId.getMountPoint();
773 final int startOfRemoteRpcName = identifier.lastIndexOf(ControllerContext.MOUNT)
774 + ControllerContext.MOUNT.length() + 1;
775 final String remoteRpcName = identifier.substring(startOfRemoteRpcName);
776 identifierEncoded = remoteRpcName;
778 } else if (identifier.indexOf("/") != CHAR_NOT_FOUND) {
779 final String slashErrorMsg = String.format("Identifier %n%s%ncan\'t contain slash "
780 + "character (/).%nIf slash is part of identifier name then use %%2F placeholder.", identifier);
781 throw new RestconfDocumentedException(slashErrorMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
783 identifierEncoded = identifier;
786 final String identifierDecoded = controllerContext.urlPathArgDecode(identifierEncoded);
788 RpcDefinition rpc = null;
789 if (mountPoint == null) {
790 rpc = controllerContext.getRpcDefinition(identifierDecoded);
792 rpc = findRpc(mountPoint.getSchemaContext(), identifierDecoded);
796 throw new RestconfDocumentedException("RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT);
799 if (mountPoint == null) {
800 return new BrokerRpcExecutor(rpc, broker);
802 return new MountPointRpcExecutor(rpc, mountPoint);
807 private RpcDefinition findRpc(final SchemaContext schemaContext, final String identifierDecoded) {
808 final String[] splittedIdentifier = identifierDecoded.split(":");
809 if (splittedIdentifier.length != 2) {
810 throw new RestconfDocumentedException(identifierDecoded
811 + " couldn't be splitted to 2 parts (module:rpc name)", ErrorType.APPLICATION,
812 ErrorTag.INVALID_VALUE);
814 for (final Module module : schemaContext.getModules()) {
815 if (module.getName().equals(splittedIdentifier[0])) {
816 for (final RpcDefinition rpcDefinition : module.getRpcs()) {
817 if (rpcDefinition.getQName().getLocalName().equals(splittedIdentifier[1])) {
818 return rpcDefinition;
827 * @deprecated method will be removed for Lithium release
830 private StructuredData callRpc(final RpcExecutor rpcExecutor, final CompositeNode payload, final boolean prettyPrint) {
831 if (rpcExecutor == null) {
832 throw new RestconfDocumentedException("RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT);
835 CompositeNode rpcRequest = null;
836 final RpcDefinition rpc = rpcExecutor.getRpcDefinition();
837 final QName rpcName = rpc.getQName();
839 if (payload == null) {
840 rpcRequest = NodeFactory.createMutableCompositeNode(rpcName, null, null, null, null);
842 final CompositeNode value = this.normalizeNode(payload, rpc.getInput(), null);
843 final List<Node<?>> input = Collections.<Node<?>> singletonList(value);
844 rpcRequest = NodeFactory.createMutableCompositeNode(rpcName, null, input, null, null);
847 final RpcResult<CompositeNode> rpcResult = rpcExecutor.invokeRpc(rpcRequest);
849 checkRpcSuccessAndThrowException(rpcResult);
851 if (rpcResult.getResult() == null) {
855 if (rpc.getOutput() == null) {
856 return null; // no output, nothing to send back.
859 return new StructuredData(rpcResult.getResult(), rpc.getOutput(), null, prettyPrint);
862 private void checkRpcSuccessAndThrowException(final RpcResult<CompositeNode> rpcResult) {
863 if (rpcResult.isSuccessful() == false) {
865 throw new RestconfDocumentedException("The operation was not successful", null,
866 rpcResult.getErrors());
871 public NormalizedNodeContext readConfigurationData(final String identifier, final UriInfo uriInfo) {
872 final InstanceIdentifierContext iiWithData = controllerContext.toInstanceIdentifier(identifier);
873 final DOMMountPoint mountPoint = iiWithData.getMountPoint();
874 NormalizedNode<?, ?> data = null;
875 final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
876 if (mountPoint != null) {
877 data = broker.readConfigurationData(mountPoint, normalizedII);
879 data = broker.readConfigurationData(normalizedII);
881 return new NormalizedNodeContext(iiWithData, data);
884 @SuppressWarnings("unchecked")
885 private <T extends Node<?>> T pruneDataAtDepth(final T node, final Integer depth) {
890 if (node instanceof CompositeNode) {
891 final ImmutableList.Builder<Node<?>> newChildNodes = ImmutableList.<Node<?>> builder();
893 for (final Node<?> childNode : ((CompositeNode) node).getValue()) {
894 newChildNodes.add(pruneDataAtDepth(childNode, depth - 1));
898 return (T) ImmutableCompositeNode.create(node.getNodeType(), newChildNodes.build());
899 } else { // SimpleNode
904 private Integer parseDepthParameter(final UriInfo info) {
905 final String param = info.getQueryParameters(false).getFirst(UriParameters.DEPTH.toString());
906 if (Strings.isNullOrEmpty(param) || "unbounded".equals(param)) {
911 final Integer depth = Integer.valueOf(param);
913 throw new RestconfDocumentedException(new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
914 "Invalid depth parameter: " + depth, null,
915 "The depth parameter must be an integer > 1 or \"unbounded\""));
919 } catch (final NumberFormatException e) {
920 throw new RestconfDocumentedException(new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
921 "Invalid depth parameter: " + e.getMessage(), null,
922 "The depth parameter must be an integer > 1 or \"unbounded\""));
927 public NormalizedNodeContext readOperationalData(final String identifier, final UriInfo info) {
928 final InstanceIdentifierContext iiWithData = controllerContext.toInstanceIdentifier(identifier);
929 final DOMMountPoint mountPoint = iiWithData.getMountPoint();
930 NormalizedNode<?, ?> data = null;
931 final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
932 if (mountPoint != null) {
933 data = broker.readOperationalData(mountPoint, normalizedII);
935 data = broker.readOperationalData(normalizedII);
938 return new NormalizedNodeContext(iiWithData, data);
941 private boolean parsePrettyPrintParameter(final UriInfo info) {
942 final String param = info.getQueryParameters(false).getFirst(UriParameters.PRETTY_PRINT.toString());
943 return Boolean.parseBoolean(param);
947 public Response updateConfigurationData(final String identifier, final NormalizedNodeContext payload) {
948 Preconditions.checkNotNull(identifier);
949 final InstanceIdentifierContext<DataSchemaNode> iiWithData = controllerContext.toInstanceIdentifier(identifier);
951 validateInput(iiWithData.getSchemaNode(), payload);
952 validateTopLevelNodeName(payload, iiWithData.getInstanceIdentifier());
953 validateListKeysEqualityInPayloadAndUri(iiWithData, payload.getData());
955 final DOMMountPoint mountPoint = iiWithData.getMountPoint();
956 final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
959 * There is a small window where another write transaction could be updating the same data
960 * simultaneously and we get an OptimisticLockFailedException. This error is likely
961 * transient and The WriteTransaction#submit API docs state that a retry will likely
962 * succeed. So we'll try again if that scenario occurs. If it fails a third time then it
963 * probably will never succeed so we'll fail in that case.
965 * By retrying we're attempting to hide the internal implementation of the data store and
966 * how it handles concurrent updates from the restconf client. The client has instructed us
967 * to put the data and we should make every effort to do so without pushing optimistic lock
968 * failures back to the client and forcing them to handle it via retry (and having to
969 * document the behavior).
974 if (mountPoint != null) {
975 broker.commitConfigurationDataPut(mountPoint, normalizedII, payload.getData()).checkedGet();
977 broker.commitConfigurationDataPut(normalizedII, payload.getData()).checkedGet();
981 } catch (final TransactionCommitFailedException e) {
982 if(e instanceof OptimisticLockFailedException) {
984 LOG.debug("Got OptimisticLockFailedException on last try - failing");
985 throw new RestconfDocumentedException(e.getMessage(), e, e.getErrorList());
988 LOG.debug("Got OptimisticLockFailedException - trying again");
990 throw new RestconfDocumentedException(e.getMessage(), e, e.getErrorList());
995 return Response.status(Status.OK).build();
998 private void validateTopLevelNodeName(final NormalizedNodeContext node,
999 final YangInstanceIdentifier identifier) {
1001 final String payloadName = node.getData().getNodeType().getLocalName();
1002 final Iterator<PathArgument> pathArguments = identifier.getReversePathArguments().iterator();
1005 if ( ! pathArguments.hasNext()) {
1007 if ( ! node.getData().getNodeType().equals(NETCONF_BASE_QNAME)) {
1008 throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument",
1009 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
1013 final String identifierName = pathArguments.next().getNodeType().getLocalName();
1014 if ( ! payloadName.equals(identifierName)) {
1015 throw new RestconfDocumentedException("Payload name (" + payloadName
1016 + ") is different from identifier name (" + identifierName + ")", ErrorType.PROTOCOL,
1017 ErrorTag.MALFORMED_MESSAGE);
1023 * @deprecated method will be removed for Lithium release
1029 private void validateTopLevelNodeName(final Node<?> node,
1030 final YangInstanceIdentifier identifier) {
1031 final String payloadName = getName(node);
1032 final Iterator<PathArgument> pathArguments = identifier.getReversePathArguments().iterator();
1035 if (!pathArguments.hasNext()) {
1037 if (!node.getNodeType().equals(NETCONF_BASE_QNAME)) {
1038 throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument",
1039 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
1043 final String identifierName = pathArguments.next().getNodeType().getLocalName();
1044 if (!payloadName.equals(identifierName)) {
1045 throw new RestconfDocumentedException("Payload name (" + payloadName
1046 + ") is different from identifier name (" + identifierName + ")", ErrorType.PROTOCOL,
1047 ErrorTag.MALFORMED_MESSAGE);
1053 * Validates whether keys in {@code payload} are equal to values of keys in {@code iiWithData} for list schema node
1055 * @throws RestconfDocumentedException
1056 * if key values or key count in payload and URI isn't equal
1059 private void validateListKeysEqualityInPayloadAndUri(final InstanceIdentifierContext iiWithData,
1060 final NormalizedNode<?, ?> payload) {
1061 if (iiWithData.getSchemaNode() instanceof ListSchemaNode) {
1062 final List<QName> keyDefinitions = ((ListSchemaNode) iiWithData.getSchemaNode()).getKeyDefinition();
1063 final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
1064 if (lastPathArgument instanceof NodeIdentifierWithPredicates) {
1065 final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument)
1067 isEqualUriAndPayloadKeyValues(uriKeyValues, payload, keyDefinitions);
1073 * @deprecated method will be removed for Lithium release
1075 * Validates whether keys in {@code payload} are equal to values of keys in {@code iiWithData} for list schema node
1077 * @throws RestconfDocumentedException
1078 * if key values or key count in payload and URI isn't equal
1082 private void validateListKeysEqualityInPayloadAndUri(final InstanceIdentifierContext iiWithData,
1083 final CompositeNode payload) {
1084 if (iiWithData.getSchemaNode() instanceof ListSchemaNode) {
1085 final List<QName> keyDefinitions = ((ListSchemaNode) iiWithData.getSchemaNode()).getKeyDefinition();
1086 final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
1087 if (lastPathArgument instanceof NodeIdentifierWithPredicates) {
1088 final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument)
1090 isEqualUriAndPayloadKeyValues(uriKeyValues, payload, keyDefinitions);
1095 private void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final NormalizedNode<?, ?> payload,
1096 final List<QName> keyDefinitions) {
1097 for (final QName keyDefinition : keyDefinitions) {
1098 final Object uriKeyValue = uriKeyValues.get(keyDefinition);
1099 // should be caught during parsing URI to InstanceIdentifier
1100 if (uriKeyValue == null) {
1101 final String errMsg = "Missing key " + keyDefinition + " in URI.";
1102 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
1104 // TODO thing about the possibility to fix
1105 // final List<SimpleNode<?>> payloadKeyValues = payload.getSimpleNodesByName(keyDefinition.getLocalName());
1106 // if (payloadKeyValues.isEmpty()) {
1107 // final String errMsg = "Missing key " + keyDefinition.getLocalName() + " in the message body.";
1108 // throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
1111 // final Object payloadKeyValue = payloadKeyValues.iterator().next().getValue();
1112 // if (!uriKeyValue.equals(payloadKeyValue)) {
1113 // final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName() +
1114 // "' specified in the URI doesn't match the value '" + payloadKeyValue + "' specified in the message body. ";
1115 // throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1121 * @deprecated method will be removed for Lithium release
1123 * @param uriKeyValues
1125 * @param keyDefinitions
1128 private void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final CompositeNode payload,
1129 final List<QName> keyDefinitions) {
1130 for (final QName keyDefinition : keyDefinitions) {
1131 final Object uriKeyValue = uriKeyValues.get(keyDefinition);
1132 // should be caught during parsing URI to InstanceIdentifier
1133 if (uriKeyValue == null) {
1134 throw new RestconfDocumentedException("Missing key " + keyDefinition + " in URI.", ErrorType.PROTOCOL,
1135 ErrorTag.DATA_MISSING);
1137 final List<SimpleNode<?>> payloadKeyValues = payload.getSimpleNodesByName(keyDefinition.getLocalName());
1138 if (payloadKeyValues.isEmpty()) {
1139 throw new RestconfDocumentedException("Missing key " + keyDefinition.getLocalName()
1140 + " in the message body.", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
1143 final Object payloadKeyValue = payloadKeyValues.iterator().next().getValue();
1144 if (!uriKeyValue.equals(payloadKeyValue)) {
1145 throw new RestconfDocumentedException("The value '" + uriKeyValue + "' for key '"
1146 + keyDefinition.getLocalName() + "' specified in the URI doesn't match the value '"
1147 + payloadKeyValue + "' specified in the message body. ", ErrorType.PROTOCOL,
1148 ErrorTag.INVALID_VALUE);
1154 public Response createConfigurationData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
1155 if (payload == null) {
1156 throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
1159 final URI payloadNS = payload.getData().getNodeType().getNamespace();
1160 if (payloadNS == null) {
1161 throw new RestconfDocumentedException(
1162 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)",
1163 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE);
1166 final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
1168 final InstanceIdentifierContext iiWithData = mountPoint != null
1169 ? controllerContext.toMountPointIdentifier(identifier)
1170 : controllerContext.toInstanceIdentifier(identifier);
1171 final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
1174 if (mountPoint != null) {
1175 broker.commitConfigurationDataPost(mountPoint, normalizedII, payload.getData());
1177 broker.commitConfigurationDataPost(normalizedII, payload.getData());
1179 } catch(final RestconfDocumentedException e) {
1181 } catch (final Exception e) {
1182 throw new RestconfDocumentedException("Error creating data", e);
1186 final ResponseBuilder responseBuilder = Response.status(Status.NO_CONTENT);
1187 final URI location = resolveLocation(uriInfo, "config", mountPoint, normalizedII);
1188 if (location != null) {
1189 responseBuilder.location(location);
1191 return responseBuilder.build();
1194 // FIXME create RestconfIdetifierHelper and move this method there
1195 private YangInstanceIdentifier checkConsistencyOfNormalizedNodeContext(final NormalizedNodeContext payload) {
1196 Preconditions.checkArgument(payload != null);
1197 Preconditions.checkArgument(payload.getData() != null);
1198 Preconditions.checkArgument(payload.getData().getNodeType() != null);
1199 Preconditions.checkArgument(payload.getInstanceIdentifierContext() != null);
1200 Preconditions.checkArgument(payload.getInstanceIdentifierContext().getInstanceIdentifier() != null);
1202 final QName payloadNodeQname = payload.getData().getNodeType();
1203 final YangInstanceIdentifier yangIdent = payload.getInstanceIdentifierContext().getInstanceIdentifier();
1204 if (payloadNodeQname.compareTo(yangIdent.getLastPathArgument().getNodeType()) > 0) {
1207 final InstanceIdentifierContext parentContext = payload.getInstanceIdentifierContext();
1208 final SchemaNode parentSchemaNode = parentContext.getSchemaNode();
1209 if(parentSchemaNode instanceof DataNodeContainer) {
1210 final DataNodeContainer cast = (DataNodeContainer) parentSchemaNode;
1211 for (final DataSchemaNode child : cast.getChildNodes()) {
1212 if (payloadNodeQname.compareTo(child.getQName()) == 0) {
1213 return YangInstanceIdentifier.builder(yangIdent).node(child.getQName()).build();
1217 if (parentSchemaNode instanceof RpcDefinition) {
1220 final String errMsg = "Error parsing input: DataSchemaNode has not children";
1221 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
1225 public Response createConfigurationData(final NormalizedNodeContext payload, final UriInfo uriInfo) {
1226 if (payload == null) {
1227 throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
1230 final URI payloadNS = payload.getData().getNodeType().getNamespace();
1231 if (payloadNS == null) {
1232 throw new RestconfDocumentedException(
1233 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)",
1234 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE);
1237 final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
1238 final InstanceIdentifierContext iiWithData = payload.getInstanceIdentifierContext();
1239 final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
1242 if (mountPoint != null) {
1243 broker.commitConfigurationDataPost(mountPoint, normalizedII, payload.getData());
1246 broker.commitConfigurationDataPost(normalizedII, payload.getData());
1248 } catch(final RestconfDocumentedException e) {
1250 } catch (final Exception e) {
1251 throw new RestconfDocumentedException("Error creating data", e);
1254 final ResponseBuilder responseBuilder = Response.status(Status.NO_CONTENT);
1255 final URI location = resolveLocation(uriInfo, "", mountPoint, normalizedII);
1256 if (location != null) {
1257 responseBuilder.location(location);
1259 return responseBuilder.build();
1262 private URI resolveLocation(final UriInfo uriInfo, final String uriBehindBase, final DOMMountPoint mountPoint, final YangInstanceIdentifier normalizedII) {
1263 final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
1264 uriBuilder.path("config");
1266 uriBuilder.path(controllerContext.toFullRestconfIdentifier(normalizedII, mountPoint));
1267 } catch (final Exception e) {
1268 LOG.debug("Location for instance identifier"+normalizedII+"wasn't created", e);
1271 return uriBuilder.build();
1275 public Response deleteConfigurationData(final String identifier) {
1276 final InstanceIdentifierContext iiWithData = controllerContext.toInstanceIdentifier(identifier);
1277 final DOMMountPoint mountPoint = iiWithData.getMountPoint();
1278 YangInstanceIdentifier normalizedII;
1281 if (mountPoint != null) {
1282 normalizedII = new DataNormalizer(mountPoint.getSchemaContext()).toNormalized(iiWithData
1283 .getInstanceIdentifier());
1284 broker.commitConfigurationDataDelete(mountPoint, normalizedII);
1286 normalizedII = controllerContext.toNormalized(iiWithData.getInstanceIdentifier());
1287 broker.commitConfigurationDataDelete(normalizedII).get();
1289 } catch (final Exception e) {
1290 final Optional<Throwable> searchedException = Iterables.tryFind(Throwables.getCausalChain(e),
1291 Predicates.instanceOf(ModifiedNodeDoesNotExistException.class));
1292 if (searchedException.isPresent()) {
1293 throw new RestconfDocumentedException("Data specified for deleting doesn't exist.", ErrorType.APPLICATION, ErrorTag.DATA_MISSING);
1295 throw new RestconfDocumentedException("Error while deleting data", e);
1297 return Response.status(Status.OK).build();
1301 * Subscribes to some path in schema context (stream) to listen on changes on this stream.
1303 * Additional parameters for subscribing to stream are loaded via rpc input parameters:
1305 * <li>datastore</li> - default CONFIGURATION (other values of {@link LogicalDatastoreType} enum type)
1306 * <li>scope</li> - default BASE (other values of {@link DataChangeScope})
1310 public Response subscribeToStream(final String identifier, final UriInfo uriInfo) {
1311 final String streamName = Notificator.createStreamNameFromUri(identifier);
1312 if (Strings.isNullOrEmpty(streamName)) {
1313 throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1316 final ListenerAdapter listener = Notificator.getListenerFor(streamName);
1317 if (listener == null) {
1318 throw new RestconfDocumentedException("Stream was not found.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
1321 final Map<String, String> paramToValues = resolveValuesFromUri(identifier);
1322 final LogicalDatastoreType datastore = parserURIEnumParameter(LogicalDatastoreType.class,
1323 paramToValues.get(DATASTORE_PARAM_NAME));
1324 if (datastore == null) {
1325 throw new RestconfDocumentedException("Stream name doesn't contains datastore value (pattern /datastore=)",
1326 ErrorType.APPLICATION, ErrorTag.MISSING_ATTRIBUTE);
1328 final DataChangeScope scope = parserURIEnumParameter(DataChangeScope.class, paramToValues.get(SCOPE_PARAM_NAME));
1329 if (scope == null) {
1330 throw new RestconfDocumentedException("Stream name doesn't contains datastore value (pattern /scope=)",
1331 ErrorType.APPLICATION, ErrorTag.MISSING_ATTRIBUTE);
1334 broker.registerToListenDataChanges(datastore, scope, listener);
1336 final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
1337 int notificationPort = NOTIFICATION_PORT;
1339 final WebSocketServer webSocketServerInstance = WebSocketServer.getInstance();
1340 notificationPort = webSocketServerInstance.getPort();
1341 } catch (final NullPointerException e) {
1342 WebSocketServer.createInstance(NOTIFICATION_PORT);
1344 final UriBuilder uriToWebsocketServerBuilder = uriBuilder.port(notificationPort).scheme("ws");
1345 final URI uriToWebsocketServer = uriToWebsocketServerBuilder.replacePath(streamName).build();
1347 return Response.status(Status.OK).location(uriToWebsocketServer).build();
1351 * Load parameter for subscribing to stream from input composite node
1355 * @return enum object if its string value is equal to {@code paramName}. In other cases null.
1357 private <T> T parseEnumTypeParameter(final ContainerNode value, final Class<T> classDescriptor,
1358 final String paramName) {
1359 final QNameModule salRemoteAugment = QNameModule.create(NAMESPACE_EVENT_SUBSCRIPTION_AUGMENT,
1360 EVENT_SUBSCRIPTION_AUGMENT_REVISION);
1361 Optional<DataContainerChild<? extends PathArgument, ?>> enumNode = value.getChild(new NodeIdentifier(
1362 QName.create(salRemoteAugment, paramName)));
1363 if (!enumNode.isPresent()) {
1366 final Object rawValue = enumNode.get().getValue();
1367 if (!(rawValue instanceof String)) {
1371 return resolveAsEnum(classDescriptor, (String) rawValue);
1375 * Checks whether {@code value} is one of the string representation of enumeration {@code classDescriptor}
1377 * @return enum object if string value of {@code classDescriptor} enumeration is equal to {@code value}. Other cases
1380 private <T> T parserURIEnumParameter(final Class<T> classDescriptor, final String value) {
1381 if (Strings.isNullOrEmpty(value)) {
1384 return resolveAsEnum(classDescriptor, value);
1387 private <T> T resolveAsEnum(final Class<T> classDescriptor, final String value) {
1388 final T[] enumConstants = classDescriptor.getEnumConstants();
1389 if (enumConstants != null) {
1390 for (final T enm : classDescriptor.getEnumConstants()) {
1391 if (((Enum<?>) enm).name().equals(value)) {
1399 private Map<String, String> resolveValuesFromUri(final String uri) {
1400 final Map<String, String> result = new HashMap<>();
1401 final String[] tokens = uri.split("/");
1402 for (int i = 1; i < tokens.length; i++) {
1403 final String[] parameterTokens = tokens[i].split("=");
1404 if (parameterTokens.length == 2) {
1405 result.put(parameterTokens[0], parameterTokens[1]);
1411 private Module findModule(final DOMMountPoint mountPoint, final Node<?> data) {
1412 Module module = null;
1413 if (data instanceof NodeWrapper) {
1414 module = findModule(mountPoint, (NodeWrapper<?>) data);
1415 } else if (data != null) {
1416 final URI namespace = data.getNodeType().getNamespace();
1417 if (mountPoint != null) {
1418 module = controllerContext.findModuleByNamespace(mountPoint, namespace);
1420 module = controllerContext.findModuleByNamespace(namespace);
1423 if (module != null) {
1426 throw new RestconfDocumentedException(
1427 "Data has bad format. Root element node has incorrect namespace (XML format) or module name(JSON format)",
1428 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE);
1431 private Module findModule(final DOMMountPoint mountPoint, final NodeWrapper<?> data) {
1432 final URI namespace = data.getNamespace();
1433 Preconditions.<URI> checkNotNull(namespace);
1435 Module module = null;
1436 if (mountPoint != null) {
1437 module = controllerContext.findModuleByNamespace(mountPoint, namespace);
1438 if (module == null) {
1439 module = controllerContext.findModuleByName(mountPoint, namespace.toString());
1442 module = controllerContext.findModuleByNamespace(namespace);
1443 if (module == null) {
1444 module = controllerContext.findModuleByName(namespace.toString());
1451 private InstanceIdentifierContext addLastIdentifierFromData(final InstanceIdentifierContext identifierWithSchemaNode,
1452 final CompositeNode data, final DataSchemaNode schemaOfData, final SchemaContext schemaContext) {
1453 YangInstanceIdentifier instanceIdentifier = null;
1454 if (identifierWithSchemaNode != null) {
1455 instanceIdentifier = identifierWithSchemaNode.getInstanceIdentifier();
1458 final YangInstanceIdentifier iiOriginal = instanceIdentifier;
1459 InstanceIdentifierBuilder iiBuilder = null;
1460 if (iiOriginal == null) {
1461 iiBuilder = YangInstanceIdentifier.builder();
1463 iiBuilder = YangInstanceIdentifier.builder(iiOriginal);
1466 if ((schemaOfData instanceof ListSchemaNode)) {
1467 final HashMap<QName, Object> keys = resolveKeysFromData(((ListSchemaNode) schemaOfData), data);
1468 iiBuilder.nodeWithKey(schemaOfData.getQName(), keys);
1470 iiBuilder.node(schemaOfData.getQName());
1473 final YangInstanceIdentifier instance = iiBuilder.toInstance();
1474 DOMMountPoint mountPoint = null;
1475 final SchemaContext schemaCtx = null;
1476 if (identifierWithSchemaNode != null) {
1477 mountPoint = identifierWithSchemaNode.getMountPoint();
1480 return new InstanceIdentifierContext(instance, schemaOfData, mountPoint,schemaContext);
1483 private HashMap<QName, Object> resolveKeysFromData(final ListSchemaNode listNode, final CompositeNode dataNode) {
1484 final HashMap<QName, Object> keyValues = new HashMap<QName, Object>();
1485 final List<QName> _keyDefinition = listNode.getKeyDefinition();
1486 for (final QName key : _keyDefinition) {
1487 SimpleNode<? extends Object> head = null;
1488 final String localName = key.getLocalName();
1489 final List<SimpleNode<? extends Object>> simpleNodesByName = dataNode.getSimpleNodesByName(localName);
1490 if (simpleNodesByName != null) {
1491 head = Iterables.getFirst(simpleNodesByName, null);
1494 Object dataNodeKeyValueObject = null;
1496 dataNodeKeyValueObject = head.getValue();
1499 if (dataNodeKeyValueObject == null) {
1500 throw new RestconfDocumentedException("Data contains list \"" + dataNode.getNodeType().getLocalName()
1501 + "\" which does not contain key: \"" + key.getLocalName() + "\"", ErrorType.PROTOCOL,
1502 ErrorTag.INVALID_VALUE);
1505 keyValues.put(key, dataNodeKeyValueObject);
1511 private boolean endsWithMountPoint(final String identifier) {
1512 return identifier.endsWith(ControllerContext.MOUNT) || identifier.endsWith(ControllerContext.MOUNT + "/");
1515 private boolean representsMountPointRootData(final Node<?> data) {
1516 final URI namespace = namespace(data);
1517 return (SchemaContext.NAME.getNamespace().equals(namespace) /*
1518 * || MOUNT_POINT_MODULE_NAME .equals( namespace .
1521 && SchemaContext.NAME.getLocalName().equals(localName(data));
1524 private String addMountPointIdentifier(final String identifier) {
1525 final boolean endsWith = identifier.endsWith("/");
1527 return (identifier + ControllerContext.MOUNT);
1530 return identifier + "/" + ControllerContext.MOUNT;
1534 * @deprecated method will be removed in Lithium release
1535 * we don't wish to use Node and CompositeNode anywhere
1538 public CompositeNode normalizeNode(final Node<?> node, final DataSchemaNode schema, final DOMMountPoint mountPoint) {
1539 if (schema == null) {
1540 final String localName = node == null ? null :
1541 node instanceof NodeWrapper ? ((NodeWrapper<?>)node).getLocalName() :
1542 node.getNodeType().getLocalName();
1544 throw new RestconfDocumentedException("Data schema node was not found for " + localName,
1545 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1548 if (!(schema instanceof DataNodeContainer)) {
1549 throw new RestconfDocumentedException("Root element has to be container or list yang datatype.",
1550 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1553 if ((node instanceof NodeWrapper<?>)) {
1554 NodeWrapper<?> nodeWrap = (NodeWrapper<?>) node;
1555 final boolean isChangeAllowed = ((NodeWrapper<?>) node).isChangeAllowed();
1556 if (isChangeAllowed) {
1557 nodeWrap = topLevelElementAsCompositeNodeWrapper((NodeWrapper<?>) node, schema);
1559 this.normalizeNode(nodeWrap, schema, null, mountPoint);
1560 } catch (final IllegalArgumentException e) {
1561 final RestconfDocumentedException restconfDocumentedException = new RestconfDocumentedException(e.getMessage(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1562 restconfDocumentedException.addSuppressed(e);
1563 throw restconfDocumentedException;
1565 if (nodeWrap instanceof CompositeNodeWrapper) {
1566 return ((CompositeNodeWrapper) nodeWrap).unwrap();
1571 if (node instanceof CompositeNode) {
1572 return (CompositeNode) node;
1575 throw new RestconfDocumentedException("Top level element is not interpreted as composite node.",
1576 ErrorType.APPLICATION, ErrorTag.INVALID_VALUE);
1579 private void normalizeNode(final NodeWrapper<? extends Object> nodeBuilder, final DataSchemaNode schema,
1580 final QName previousAugment, final DOMMountPoint mountPoint) {
1581 if (schema == null) {
1582 throw new RestconfDocumentedException("Data has bad format.\n\"" + nodeBuilder.getLocalName()
1583 + "\" does not exist in yang schema.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1586 QName currentAugment = null;
1587 if (nodeBuilder.getQname() != null) {
1588 currentAugment = previousAugment;
1590 currentAugment = normalizeNodeName(nodeBuilder, schema, previousAugment, mountPoint);
1591 if (nodeBuilder.getQname() == null) {
1592 throw new RestconfDocumentedException(
1593 "Data has bad format.\nIf data is in XML format then namespace for \""
1594 + nodeBuilder.getLocalName() + "\" should be \"" + schema.getQName().getNamespace()
1595 + "\".\n" + "If data is in JSON format then module name for \""
1596 + nodeBuilder.getLocalName() + "\" should be corresponding to namespace \""
1597 + schema.getQName().getNamespace() + "\".", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1601 if (nodeBuilder instanceof CompositeNodeWrapper) {
1602 if (schema instanceof DataNodeContainer) {
1603 normalizeCompositeNode((CompositeNodeWrapper) nodeBuilder, (DataNodeContainer) schema, mountPoint,
1605 } else if (schema instanceof AnyXmlSchemaNode) {
1606 normalizeAnyXmlNode((CompositeNodeWrapper) nodeBuilder, (AnyXmlSchemaNode) schema);
1608 } else if (nodeBuilder instanceof SimpleNodeWrapper) {
1609 normalizeSimpleNode((SimpleNodeWrapper) nodeBuilder, schema, mountPoint);
1610 } else if ((nodeBuilder instanceof EmptyNodeWrapper)) {
1611 normalizeEmptyNode((EmptyNodeWrapper) nodeBuilder, schema);
1615 private void normalizeAnyXmlNode(final CompositeNodeWrapper compositeNode, final AnyXmlSchemaNode schema) {
1616 final List<NodeWrapper<?>> children = compositeNode.getValues();
1617 for (final NodeWrapper<? extends Object> child : children) {
1618 child.setNamespace(schema.getQName().getNamespace());
1619 if (child instanceof CompositeNodeWrapper) {
1620 normalizeAnyXmlNode((CompositeNodeWrapper) child, schema);
1625 private void normalizeEmptyNode(final EmptyNodeWrapper emptyNodeBuilder, final DataSchemaNode schema) {
1626 if ((schema instanceof LeafSchemaNode)) {
1627 emptyNodeBuilder.setComposite(false);
1629 if ((schema instanceof ContainerSchemaNode)) {
1630 // FIXME: Add presence check
1631 emptyNodeBuilder.setComposite(true);
1636 private void normalizeSimpleNode(final SimpleNodeWrapper simpleNode, final DataSchemaNode schema,
1637 final DOMMountPoint mountPoint) {
1638 final Object value = simpleNode.getValue();
1639 Object inputValue = value;
1640 final TypeDef typeDef = this.typeDefinition(schema);
1641 TypeDefinition<? extends Object> typeDefinition = typeDef != null ? typeDef.typedef : null;
1643 // For leafrefs, extract the type it is pointing to
1644 if(typeDefinition instanceof LeafrefTypeDefinition) {
1645 if (schema.getQName().equals(typeDef.qName)) {
1646 typeDefinition = SchemaContextUtil.getBaseTypeForLeafRef(((LeafrefTypeDefinition) typeDefinition), mountPoint == null ? controllerContext.getGlobalSchema() : mountPoint.getSchemaContext(), schema);
1648 typeDefinition = SchemaContextUtil.getBaseTypeForLeafRef(((LeafrefTypeDefinition) typeDefinition), mountPoint == null ? controllerContext.getGlobalSchema() : mountPoint.getSchemaContext(), typeDef.qName);
1652 if (typeDefinition instanceof IdentityrefTypeDefinition) {
1653 inputValue = parseToIdentityValuesDTO(simpleNode, value, inputValue);
1656 Object outputValue = inputValue;
1658 if (typeDefinition != null) {
1659 final Codec<Object, Object> codec = RestCodec.from(typeDefinition, mountPoint);
1660 outputValue = codec == null ? null : codec.deserialize(inputValue);
1663 simpleNode.setValue(outputValue);
1666 private Object parseToIdentityValuesDTO(final SimpleNodeWrapper simpleNode, final Object value, Object inputValue) {
1667 if ((value instanceof String)) {
1668 inputValue = new IdentityValuesDTO(simpleNode.getNamespace().toString(), (String) value, null,
1670 } // else value is already instance of IdentityValuesDTO
1674 private void normalizeCompositeNode(final CompositeNodeWrapper compositeNodeBuilder,
1675 final DataNodeContainer schema, final DOMMountPoint mountPoint, final QName currentAugment) {
1676 final List<NodeWrapper<?>> children = compositeNodeBuilder.getValues();
1677 checkNodeMultiplicityAccordingToSchema(schema, children);
1678 for (final NodeWrapper<? extends Object> child : children) {
1679 final List<DataSchemaNode> potentialSchemaNodes = ControllerContext.findInstanceDataChildrenByName(
1680 schema, child.getLocalName());
1682 if (potentialSchemaNodes.size() > 1 && child.getNamespace() == null) {
1683 final StringBuilder builder = new StringBuilder();
1684 for (final DataSchemaNode potentialSchemaNode : potentialSchemaNodes) {
1685 builder.append(" ").append(potentialSchemaNode.getQName().getNamespace().toString()).append("\n");
1688 throw new RestconfDocumentedException("Node \"" + child.getLocalName()
1689 + "\" is added as augment from more than one module. "
1690 + "Therefore node must have namespace (XML format) or module name (JSON format)."
1691 + "\nThe node is added as augment from modules with namespaces:\n" + builder,
1692 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1695 boolean rightNodeSchemaFound = false;
1696 for (final DataSchemaNode potentialSchemaNode : potentialSchemaNodes) {
1697 if (!rightNodeSchemaFound) {
1698 final QName potentialCurrentAugment = normalizeNodeName(child, potentialSchemaNode,
1699 currentAugment, mountPoint);
1700 if (child.getQname() != null) {
1701 this.normalizeNode(child, potentialSchemaNode, potentialCurrentAugment, mountPoint);
1702 rightNodeSchemaFound = true;
1707 if (!rightNodeSchemaFound) {
1708 throw new RestconfDocumentedException("Schema node \"" + child.getLocalName()
1709 + "\" was not found in module.", ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT);
1713 if ((schema instanceof ListSchemaNode)) {
1714 final ListSchemaNode listSchemaNode = (ListSchemaNode) schema;
1715 final List<QName> listKeys = listSchemaNode.getKeyDefinition();
1716 for (final QName listKey : listKeys) {
1717 boolean foundKey = false;
1718 for (final NodeWrapper<? extends Object> child : children) {
1719 if (Objects.equal(child.unwrap().getNodeType().getLocalName(), listKey.getLocalName())) {
1725 throw new RestconfDocumentedException("Missing key in URI \"" + listKey.getLocalName()
1726 + "\" of list \"" + listSchemaNode.getQName().getLocalName() + "\"", ErrorType.PROTOCOL,
1727 ErrorTag.DATA_MISSING);
1733 private void checkNodeMultiplicityAccordingToSchema(final DataNodeContainer dataNodeContainer,
1734 final List<NodeWrapper<?>> nodes) {
1735 final Map<String, Integer> equalNodeNamesToCounts = new HashMap<String, Integer>();
1736 for (final NodeWrapper<?> child : nodes) {
1737 Integer count = equalNodeNamesToCounts.get(child.getLocalName());
1738 equalNodeNamesToCounts.put(child.getLocalName(), count == null ? 1 : ++count);
1741 for (final DataSchemaNode childSchemaNode : dataNodeContainer.getChildNodes()) {
1742 if (childSchemaNode instanceof ContainerSchemaNode || childSchemaNode instanceof LeafSchemaNode) {
1743 final String localName = childSchemaNode.getQName().getLocalName();
1744 final Integer count = equalNodeNamesToCounts.get(localName);
1745 if (count != null && count > 1) {
1746 throw new RestconfDocumentedException("Multiple input data elements were specified for '"
1747 + childSchemaNode.getQName().getLocalName()
1748 + "'. The data for this element type can only be specified once.", ErrorType.APPLICATION,
1749 ErrorTag.BAD_ELEMENT);
1755 private QName normalizeNodeName(final NodeWrapper<? extends Object> nodeBuilder, final DataSchemaNode schema,
1756 final QName previousAugment, final DOMMountPoint mountPoint) {
1757 QName validQName = schema.getQName();
1758 QName currentAugment = previousAugment;
1759 if (schema.isAugmenting()) {
1760 currentAugment = schema.getQName();
1761 } else if (previousAugment != null
1762 && !Objects.equal(schema.getQName().getNamespace(), previousAugment.getNamespace())) {
1763 validQName = QName.create(currentAugment, schema.getQName().getLocalName());
1766 String moduleName = null;
1767 if (mountPoint == null) {
1768 moduleName = controllerContext.findModuleNameByNamespace(validQName.getNamespace());
1770 moduleName = controllerContext.findModuleNameByNamespace(mountPoint, validQName.getNamespace());
1773 if (nodeBuilder.getNamespace() == null || Objects.equal(nodeBuilder.getNamespace(), validQName.getNamespace())
1774 || Objects.equal(nodeBuilder.getNamespace().toString(), moduleName)) {
1776 * || Note : this check is wrong -
1777 * can never be true as it compares
1778 * a URI with a String not sure what
1779 * the intention is so commented out
1780 * ... Objects . equal ( nodeBuilder
1781 * . getNamespace ( ) ,
1782 * MOUNT_POINT_MODULE_NAME )
1785 nodeBuilder.setQname(validQName);
1788 return currentAugment;
1791 private URI namespace(final Node<?> data) {
1792 if (data instanceof NodeWrapper) {
1793 return ((NodeWrapper<?>) data).getNamespace();
1794 } else if (data != null) {
1795 return data.getNodeType().getNamespace();
1797 throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(data).toString());
1801 private String localName(final Node<?> data) {
1802 if (data instanceof NodeWrapper) {
1803 return ((NodeWrapper<?>) data).getLocalName();
1804 } else if (data != null) {
1805 return data.getNodeType().getLocalName();
1807 throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(data).toString());
1812 * @deprecated method will be removed for Lithium release
1818 private String getName(final Node<?> data) {
1819 if (data instanceof NodeWrapper) {
1820 return ((NodeWrapper<?>) data).getLocalName();
1821 } else if (data != null) {
1822 return data.getNodeType().getLocalName();
1824 throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(data).toString());
1828 private TypeDef typeDefinition(final TypeDefinition<?> type, final QName nodeQName) {
1829 TypeDefinition<?> baseType = type;
1830 QName qName = nodeQName;
1831 while (baseType.getBaseType() != null) {
1832 if (baseType instanceof ExtendedType) {
1833 qName = baseType.getQName();
1835 baseType = baseType.getBaseType();
1838 return new TypeDef(baseType, qName);
1842 private TypeDef typeDefinition(final DataSchemaNode node) {
1843 if (node instanceof LeafListSchemaNode) {
1844 return typeDefinition(((LeafListSchemaNode)node).getType(), node.getQName());
1845 } else if (node instanceof LeafSchemaNode) {
1846 return typeDefinition(((LeafSchemaNode)node).getType(), node.getQName());
1847 } else if (node instanceof AnyXmlSchemaNode) {
1850 throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(node).toString());
1854 private CompositeNode datastoreNormalizedNodeToCompositeNode(final NormalizedNode<?, ?> dataNode, final DataSchemaNode schema) {
1855 Node<?> nodes = null;
1856 if (dataNode == null) {
1857 throw new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION, ErrorTag.DATA_MISSING,
1858 "No data was found."));
1860 nodes = DataNormalizer.toLegacy(dataNode);
1861 if (nodes != null) {
1862 if (nodes instanceof CompositeNode) {
1863 return (CompositeNode) nodes;
1865 LOG.error("The node " + dataNode.getNodeType() + " couldn't be transformed to compositenode.");
1868 LOG.error("Top level node isn't of type Container or List schema node but "
1869 + schema.getClass().getSimpleName());
1872 throw new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION, ErrorTag.INVALID_VALUE,
1873 "It wasn't possible to correctly interpret data."));
1876 private NormalizedNode<?, ?> compositeNodeToDatastoreNormalizedNode(final CompositeNode compNode,
1877 final DataSchemaNode schema) {
1878 final List<Node<?>> lst = new ArrayList<Node<?>>();
1880 if (schema instanceof ContainerSchemaNode) {
1881 return CnSnToNormalizedNodeParserFactory.getInstance().getContainerNodeParser()
1882 .parse(lst, (ContainerSchemaNode) schema);
1883 } else if (schema instanceof ListSchemaNode) {
1884 return CnSnToNormalizedNodeParserFactory.getInstance().getMapEntryNodeParser()
1885 .parse(lst, (ListSchemaNode) schema);
1888 LOG.error("Top level isn't of type container, list, leaf schema node but " + schema.getClass().getSimpleName());
1890 throw new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION, ErrorTag.INVALID_VALUE,
1891 "It wasn't possible to translate specified data to datastore readable form."));
1894 private InstanceIdentifierContext normalizeInstanceIdentifierWithSchemaNode(
1895 final InstanceIdentifierContext iiWithSchemaNode) {
1896 return normalizeInstanceIdentifierWithSchemaNode(iiWithSchemaNode, false);
1899 private InstanceIdentifierContext normalizeInstanceIdentifierWithSchemaNode(
1900 final InstanceIdentifierContext iiWithSchemaNode, final boolean unwrapLastListNode) {
1901 return new InstanceIdentifierContext(instanceIdentifierToReadableFormForNormalizeNode(
1902 iiWithSchemaNode.getInstanceIdentifier(), unwrapLastListNode), iiWithSchemaNode.getSchemaNode(),
1903 iiWithSchemaNode.getMountPoint(),iiWithSchemaNode.getSchemaContext());
1906 private YangInstanceIdentifier instanceIdentifierToReadableFormForNormalizeNode(
1907 final YangInstanceIdentifier instIdentifier, final boolean unwrapLastListNode) {
1908 Preconditions.checkNotNull(instIdentifier, "Instance identifier can't be null");
1909 final List<PathArgument> result = new ArrayList<PathArgument>();
1910 final Iterator<PathArgument> iter = instIdentifier.getPathArguments().iterator();
1911 while (iter.hasNext()) {
1912 final PathArgument pathArgument = iter.next();
1913 if (pathArgument instanceof NodeIdentifierWithPredicates && (iter.hasNext() || unwrapLastListNode)) {
1914 result.add(new YangInstanceIdentifier.NodeIdentifier(pathArgument.getNodeType()));
1916 result.add(pathArgument);
1918 return YangInstanceIdentifier.create(result);
1921 private CompositeNodeWrapper topLevelElementAsCompositeNodeWrapper(final NodeWrapper<?> node,
1922 final DataSchemaNode schemaNode) {
1923 if (node instanceof CompositeNodeWrapper) {
1924 return (CompositeNodeWrapper) node;
1925 } else if (node instanceof SimpleNodeWrapper && isDataContainerNode(schemaNode)) {
1926 final SimpleNodeWrapper simpleNodeWrapper = (SimpleNodeWrapper) node;
1927 return new CompositeNodeWrapper(namespace(simpleNodeWrapper), localName(simpleNodeWrapper));
1930 throw new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION, ErrorTag.INVALID_VALUE,
1931 "Top level element has to be composite node or has to represent data container node."));
1934 private boolean isDataContainerNode(final DataSchemaNode schemaNode) {
1935 if (schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode) {
1941 public BigInteger getOperationalReceived() {
1942 // TODO Auto-generated method stub
1946 private MapNode makeModuleMapNode(final Set<Module> modules) {
1947 Preconditions.checkNotNull(modules);
1948 final Module restconfModule = getRestconfModule();
1949 final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
1950 restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
1951 Preconditions.checkState(moduleSchemaNode instanceof ListSchemaNode);
1953 final CollectionNodeBuilder<MapEntryNode, MapNode> listModuleBuilder = Builders
1954 .mapBuilder((ListSchemaNode) moduleSchemaNode);
1956 for (final Module module : modules) {
1957 listModuleBuilder.withChild(toModuleEntryNode(module, moduleSchemaNode));
1959 return listModuleBuilder.build();
1962 protected MapEntryNode toModuleEntryNode(final Module module, final DataSchemaNode moduleSchemaNode) {
1963 Preconditions.checkArgument(moduleSchemaNode instanceof ListSchemaNode,
1964 "moduleSchemaNode has to be of type ListSchemaNode");
1965 final ListSchemaNode listModuleSchemaNode = (ListSchemaNode) moduleSchemaNode;
1966 final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> moduleNodeValues = Builders
1967 .mapEntryBuilder(listModuleSchemaNode);
1969 List<DataSchemaNode> instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
1970 (listModuleSchemaNode), "name");
1971 final DataSchemaNode nameSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
1972 Preconditions.checkState(nameSchemaNode instanceof LeafSchemaNode);
1973 moduleNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) nameSchemaNode).withValue(module.getName())
1976 instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
1977 (listModuleSchemaNode), "revision");
1978 final DataSchemaNode revisionSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
1979 Preconditions.checkState(revisionSchemaNode instanceof LeafSchemaNode);
1980 final String revision = REVISION_FORMAT.format(module.getRevision());
1981 moduleNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) revisionSchemaNode).withValue(revision)
1984 instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
1985 (listModuleSchemaNode), "namespace");
1986 final DataSchemaNode namespaceSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
1987 Preconditions.checkState(namespaceSchemaNode instanceof LeafSchemaNode);
1988 moduleNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) namespaceSchemaNode)
1989 .withValue(module.getNamespace().toString()).build());
1991 instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
1992 (listModuleSchemaNode), "feature");
1993 final DataSchemaNode featureSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
1994 Preconditions.checkState(featureSchemaNode instanceof LeafListSchemaNode);
1995 final ListNodeBuilder<Object, LeafSetEntryNode<Object>> featuresBuilder = Builders
1996 .leafSetBuilder((LeafListSchemaNode) featureSchemaNode);
1997 for (final FeatureDefinition feature : module.getFeatures()) {
1998 featuresBuilder.withChild(Builders.leafSetEntryBuilder(((LeafListSchemaNode) featureSchemaNode))
1999 .withValue(feature.getQName().getLocalName()).build());
2001 moduleNodeValues.withChild(featuresBuilder.build());
2003 return moduleNodeValues.build();
2006 protected MapEntryNode toStreamEntryNode(final String streamName, final DataSchemaNode streamSchemaNode) {
2007 Preconditions.checkArgument(streamSchemaNode instanceof ListSchemaNode,
2008 "streamSchemaNode has to be of type ListSchemaNode");
2009 final ListSchemaNode listStreamSchemaNode = (ListSchemaNode) streamSchemaNode;
2010 final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> streamNodeValues = Builders
2011 .mapEntryBuilder(listStreamSchemaNode);
2013 List<DataSchemaNode> instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
2014 (listStreamSchemaNode), "name");
2015 final DataSchemaNode nameSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
2016 Preconditions.checkState(nameSchemaNode instanceof LeafSchemaNode);
2017 streamNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) nameSchemaNode).withValue(streamName)
2020 instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
2021 (listStreamSchemaNode), "description");
2022 final DataSchemaNode descriptionSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
2023 Preconditions.checkState(descriptionSchemaNode instanceof LeafSchemaNode);
2024 streamNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) nameSchemaNode)
2025 .withValue("DESCRIPTION_PLACEHOLDER").build());
2027 instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
2028 (listStreamSchemaNode), "replay-support");
2029 final DataSchemaNode replaySupportSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
2030 Preconditions.checkState(replaySupportSchemaNode instanceof LeafSchemaNode);
2031 streamNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) replaySupportSchemaNode)
2032 .withValue(Boolean.valueOf(true)).build());
2034 instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
2035 (listStreamSchemaNode), "replay-log-creation-time");
2036 final DataSchemaNode replayLogCreationTimeSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
2037 Preconditions.checkState(replayLogCreationTimeSchemaNode instanceof LeafSchemaNode);
2038 streamNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) replayLogCreationTimeSchemaNode)
2039 .withValue("").build());
2041 instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(
2042 (listStreamSchemaNode), "events");
2043 final DataSchemaNode eventsSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
2044 Preconditions.checkState(eventsSchemaNode instanceof LeafSchemaNode);
2045 streamNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) eventsSchemaNode)
2046 .withValue("").build());
2048 return streamNodeValues.build();