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.Preconditions;
13 import com.google.common.base.Splitter;
14 import com.google.common.base.Strings;
15 import com.google.common.collect.ImmutableList;
16 import com.google.common.collect.Iterables;
17 import com.google.common.collect.Lists;
19 import java.text.ParseException;
20 import java.text.SimpleDateFormat;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.Date;
26 import java.util.HashMap;
27 import java.util.List;
30 import java.util.concurrent.Future;
31 import javax.ws.rs.core.Response;
32 import javax.ws.rs.core.Response.Status;
33 import javax.ws.rs.core.UriBuilder;
34 import javax.ws.rs.core.UriInfo;
35 import org.apache.commons.lang3.StringUtils;
36 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
37 import org.opendaylight.controller.sal.core.api.mount.MountInstance;
38 import org.opendaylight.controller.sal.rest.api.Draft02;
39 import org.opendaylight.controller.sal.rest.api.RestconfService;
40 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
41 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
42 import org.opendaylight.controller.sal.restconf.rpc.impl.BrokerRpcExecutor;
43 import org.opendaylight.controller.sal.restconf.rpc.impl.MountPointRpcExecutor;
44 import org.opendaylight.controller.sal.restconf.rpc.impl.RpcExecutor;
45 import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter;
46 import org.opendaylight.controller.sal.streams.listeners.Notificator;
47 import org.opendaylight.controller.sal.streams.websockets.WebSocketServer;
48 import org.opendaylight.yangtools.concepts.Codec;
49 import org.opendaylight.yangtools.yang.common.QName;
50 import org.opendaylight.yangtools.yang.common.RpcError;
51 import org.opendaylight.yangtools.yang.common.RpcResult;
52 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
53 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
54 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder;
55 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
56 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
57 import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode;
58 import org.opendaylight.yangtools.yang.data.api.Node;
59 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
60 import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
61 import org.opendaylight.yangtools.yang.data.impl.NodeFactory;
62 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
63 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
64 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
65 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
66 import org.opendaylight.yangtools.yang.model.api.FeatureDefinition;
67 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
68 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
69 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
70 import org.opendaylight.yangtools.yang.model.api.Module;
71 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
72 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
73 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
74 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
75 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
76 import org.opendaylight.yangtools.yang.model.util.EmptyType;
77 import org.opendaylight.yangtools.yang.parser.builder.impl.ContainerSchemaNodeBuilder;
78 import org.opendaylight.yangtools.yang.parser.builder.impl.LeafSchemaNodeBuilder;
80 public class RestconfImpl implements RestconfService {
81 private enum UriParameters {
82 PRETTY_PRINT("prettyPrint"),
85 private String uriParameterName;
87 UriParameters(String uriParameterName) {
88 this.uriParameterName = uriParameterName;
92 public String toString() {
93 return uriParameterName;
97 private final static RestconfImpl INSTANCE = new RestconfImpl();
99 private static final int CHAR_NOT_FOUND = -1;
101 private final static String MOUNT_POINT_MODULE_NAME = "ietf-netconf";
103 private final static SimpleDateFormat REVISION_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
105 private final static String SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote";
107 private final static String SAL_REMOTE_RPC_SUBSRCIBE = "create-data-change-event-subscription";
109 private BrokerFacade broker;
111 private ControllerContext controllerContext;
113 public void setBroker(final BrokerFacade broker) {
114 this.broker = broker;
117 public void setControllerContext(final ControllerContext controllerContext) {
118 this.controllerContext = controllerContext;
121 private RestconfImpl() {
124 public static RestconfImpl getInstance() {
129 public StructuredData getModules(final UriInfo uriInfo) {
130 final Module restconfModule = this.getRestconfModule();
132 final List<Node<?>> modulesAsData = new ArrayList<Node<?>>();
133 final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(restconfModule,
134 Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
136 Set<Module> allModules = this.controllerContext.getAllModules();
137 for (final Module module : allModules) {
138 CompositeNode moduleCompositeNode = this.toModuleCompositeNode(module, moduleSchemaNode);
139 modulesAsData.add(moduleCompositeNode);
142 final DataSchemaNode modulesSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(restconfModule,
143 Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
144 QName qName = modulesSchemaNode.getQName();
145 final CompositeNode modulesNode = NodeFactory.createImmutableCompositeNode(qName, null, modulesAsData);
146 return new StructuredData(modulesNode, modulesSchemaNode, null, parsePrettyPrintParameter(uriInfo));
150 public StructuredData getAvailableStreams(final UriInfo uriInfo) {
151 Set<String> availableStreams = Notificator.getStreamNames();
153 final List<Node<?>> streamsAsData = new ArrayList<Node<?>>();
154 Module restconfModule = this.getRestconfModule();
155 final DataSchemaNode streamSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(restconfModule,
156 Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
157 for (final String streamName : availableStreams) {
158 streamsAsData.add(this.toStreamCompositeNode(streamName, streamSchemaNode));
161 final DataSchemaNode streamsSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(restconfModule,
162 Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
163 QName qName = streamsSchemaNode.getQName();
164 final CompositeNode streamsNode = NodeFactory.createImmutableCompositeNode(qName, null, streamsAsData);
165 return new StructuredData(streamsNode, streamsSchemaNode, null, parsePrettyPrintParameter(uriInfo));
169 public StructuredData getModules(final String identifier, final UriInfo uriInfo) {
170 Set<Module> modules = null;
171 MountInstance mountPoint = null;
172 if (identifier.contains(ControllerContext.MOUNT)) {
173 InstanceIdWithSchemaNode mountPointIdentifier = this.controllerContext.toMountPointIdentifier(identifier);
174 mountPoint = mountPointIdentifier.getMountPoint();
175 modules = this.controllerContext.getAllModules(mountPoint);
177 throw new RestconfDocumentedException(
178 "URI has bad format. If modules behind mount point should be showed, URI has to end with "
179 + ControllerContext.MOUNT, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
182 final List<Node<?>> modulesAsData = new ArrayList<Node<?>>();
183 Module restconfModule = this.getRestconfModule();
184 final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(restconfModule,
185 Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
187 for (final Module module : modules) {
188 modulesAsData.add(this.toModuleCompositeNode(module, moduleSchemaNode));
191 final DataSchemaNode modulesSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(restconfModule,
192 Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
193 QName qName = modulesSchemaNode.getQName();
194 final CompositeNode modulesNode = NodeFactory.createImmutableCompositeNode(qName, null, modulesAsData);
195 return new StructuredData(modulesNode, modulesSchemaNode, mountPoint, parsePrettyPrintParameter(uriInfo));
199 public StructuredData getModule(final String identifier, final UriInfo uriInfo) {
200 final QName moduleNameAndRevision = this.getModuleNameAndRevision(identifier);
201 Module module = null;
202 MountInstance mountPoint = null;
203 if (identifier.contains(ControllerContext.MOUNT)) {
204 InstanceIdWithSchemaNode mountPointIdentifier = this.controllerContext.toMountPointIdentifier(identifier);
205 mountPoint = mountPointIdentifier.getMountPoint();
206 module = this.controllerContext.findModuleByNameAndRevision(mountPoint, moduleNameAndRevision);
208 module = this.controllerContext.findModuleByNameAndRevision(moduleNameAndRevision);
211 if (module == null) {
212 throw new RestconfDocumentedException("Module with name '" + moduleNameAndRevision.getLocalName()
213 + "' and revision '" + moduleNameAndRevision.getRevision() + "' was not found.",
214 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
217 Module restconfModule = this.getRestconfModule();
218 final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(restconfModule,
219 Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
220 final CompositeNode moduleNode = this.toModuleCompositeNode(module, moduleSchemaNode);
221 return new StructuredData(moduleNode, moduleSchemaNode, mountPoint, parsePrettyPrintParameter(uriInfo));
225 public StructuredData getOperations(final UriInfo uriInfo) {
226 Set<Module> allModules = this.controllerContext.getAllModules();
227 return this.operationsFromModulesToStructuredData(allModules, null, parsePrettyPrintParameter(uriInfo));
231 public StructuredData getOperations(final String identifier, final UriInfo uriInfo) {
232 Set<Module> modules = null;
233 MountInstance mountPoint = null;
234 if (identifier.contains(ControllerContext.MOUNT)) {
235 InstanceIdWithSchemaNode mountPointIdentifier = this.controllerContext.toMountPointIdentifier(identifier);
236 mountPoint = mountPointIdentifier.getMountPoint();
237 modules = this.controllerContext.getAllModules(mountPoint);
239 throw new RestconfDocumentedException(
240 "URI has bad format. If operations behind mount point should be showed, URI has to end with "
241 + ControllerContext.MOUNT, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
244 return this.operationsFromModulesToStructuredData(modules, mountPoint, parsePrettyPrintParameter(uriInfo));
247 private StructuredData operationsFromModulesToStructuredData(final Set<Module> modules,
248 final MountInstance mountPoint, boolean prettyPrint) {
249 final List<Node<?>> operationsAsData = new ArrayList<Node<?>>();
250 Module restconfModule = this.getRestconfModule();
251 final DataSchemaNode operationsSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
252 restconfModule, Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE);
253 QName qName = operationsSchemaNode.getQName();
254 SchemaPath path = operationsSchemaNode.getPath();
255 ContainerSchemaNodeBuilder containerSchemaNodeBuilder = new ContainerSchemaNodeBuilder(
256 Draft02.RestConfModule.NAME, 0, qName, path);
257 final ContainerSchemaNodeBuilder fakeOperationsSchemaNode = containerSchemaNodeBuilder;
258 for (final Module module : modules) {
259 Set<RpcDefinition> rpcs = module.getRpcs();
260 for (final RpcDefinition rpc : rpcs) {
261 QName rpcQName = rpc.getQName();
262 SimpleNode<Object> immutableSimpleNode = NodeFactory.<Object> createImmutableSimpleNode(rpcQName, null,
264 operationsAsData.add(immutableSimpleNode);
266 String name = module.getName();
267 LeafSchemaNodeBuilder leafSchemaNodeBuilder = new LeafSchemaNodeBuilder(name, 0, rpcQName,
268 SchemaPath.create(true, QName.create("dummy")));
269 final LeafSchemaNodeBuilder fakeRpcSchemaNode = leafSchemaNodeBuilder;
270 fakeRpcSchemaNode.setAugmenting(true);
272 EmptyType instance = EmptyType.getInstance();
273 fakeRpcSchemaNode.setType(instance);
274 fakeOperationsSchemaNode.addChildNode(fakeRpcSchemaNode.build());
278 final CompositeNode operationsNode = NodeFactory.createImmutableCompositeNode(qName, null, operationsAsData);
279 ContainerSchemaNode schemaNode = fakeOperationsSchemaNode.build();
280 return new StructuredData(operationsNode, schemaNode, mountPoint, prettyPrint);
283 private Module getRestconfModule() {
284 Module restconfModule = controllerContext.getRestconfModule();
285 if (restconfModule == null) {
286 throw new RestconfDocumentedException("ietf-restconf module was not found.", ErrorType.APPLICATION,
287 ErrorTag.OPERATION_NOT_SUPPORTED);
290 return restconfModule;
293 private QName getModuleNameAndRevision(final String identifier) {
294 final int mountIndex = identifier.indexOf(ControllerContext.MOUNT);
295 String moduleNameAndRevision = "";
296 if (mountIndex >= 0) {
297 moduleNameAndRevision = identifier.substring(mountIndex + ControllerContext.MOUNT.length());
299 moduleNameAndRevision = identifier;
302 Splitter splitter = Splitter.on("/").omitEmptyStrings();
303 Iterable<String> split = splitter.split(moduleNameAndRevision);
304 final List<String> pathArgs = Lists.<String> newArrayList(split);
305 if (pathArgs.size() < 2) {
306 throw new RestconfDocumentedException(
307 "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'", ErrorType.PROTOCOL,
308 ErrorTag.INVALID_VALUE);
312 final String moduleName = pathArgs.get(0);
313 String revision = pathArgs.get(1);
314 final Date moduleRevision = REVISION_FORMAT.parse(revision);
315 return QName.create(null, moduleRevision, moduleName);
316 } catch (ParseException e) {
317 throw new RestconfDocumentedException("URI has bad format. It should be \'moduleName/yyyy-MM-dd\'",
318 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
322 private CompositeNode toStreamCompositeNode(final String streamName, final DataSchemaNode streamSchemaNode) {
323 final List<Node<?>> streamNodeValues = new ArrayList<Node<?>>();
324 List<DataSchemaNode> instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
325 ((DataNodeContainer) streamSchemaNode), "name");
326 final DataSchemaNode nameSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
328 .add(NodeFactory.<String> createImmutableSimpleNode(nameSchemaNode.getQName(), null, streamName));
330 instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
331 ((DataNodeContainer) streamSchemaNode), "description");
332 final DataSchemaNode descriptionSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
333 streamNodeValues.add(NodeFactory.<String> createImmutableSimpleNode(descriptionSchemaNode.getQName(), null,
334 "DESCRIPTION_PLACEHOLDER"));
336 instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
337 ((DataNodeContainer) streamSchemaNode), "replay-support");
338 final DataSchemaNode replaySupportSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
339 streamNodeValues.add(NodeFactory.<Boolean> createImmutableSimpleNode(replaySupportSchemaNode.getQName(), null,
340 Boolean.valueOf(true)));
342 instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
343 ((DataNodeContainer) streamSchemaNode), "replay-log-creation-time");
344 final DataSchemaNode replayLogCreationTimeSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
345 streamNodeValues.add(NodeFactory.<String> createImmutableSimpleNode(replayLogCreationTimeSchemaNode.getQName(),
348 instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
349 ((DataNodeContainer) streamSchemaNode), "events");
350 final DataSchemaNode eventsSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
351 streamNodeValues.add(NodeFactory.<String> createImmutableSimpleNode(eventsSchemaNode.getQName(), null, ""));
353 return NodeFactory.createImmutableCompositeNode(streamSchemaNode.getQName(), null, streamNodeValues);
356 private CompositeNode toModuleCompositeNode(final Module module, final DataSchemaNode moduleSchemaNode) {
357 final List<Node<?>> moduleNodeValues = new ArrayList<Node<?>>();
358 List<DataSchemaNode> instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
359 ((DataNodeContainer) moduleSchemaNode), "name");
360 final DataSchemaNode nameSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
361 moduleNodeValues.add(NodeFactory.<String> createImmutableSimpleNode(nameSchemaNode.getQName(), null,
364 instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
365 ((DataNodeContainer) moduleSchemaNode), "revision");
366 final DataSchemaNode revisionSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
367 Date _revision = module.getRevision();
368 moduleNodeValues.add(NodeFactory.<String> createImmutableSimpleNode(revisionSchemaNode.getQName(), null,
369 REVISION_FORMAT.format(_revision)));
371 instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
372 ((DataNodeContainer) moduleSchemaNode), "namespace");
373 final DataSchemaNode namespaceSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
374 moduleNodeValues.add(NodeFactory.<String> createImmutableSimpleNode(namespaceSchemaNode.getQName(), null,
375 module.getNamespace().toString()));
377 instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
378 ((DataNodeContainer) moduleSchemaNode), "feature");
379 final DataSchemaNode featureSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
380 for (final FeatureDefinition feature : module.getFeatures()) {
381 moduleNodeValues.add(NodeFactory.<String> createImmutableSimpleNode(featureSchemaNode.getQName(), null,
382 feature.getQName().getLocalName()));
385 return NodeFactory.createImmutableCompositeNode(moduleSchemaNode.getQName(), null, moduleNodeValues);
389 public Object getRoot() {
394 public StructuredData invokeRpc(final String identifier, final CompositeNode payload, final UriInfo uriInfo) {
395 final RpcExecutor rpc = this.resolveIdentifierInInvokeRpc(identifier);
396 QName rpcName = rpc.getRpcDefinition().getQName();
397 URI rpcNamespace = rpcName.getNamespace();
398 if (Objects.equal(rpcNamespace.toString(), SAL_REMOTE_NAMESPACE)
399 && Objects.equal(rpcName.getLocalName(), SAL_REMOTE_RPC_SUBSRCIBE)) {
400 return invokeSalRemoteRpcSubscribeRPC(payload, rpc.getRpcDefinition(), parsePrettyPrintParameter(uriInfo));
403 validateInput(rpc.getRpcDefinition().getInput(), payload);
405 return callRpc(rpc, payload, parsePrettyPrintParameter(uriInfo));
408 private void validateInput(final DataSchemaNode inputSchema, final CompositeNode payload) {
409 if (inputSchema != null && payload == null) {
410 // expected a non null payload
411 throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
412 } else if (inputSchema == null && payload != null) {
413 // did not expect any input
414 throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
418 // TODO: Validate "mandatory" and "config" values here??? Or should those be
419 // validate in a more central location inside MD-SAL core.
423 private StructuredData invokeSalRemoteRpcSubscribeRPC(final CompositeNode payload, final RpcDefinition rpc,
424 final boolean prettyPrint) {
425 final CompositeNode value = this.normalizeNode(payload, rpc.getInput(), null);
426 final SimpleNode<? extends Object> pathNode = value == null ? null : value.getFirstSimpleByName(QName.create(
427 rpc.getQName(), "path"));
428 final Object pathValue = pathNode == null ? null : pathNode.getValue();
430 if (!(pathValue instanceof InstanceIdentifier)) {
431 throw new RestconfDocumentedException("Instance identifier was not normalized correctly.",
432 ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
435 final InstanceIdentifier pathIdentifier = ((InstanceIdentifier) pathValue);
436 String streamName = null;
437 if (!Iterables.isEmpty(pathIdentifier.getPathArguments())) {
438 String fullRestconfIdentifier = this.controllerContext.toFullRestconfIdentifier(pathIdentifier);
439 streamName = Notificator.createStreamNameFromUri(fullRestconfIdentifier);
442 if (Strings.isNullOrEmpty(streamName)) {
443 throw new RestconfDocumentedException(
444 "Path is empty or contains data node which is not Container or List build-in type.",
445 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
448 final SimpleNode<String> streamNameNode = NodeFactory.<String> createImmutableSimpleNode(
449 QName.create(rpc.getOutput().getQName(), "stream-name"), null, streamName);
450 final List<Node<?>> output = new ArrayList<Node<?>>();
451 output.add(streamNameNode);
453 final MutableCompositeNode responseData = NodeFactory.createMutableCompositeNode(rpc.getOutput().getQName(),
454 null, output, null, null);
456 if (!Notificator.existListenerFor(pathIdentifier)) {
457 Notificator.createListener(pathIdentifier, streamName);
460 return new StructuredData(responseData, rpc.getOutput(), null, prettyPrint);
464 public StructuredData invokeRpc(final String identifier, final String noPayload, final UriInfo uriInfo) {
465 if (StringUtils.isNotBlank(noPayload)) {
466 throw new RestconfDocumentedException("Content must be empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
468 return invokeRpc(identifier, (CompositeNode) null, uriInfo);
471 private RpcExecutor resolveIdentifierInInvokeRpc(final String identifier) {
472 String identifierEncoded = null;
473 MountInstance mountPoint = null;
474 if (identifier.contains(ControllerContext.MOUNT)) {
475 // mounted RPC call - look up mount instance.
476 InstanceIdWithSchemaNode mountPointId = controllerContext.toMountPointIdentifier(identifier);
477 mountPoint = mountPointId.getMountPoint();
479 int startOfRemoteRpcName = identifier.lastIndexOf(ControllerContext.MOUNT)
480 + ControllerContext.MOUNT.length() + 1;
481 String remoteRpcName = identifier.substring(startOfRemoteRpcName);
482 identifierEncoded = remoteRpcName;
484 } else if (identifier.indexOf("/") != CHAR_NOT_FOUND) {
485 final String slashErrorMsg = String.format("Identifier %n%s%ncan\'t contain slash "
486 + "character (/).%nIf slash is part of identifier name then use %%2F placeholder.", identifier);
487 throw new RestconfDocumentedException(slashErrorMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
489 identifierEncoded = identifier;
492 final String identifierDecoded = controllerContext.urlPathArgDecode(identifierEncoded);
493 RpcDefinition rpc = controllerContext.getRpcDefinition(identifierDecoded);
496 throw new RestconfDocumentedException("RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT);
499 if (mountPoint == null) {
500 return new BrokerRpcExecutor(rpc, broker);
502 return new MountPointRpcExecutor(rpc, mountPoint);
507 private StructuredData callRpc(final RpcExecutor rpcExecutor, final CompositeNode payload, boolean prettyPrint) {
508 if (rpcExecutor == null) {
509 throw new RestconfDocumentedException("RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT);
512 CompositeNode rpcRequest = null;
513 RpcDefinition rpc = rpcExecutor.getRpcDefinition();
514 QName rpcName = rpc.getQName();
516 if (payload == null) {
517 rpcRequest = NodeFactory.createMutableCompositeNode(rpcName, null, null, null, null);
519 final CompositeNode value = this.normalizeNode(payload, rpc.getInput(), null);
520 List<Node<?>> input = Collections.<Node<?>> singletonList(value);
521 rpcRequest = NodeFactory.createMutableCompositeNode(rpcName, null, input, null, null);
524 RpcResult<CompositeNode> rpcResult = rpcExecutor.invokeRpc(rpcRequest);
526 checkRpcSuccessAndThrowException(rpcResult);
528 if (rpcResult.getResult() == null) {
532 if (rpc.getOutput() == null) {
533 return null; // no output, nothing to send back.
536 return new StructuredData(rpcResult.getResult(), rpc.getOutput(), null, prettyPrint);
539 private void checkRpcSuccessAndThrowException(final RpcResult<CompositeNode> rpcResult) {
540 if (rpcResult.isSuccessful() == false) {
542 Collection<RpcError> rpcErrors = rpcResult.getErrors();
543 if (rpcErrors == null || rpcErrors.isEmpty()) {
544 throw new RestconfDocumentedException(
545 "The operation was not successful and there were no RPC errors returned", ErrorType.RPC,
546 ErrorTag.OPERATION_FAILED);
549 List<RestconfError> errorList = Lists.newArrayList();
550 for (RpcError rpcError : rpcErrors) {
551 errorList.add(new RestconfError(rpcError));
554 throw new RestconfDocumentedException(errorList);
559 public StructuredData readConfigurationData(final String identifier, final UriInfo uriInfo) {
560 final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
561 CompositeNode data = null;
562 MountInstance mountPoint = iiWithData.getMountPoint();
563 if (mountPoint != null) {
564 data = broker.readConfigurationDataBehindMountPoint(mountPoint, iiWithData.getInstanceIdentifier());
566 data = broker.readConfigurationData(iiWithData.getInstanceIdentifier());
569 data = pruneDataAtDepth(data, parseDepthParameter(uriInfo));
570 boolean prettyPrintMode = parsePrettyPrintParameter(uriInfo);
571 return new StructuredData(data, iiWithData.getSchemaNode(), iiWithData.getMountPoint(), prettyPrintMode);
574 @SuppressWarnings("unchecked")
575 private <T extends Node<?>> T pruneDataAtDepth(final T node, final Integer depth) {
580 if (node instanceof CompositeNode) {
581 ImmutableList.Builder<Node<?>> newChildNodes = ImmutableList.<Node<?>> builder();
583 for (Node<?> childNode : ((CompositeNode) node).getValue()) {
584 newChildNodes.add(pruneDataAtDepth(childNode, depth - 1));
588 return (T) ImmutableCompositeNode.create(node.getNodeType(), newChildNodes.build());
589 } else { // SimpleNode
594 private Integer parseDepthParameter(final UriInfo info) {
595 String param = info.getQueryParameters(false).getFirst(UriParameters.DEPTH.toString());
596 if (Strings.isNullOrEmpty(param) || "unbounded".equals(param)) {
601 Integer depth = Integer.valueOf(param);
603 throw new RestconfDocumentedException(new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
604 "Invalid depth parameter: " + depth, null,
605 "The depth parameter must be an integer > 1 or \"unbounded\""));
609 } catch (NumberFormatException e) {
610 throw new RestconfDocumentedException(new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
611 "Invalid depth parameter: " + e.getMessage(), null,
612 "The depth parameter must be an integer > 1 or \"unbounded\""));
617 public StructuredData readOperationalData(final String identifier, final UriInfo info) {
618 final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
619 CompositeNode data = null;
620 MountInstance mountPoint = iiWithData.getMountPoint();
621 if (mountPoint != null) {
622 data = broker.readOperationalDataBehindMountPoint(mountPoint, iiWithData.getInstanceIdentifier());
624 data = broker.readOperationalData(iiWithData.getInstanceIdentifier());
627 data = pruneDataAtDepth(data, parseDepthParameter(info));
628 boolean prettyPrintMode = parsePrettyPrintParameter(info);
629 return new StructuredData(data, iiWithData.getSchemaNode(), mountPoint, prettyPrintMode);
632 private boolean parsePrettyPrintParameter(UriInfo info) {
633 String param = info.getQueryParameters(false).getFirst(UriParameters.PRETTY_PRINT.toString());
634 return Boolean.parseBoolean(param);
638 public Response updateConfigurationData(final String identifier, final CompositeNode payload) {
639 final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
641 validateInput(iiWithData.getSchemaNode(), payload);
643 MountInstance mountPoint = iiWithData.getMountPoint();
644 final CompositeNode value = this.normalizeNode(payload, iiWithData.getSchemaNode(), mountPoint);
645 validateListKeysEqualityInPayloadAndUri(iiWithData, payload);
646 RpcResult<TransactionStatus> status = null;
649 if (mountPoint != null) {
650 status = broker.commitConfigurationDataPutBehindMountPoint(mountPoint,
651 iiWithData.getInstanceIdentifier(), value).get();
653 status = broker.commitConfigurationDataPut(iiWithData.getInstanceIdentifier(), value).get();
655 } catch (Exception e) {
656 throw new RestconfDocumentedException("Error updating data", e);
659 if (status.getResult() == TransactionStatus.COMMITED) {
660 return Response.status(Status.OK).build();
663 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
667 * Validates whether keys in {@code payload} are equal to values of keys in {@code iiWithData} for list schema node
669 * @throws RestconfDocumentedException
670 * if key values or key count in payload and URI isn't equal
673 private void validateListKeysEqualityInPayloadAndUri(final InstanceIdWithSchemaNode iiWithData,
674 final CompositeNode payload) {
675 if (iiWithData.getSchemaNode() instanceof ListSchemaNode) {
676 final List<QName> keyDefinitions = ((ListSchemaNode) iiWithData.getSchemaNode()).getKeyDefinition();
677 final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
678 if (lastPathArgument instanceof NodeIdentifierWithPredicates) {
679 final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument)
681 isEqualUriAndPayloadKeyValues(uriKeyValues, payload, keyDefinitions);
686 private void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final CompositeNode payload,
687 final List<QName> keyDefinitions) {
688 for (QName keyDefinition : keyDefinitions) {
689 final Object uriKeyValue = uriKeyValues.get(keyDefinition);
690 // should be caught during parsing URI to InstanceIdentifier
691 if (uriKeyValue == null) {
692 throw new RestconfDocumentedException("Missing key " + keyDefinition + " in URI.", ErrorType.PROTOCOL,
693 ErrorTag.DATA_MISSING);
695 final List<SimpleNode<?>> payloadKeyValues = payload.getSimpleNodesByName(keyDefinition.getLocalName());
696 if (payloadKeyValues.isEmpty()) {
697 throw new RestconfDocumentedException("Missing key " + keyDefinition.getLocalName()
698 + " in the message body.", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
701 Object payloadKeyValue = payloadKeyValues.iterator().next().getValue();
702 if (!uriKeyValue.equals(payloadKeyValue)) {
703 throw new RestconfDocumentedException("The value '" + uriKeyValue + "' for key '"
704 + keyDefinition.getLocalName() + "' specified in the URI doesn't match the value '"
705 + payloadKeyValue + "' specified in the message body. ", ErrorType.PROTOCOL,
706 ErrorTag.INVALID_VALUE);
712 public Response createConfigurationData(final String identifier, final CompositeNode payload) {
713 if (payload == null) {
714 throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
717 URI payloadNS = this.namespace(payload);
718 if (payloadNS == null) {
719 throw new RestconfDocumentedException(
720 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)",
721 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE);
724 InstanceIdWithSchemaNode iiWithData = null;
725 CompositeNode value = null;
726 if (this.representsMountPointRootData(payload)) {
727 // payload represents mount point data and URI represents path to the mount point
729 if (this.endsWithMountPoint(identifier)) {
730 throw new RestconfDocumentedException("URI has bad format. URI should be without \""
731 + ControllerContext.MOUNT + "\" for POST operation.", ErrorType.PROTOCOL,
732 ErrorTag.INVALID_VALUE);
735 final String completeIdentifier = this.addMountPointIdentifier(identifier);
736 iiWithData = this.controllerContext.toInstanceIdentifier(completeIdentifier);
738 value = this.normalizeNode(payload, iiWithData.getSchemaNode(), iiWithData.getMountPoint());
740 final InstanceIdWithSchemaNode incompleteInstIdWithData = this.controllerContext
741 .toInstanceIdentifier(identifier);
742 final DataNodeContainer parentSchema = (DataNodeContainer) incompleteInstIdWithData.getSchemaNode();
743 MountInstance mountPoint = incompleteInstIdWithData.getMountPoint();
744 final Module module = this.findModule(mountPoint, payload);
745 if (module == null) {
746 throw new RestconfDocumentedException("Module was not found for \"" + payloadNS + "\"",
747 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
750 String payloadName = this.getName(payload);
751 final DataSchemaNode schemaNode = this.controllerContext.findInstanceDataChildByNameAndNamespace(
752 parentSchema, payloadName, module.getNamespace());
753 value = this.normalizeNode(payload, schemaNode, mountPoint);
755 iiWithData = this.addLastIdentifierFromData(incompleteInstIdWithData, value, schemaNode);
758 RpcResult<TransactionStatus> status = null;
759 MountInstance mountPoint = iiWithData.getMountPoint();
761 if (mountPoint != null) {
762 Future<RpcResult<TransactionStatus>> future = broker.commitConfigurationDataPostBehindMountPoint(
763 mountPoint, iiWithData.getInstanceIdentifier(), value);
764 status = future == null ? null : future.get();
766 Future<RpcResult<TransactionStatus>> future = broker.commitConfigurationDataPost(
767 iiWithData.getInstanceIdentifier(), value);
768 status = future == null ? null : future.get();
770 } catch (Exception e) {
771 throw new RestconfDocumentedException("Error creating data", e);
774 if (status == null) {
775 return Response.status(Status.ACCEPTED).build();
778 if (status.getResult() == TransactionStatus.COMMITED) {
779 return Response.status(Status.NO_CONTENT).build();
782 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
786 public Response createConfigurationData(final CompositeNode payload) {
787 if (payload == null) {
788 throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
791 URI payloadNS = this.namespace(payload);
792 if (payloadNS == null) {
793 throw new RestconfDocumentedException(
794 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)",
795 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE);
798 final Module module = this.findModule(null, payload);
799 if (module == null) {
800 throw new RestconfDocumentedException(
801 "Data has bad format. Root element node has incorrect namespace (XML format) or module name(JSON format)",
802 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE);
805 String payloadName = this.getName(payload);
806 final DataSchemaNode schemaNode = this.controllerContext.findInstanceDataChildByNameAndNamespace(module,
807 payloadName, module.getNamespace());
808 final CompositeNode value = this.normalizeNode(payload, schemaNode, null);
809 final InstanceIdWithSchemaNode iiWithData = this.addLastIdentifierFromData(null, value, schemaNode);
810 RpcResult<TransactionStatus> status = null;
811 MountInstance mountPoint = iiWithData.getMountPoint();
814 if (mountPoint != null) {
815 Future<RpcResult<TransactionStatus>> future = broker.commitConfigurationDataPostBehindMountPoint(
816 mountPoint, iiWithData.getInstanceIdentifier(), value);
817 status = future == null ? null : future.get();
819 Future<RpcResult<TransactionStatus>> future = broker.commitConfigurationDataPost(
820 iiWithData.getInstanceIdentifier(), value);
821 status = future == null ? null : future.get();
823 } catch (Exception e) {
824 throw new RestconfDocumentedException("Error creating data", e);
827 if (status == null) {
828 return Response.status(Status.ACCEPTED).build();
831 if (status.getResult() == TransactionStatus.COMMITED) {
832 return Response.status(Status.NO_CONTENT).build();
835 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
839 public Response deleteConfigurationData(final String identifier) {
840 final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
841 RpcResult<TransactionStatus> status = null;
842 MountInstance mountPoint = iiWithData.getMountPoint();
845 if (mountPoint != null) {
846 status = broker.commitConfigurationDataDeleteBehindMountPoint(mountPoint,
847 iiWithData.getInstanceIdentifier()).get();
849 status = broker.commitConfigurationDataDelete(iiWithData.getInstanceIdentifier()).get();
851 } catch (Exception e) {
852 throw new RestconfDocumentedException("Error creating data", e);
855 if (status.getResult() == TransactionStatus.COMMITED) {
856 return Response.status(Status.OK).build();
859 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
863 public Response subscribeToStream(final String identifier, final UriInfo uriInfo) {
864 final String streamName = Notificator.createStreamNameFromUri(identifier);
865 if (Strings.isNullOrEmpty(streamName)) {
866 throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
869 final ListenerAdapter listener = Notificator.getListenerFor(streamName);
870 if (listener == null) {
871 throw new RestconfDocumentedException("Stream was not found.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
874 broker.registerToListenDataChanges(listener);
876 final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
877 UriBuilder port = uriBuilder.port(WebSocketServer.getInstance().getPort());
878 final URI uriToWebsocketServer = port.replacePath(streamName).build();
880 return Response.status(Status.OK).location(uriToWebsocketServer).build();
883 private Module findModule(final MountInstance mountPoint, final CompositeNode data) {
884 if (data instanceof CompositeNodeWrapper) {
885 return findModule(mountPoint, (CompositeNodeWrapper) data);
886 } else if (data != null) {
887 URI namespace = data.getNodeType().getNamespace();
888 if (mountPoint != null) {
889 return this.controllerContext.findModuleByNamespace(mountPoint, namespace);
891 return this.controllerContext.findModuleByNamespace(namespace);
894 throw new IllegalArgumentException("Unhandled parameter types: "
895 + Arrays.<Object> asList(mountPoint, data).toString());
899 private Module findModule(final MountInstance mountPoint, final CompositeNodeWrapper data) {
900 URI namespace = data.getNamespace();
901 Preconditions.<URI> checkNotNull(namespace);
903 Module module = null;
904 if (mountPoint != null) {
905 module = this.controllerContext.findModuleByNamespace(mountPoint, namespace);
906 if (module == null) {
907 module = this.controllerContext.findModuleByName(mountPoint, namespace.toString());
910 module = this.controllerContext.findModuleByNamespace(namespace);
911 if (module == null) {
912 module = this.controllerContext.findModuleByName(namespace.toString());
919 private InstanceIdWithSchemaNode addLastIdentifierFromData(final InstanceIdWithSchemaNode identifierWithSchemaNode,
920 final CompositeNode data, final DataSchemaNode schemaOfData) {
921 InstanceIdentifier instanceIdentifier = null;
922 if (identifierWithSchemaNode != null) {
923 instanceIdentifier = identifierWithSchemaNode.getInstanceIdentifier();
926 final InstanceIdentifier iiOriginal = instanceIdentifier;
927 InstanceIdentifierBuilder iiBuilder = null;
928 if (iiOriginal == null) {
929 iiBuilder = InstanceIdentifier.builder();
931 iiBuilder = InstanceIdentifier.builder(iiOriginal);
934 if ((schemaOfData instanceof ListSchemaNode)) {
935 HashMap<QName, Object> keys = this.resolveKeysFromData(((ListSchemaNode) schemaOfData), data);
936 iiBuilder.nodeWithKey(schemaOfData.getQName(), keys);
938 iiBuilder.node(schemaOfData.getQName());
941 InstanceIdentifier instance = iiBuilder.toInstance();
942 MountInstance mountPoint = null;
943 if (identifierWithSchemaNode != null) {
944 mountPoint = identifierWithSchemaNode.getMountPoint();
947 return new InstanceIdWithSchemaNode(instance, schemaOfData, mountPoint);
950 private HashMap<QName, Object> resolveKeysFromData(final ListSchemaNode listNode, final CompositeNode dataNode) {
951 final HashMap<QName, Object> keyValues = new HashMap<QName, Object>();
952 List<QName> _keyDefinition = listNode.getKeyDefinition();
953 for (final QName key : _keyDefinition) {
954 SimpleNode<? extends Object> head = null;
955 String localName = key.getLocalName();
956 List<SimpleNode<? extends Object>> simpleNodesByName = dataNode.getSimpleNodesByName(localName);
957 if (simpleNodesByName != null) {
958 head = Iterables.getFirst(simpleNodesByName, null);
961 Object dataNodeKeyValueObject = null;
963 dataNodeKeyValueObject = head.getValue();
966 if (dataNodeKeyValueObject == null) {
967 throw new RestconfDocumentedException("Data contains list \"" + dataNode.getNodeType().getLocalName()
968 + "\" which does not contain key: \"" + key.getLocalName() + "\"", ErrorType.PROTOCOL,
969 ErrorTag.INVALID_VALUE);
972 keyValues.put(key, dataNodeKeyValueObject);
978 private boolean endsWithMountPoint(final String identifier) {
979 return identifier.endsWith(ControllerContext.MOUNT) || identifier.endsWith(ControllerContext.MOUNT + "/");
982 private boolean representsMountPointRootData(final CompositeNode data) {
983 URI namespace = this.namespace(data);
984 return (SchemaContext.NAME.getNamespace().equals(namespace) /*
985 * || MOUNT_POINT_MODULE_NAME .equals( namespace .
988 && SchemaContext.NAME.getLocalName().equals(this.localName(data));
991 private String addMountPointIdentifier(final String identifier) {
992 boolean endsWith = identifier.endsWith("/");
994 return (identifier + ControllerContext.MOUNT);
997 return identifier + "/" + ControllerContext.MOUNT;
1000 private CompositeNode normalizeNode(final CompositeNode node, final DataSchemaNode schema,
1001 final MountInstance mountPoint) {
1002 if (schema == null) {
1003 QName nodeType = node == null ? null : node.getNodeType();
1004 String localName = nodeType == null ? null : nodeType.getLocalName();
1006 throw new RestconfDocumentedException("Data schema node was not found for " + localName,
1007 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1010 if (!(schema instanceof DataNodeContainer)) {
1011 throw new RestconfDocumentedException("Root element has to be container or list yang datatype.",
1012 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1015 if ((node instanceof CompositeNodeWrapper)) {
1016 boolean isChangeAllowed = ((CompositeNodeWrapper) node).isChangeAllowed();
1017 if (isChangeAllowed) {
1019 this.normalizeNode(((CompositeNodeWrapper) node), schema, null, mountPoint);
1020 } catch (IllegalArgumentException e) {
1021 throw new RestconfDocumentedException(e.getMessage(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1025 return ((CompositeNodeWrapper) node).unwrap();
1031 private void normalizeNode(final NodeWrapper<? extends Object> nodeBuilder, final DataSchemaNode schema,
1032 final QName previousAugment, final MountInstance mountPoint) {
1033 if (schema == null) {
1034 throw new RestconfDocumentedException("Data has bad format.\n\"" + nodeBuilder.getLocalName()
1035 + "\" does not exist in yang schema.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1038 QName currentAugment = null;
1039 if (nodeBuilder.getQname() != null) {
1040 currentAugment = previousAugment;
1042 currentAugment = this.normalizeNodeName(nodeBuilder, schema, previousAugment, mountPoint);
1043 if (nodeBuilder.getQname() == null) {
1044 throw new RestconfDocumentedException(
1045 "Data has bad format.\nIf data is in XML format then namespace for \""
1046 + nodeBuilder.getLocalName() + "\" should be \"" + schema.getQName().getNamespace()
1047 + "\".\n" + "If data is in JSON format then module name for \""
1048 + nodeBuilder.getLocalName() + "\" should be corresponding to namespace \""
1049 + schema.getQName().getNamespace() + "\".", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1053 if (nodeBuilder instanceof CompositeNodeWrapper) {
1054 if (schema instanceof DataNodeContainer) {
1055 normalizeCompositeNode((CompositeNodeWrapper) nodeBuilder, (DataNodeContainer) schema, mountPoint,
1057 } else if (schema instanceof AnyXmlSchemaNode) {
1058 normalizeAnyXmlNode((CompositeNodeWrapper) nodeBuilder, (AnyXmlSchemaNode) schema);
1060 } else if (nodeBuilder instanceof SimpleNodeWrapper) {
1061 normalizeSimpleNode((SimpleNodeWrapper) nodeBuilder, schema, mountPoint);
1062 } else if ((nodeBuilder instanceof EmptyNodeWrapper)) {
1063 normalizeEmptyNode((EmptyNodeWrapper) nodeBuilder, schema);
1067 private void normalizeAnyXmlNode(final CompositeNodeWrapper compositeNode, final AnyXmlSchemaNode schema) {
1068 List<NodeWrapper<?>> children = compositeNode.getValues();
1069 for (NodeWrapper<? extends Object> child : children) {
1070 child.setNamespace(schema.getQName().getNamespace());
1071 if (child instanceof CompositeNodeWrapper) {
1072 normalizeAnyXmlNode((CompositeNodeWrapper) child, schema);
1077 private void normalizeEmptyNode(final EmptyNodeWrapper emptyNodeBuilder, final DataSchemaNode schema) {
1078 if ((schema instanceof LeafSchemaNode)) {
1079 emptyNodeBuilder.setComposite(false);
1081 if ((schema instanceof ContainerSchemaNode)) {
1082 // FIXME: Add presence check
1083 emptyNodeBuilder.setComposite(true);
1088 private void normalizeSimpleNode(final SimpleNodeWrapper simpleNode, final DataSchemaNode schema,
1089 final MountInstance mountPoint) {
1090 final Object value = simpleNode.getValue();
1091 Object inputValue = value;
1092 TypeDefinition<? extends Object> typeDefinition = this.typeDefinition(schema);
1093 if ((typeDefinition instanceof IdentityrefTypeDefinition)) {
1094 if ((value instanceof String)) {
1095 inputValue = new IdentityValuesDTO(simpleNode.getNamespace().toString(), (String) value, null,
1097 } // else value is already instance of IdentityValuesDTO
1100 Object outputValue = inputValue;
1102 if (typeDefinition != null) {
1103 Codec<Object, Object> codec = RestCodec.from(typeDefinition, mountPoint);
1104 outputValue = codec == null ? null : codec.deserialize(inputValue);
1107 simpleNode.setValue(outputValue);
1110 private void normalizeCompositeNode(final CompositeNodeWrapper compositeNodeBuilder,
1111 final DataNodeContainer schema, final MountInstance mountPoint, final QName currentAugment) {
1112 final List<NodeWrapper<?>> children = compositeNodeBuilder.getValues();
1113 checkNodeMultiplicityAccordingToSchema(schema, children);
1114 for (final NodeWrapper<? extends Object> child : children) {
1115 final List<DataSchemaNode> potentialSchemaNodes = this.controllerContext.findInstanceDataChildrenByName(
1116 schema, child.getLocalName());
1118 if (potentialSchemaNodes.size() > 1 && child.getNamespace() == null) {
1119 StringBuilder builder = new StringBuilder();
1120 for (final DataSchemaNode potentialSchemaNode : potentialSchemaNodes) {
1121 builder.append(" ").append(potentialSchemaNode.getQName().getNamespace().toString()).append("\n");
1124 throw new RestconfDocumentedException("Node \"" + child.getLocalName()
1125 + "\" is added as augment from more than one module. "
1126 + "Therefore node must have namespace (XML format) or module name (JSON format)."
1127 + "\nThe node is added as augment from modules with namespaces:\n" + builder,
1128 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1131 boolean rightNodeSchemaFound = false;
1132 for (final DataSchemaNode potentialSchemaNode : potentialSchemaNodes) {
1133 if (!rightNodeSchemaFound) {
1134 final QName potentialCurrentAugment = this.normalizeNodeName(child, potentialSchemaNode,
1135 currentAugment, mountPoint);
1136 if (child.getQname() != null) {
1137 this.normalizeNode(child, potentialSchemaNode, potentialCurrentAugment, mountPoint);
1138 rightNodeSchemaFound = true;
1143 if (!rightNodeSchemaFound) {
1144 throw new RestconfDocumentedException("Schema node \"" + child.getLocalName()
1145 + "\" was not found in module.", ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT);
1149 if ((schema instanceof ListSchemaNode)) {
1150 ListSchemaNode listSchemaNode = (ListSchemaNode) schema;
1151 final List<QName> listKeys = listSchemaNode.getKeyDefinition();
1152 for (final QName listKey : listKeys) {
1153 boolean foundKey = false;
1154 for (final NodeWrapper<? extends Object> child : children) {
1155 if (Objects.equal(child.unwrap().getNodeType().getLocalName(), listKey.getLocalName())) {
1161 throw new RestconfDocumentedException("Missing key in URI \"" + listKey.getLocalName()
1162 + "\" of list \"" + listSchemaNode.getQName().getLocalName() + "\"", ErrorType.PROTOCOL,
1163 ErrorTag.DATA_MISSING);
1169 private void checkNodeMultiplicityAccordingToSchema(final DataNodeContainer dataNodeContainer,
1170 final List<NodeWrapper<?>> nodes) {
1171 Map<String, Integer> equalNodeNamesToCounts = new HashMap<String, Integer>();
1172 for (NodeWrapper<?> child : nodes) {
1173 Integer count = equalNodeNamesToCounts.get(child.getLocalName());
1174 equalNodeNamesToCounts.put(child.getLocalName(), count == null ? 1 : ++count);
1177 for (DataSchemaNode childSchemaNode : dataNodeContainer.getChildNodes()) {
1178 if (childSchemaNode instanceof ContainerSchemaNode || childSchemaNode instanceof LeafSchemaNode) {
1179 String localName = childSchemaNode.getQName().getLocalName();
1180 Integer count = equalNodeNamesToCounts.get(localName);
1181 if (count != null && count > 1) {
1182 throw new RestconfDocumentedException("Multiple input data elements were specified for '"
1183 + childSchemaNode.getQName().getLocalName()
1184 + "'. The data for this element type can only be specified once.", ErrorType.APPLICATION,
1185 ErrorTag.BAD_ELEMENT);
1191 private QName normalizeNodeName(final NodeWrapper<? extends Object> nodeBuilder, final DataSchemaNode schema,
1192 final QName previousAugment, final MountInstance mountPoint) {
1193 QName validQName = schema.getQName();
1194 QName currentAugment = previousAugment;
1195 if (schema.isAugmenting()) {
1196 currentAugment = schema.getQName();
1197 } else if (previousAugment != null
1198 && !Objects.equal(schema.getQName().getNamespace(), previousAugment.getNamespace())) {
1199 validQName = QName.create(currentAugment, schema.getQName().getLocalName());
1202 String moduleName = null;
1203 if (mountPoint == null) {
1204 moduleName = controllerContext.findModuleNameByNamespace(validQName.getNamespace());
1206 moduleName = controllerContext.findModuleNameByNamespace(mountPoint, validQName.getNamespace());
1209 if (nodeBuilder.getNamespace() == null || Objects.equal(nodeBuilder.getNamespace(), validQName.getNamespace())
1210 || Objects.equal(nodeBuilder.getNamespace().toString(), moduleName) /*
1211 * || Note : this check is wrong -
1212 * can never be true as it compares
1213 * a URI with a String not sure what
1214 * the intention is so commented out
1215 * ... Objects . equal ( nodeBuilder
1216 * . getNamespace ( ) ,
1217 * MOUNT_POINT_MODULE_NAME )
1220 nodeBuilder.setQname(validQName);
1223 return currentAugment;
1226 private URI namespace(final CompositeNode data) {
1227 if (data instanceof CompositeNodeWrapper) {
1228 return ((CompositeNodeWrapper) data).getNamespace();
1229 } else if (data != null) {
1230 return data.getNodeType().getNamespace();
1232 throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(data).toString());
1236 private String localName(final CompositeNode data) {
1237 if (data instanceof CompositeNodeWrapper) {
1238 return ((CompositeNodeWrapper) data).getLocalName();
1239 } else if (data != null) {
1240 return data.getNodeType().getLocalName();
1242 throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(data).toString());
1246 private String getName(final CompositeNode data) {
1247 if (data instanceof CompositeNodeWrapper) {
1248 return ((CompositeNodeWrapper) data).getLocalName();
1249 } else if (data != null) {
1250 return data.getNodeType().getLocalName();
1252 throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(data).toString());
1256 private TypeDefinition<? extends Object> _typeDefinition(final LeafSchemaNode node) {
1257 TypeDefinition<?> baseType = node.getType();
1258 while (baseType.getBaseType() != null) {
1259 baseType = baseType.getBaseType();
1265 private TypeDefinition<? extends Object> typeDefinition(final LeafListSchemaNode node) {
1266 TypeDefinition<?> baseType = node.getType();
1267 while (baseType.getBaseType() != null) {
1268 baseType = baseType.getBaseType();
1274 private TypeDefinition<? extends Object> typeDefinition(final DataSchemaNode node) {
1275 if (node instanceof LeafListSchemaNode) {
1276 return typeDefinition((LeafListSchemaNode) node);
1277 } else if (node instanceof LeafSchemaNode) {
1278 return _typeDefinition((LeafSchemaNode) node);
1279 } else if (node instanceof AnyXmlSchemaNode) {
1282 throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(node).toString());