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);
494 RpcDefinition rpc = null;
495 if (mountPoint == null) {
496 rpc = controllerContext.getRpcDefinition(identifierDecoded);
498 rpc = findRpc(mountPoint.getSchemaContext(), identifierDecoded);
502 throw new RestconfDocumentedException("RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT);
505 if (mountPoint == null) {
506 return new BrokerRpcExecutor(rpc, broker);
508 return new MountPointRpcExecutor(rpc, mountPoint);
513 private RpcDefinition findRpc(final SchemaContext schemaContext, final String identifierDecoded) {
514 final String[] splittedIdentifier = identifierDecoded.split(":");
515 if (splittedIdentifier.length != 2) {
516 throw new RestconfDocumentedException(identifierDecoded
517 + " couldn't be splitted to 2 parts (module:rpc name)", ErrorType.APPLICATION,
518 ErrorTag.INVALID_VALUE);
520 for (Module module : schemaContext.getModules()) {
521 if (module.getName().equals(splittedIdentifier[0])) {
522 for (RpcDefinition rpcDefinition : module.getRpcs()) {
523 if (rpcDefinition.getQName().getLocalName().equals(splittedIdentifier[1])) {
524 return rpcDefinition;
532 private StructuredData callRpc(final RpcExecutor rpcExecutor, final CompositeNode payload, boolean prettyPrint) {
533 if (rpcExecutor == null) {
534 throw new RestconfDocumentedException("RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT);
537 CompositeNode rpcRequest = null;
538 RpcDefinition rpc = rpcExecutor.getRpcDefinition();
539 QName rpcName = rpc.getQName();
541 if (payload == null) {
542 rpcRequest = NodeFactory.createMutableCompositeNode(rpcName, null, null, null, null);
544 final CompositeNode value = this.normalizeNode(payload, rpc.getInput(), null);
545 List<Node<?>> input = Collections.<Node<?>> singletonList(value);
546 rpcRequest = NodeFactory.createMutableCompositeNode(rpcName, null, input, null, null);
549 RpcResult<CompositeNode> rpcResult = rpcExecutor.invokeRpc(rpcRequest);
551 checkRpcSuccessAndThrowException(rpcResult);
553 if (rpcResult.getResult() == null) {
557 if (rpc.getOutput() == null) {
558 return null; // no output, nothing to send back.
561 return new StructuredData(rpcResult.getResult(), rpc.getOutput(), null, prettyPrint);
564 private void checkRpcSuccessAndThrowException(final RpcResult<CompositeNode> rpcResult) {
565 if (rpcResult.isSuccessful() == false) {
567 Collection<RpcError> rpcErrors = rpcResult.getErrors();
568 if (rpcErrors == null || rpcErrors.isEmpty()) {
569 throw new RestconfDocumentedException(
570 "The operation was not successful and there were no RPC errors returned", ErrorType.RPC,
571 ErrorTag.OPERATION_FAILED);
574 List<RestconfError> errorList = Lists.newArrayList();
575 for (RpcError rpcError : rpcErrors) {
576 errorList.add(new RestconfError(rpcError));
579 throw new RestconfDocumentedException(errorList);
584 public StructuredData readConfigurationData(final String identifier, final UriInfo uriInfo) {
585 final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
586 CompositeNode data = null;
587 MountInstance mountPoint = iiWithData.getMountPoint();
588 if (mountPoint != null) {
589 data = broker.readConfigurationDataBehindMountPoint(mountPoint, iiWithData.getInstanceIdentifier());
591 data = broker.readConfigurationData(iiWithData.getInstanceIdentifier());
594 data = pruneDataAtDepth(data, parseDepthParameter(uriInfo));
595 boolean prettyPrintMode = parsePrettyPrintParameter(uriInfo);
596 return new StructuredData(data, iiWithData.getSchemaNode(), iiWithData.getMountPoint(), prettyPrintMode);
599 @SuppressWarnings("unchecked")
600 private <T extends Node<?>> T pruneDataAtDepth(final T node, final Integer depth) {
605 if (node instanceof CompositeNode) {
606 ImmutableList.Builder<Node<?>> newChildNodes = ImmutableList.<Node<?>> builder();
608 for (Node<?> childNode : ((CompositeNode) node).getValue()) {
609 newChildNodes.add(pruneDataAtDepth(childNode, depth - 1));
613 return (T) ImmutableCompositeNode.create(node.getNodeType(), newChildNodes.build());
614 } else { // SimpleNode
619 private Integer parseDepthParameter(final UriInfo info) {
620 String param = info.getQueryParameters(false).getFirst(UriParameters.DEPTH.toString());
621 if (Strings.isNullOrEmpty(param) || "unbounded".equals(param)) {
626 Integer depth = Integer.valueOf(param);
628 throw new RestconfDocumentedException(new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
629 "Invalid depth parameter: " + depth, null,
630 "The depth parameter must be an integer > 1 or \"unbounded\""));
634 } catch (NumberFormatException e) {
635 throw new RestconfDocumentedException(new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
636 "Invalid depth parameter: " + e.getMessage(), null,
637 "The depth parameter must be an integer > 1 or \"unbounded\""));
642 public StructuredData readOperationalData(final String identifier, final UriInfo info) {
643 final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
644 CompositeNode data = null;
645 MountInstance mountPoint = iiWithData.getMountPoint();
646 if (mountPoint != null) {
647 data = broker.readOperationalDataBehindMountPoint(mountPoint, iiWithData.getInstanceIdentifier());
649 data = broker.readOperationalData(iiWithData.getInstanceIdentifier());
652 data = pruneDataAtDepth(data, parseDepthParameter(info));
653 boolean prettyPrintMode = parsePrettyPrintParameter(info);
654 return new StructuredData(data, iiWithData.getSchemaNode(), mountPoint, prettyPrintMode);
657 private boolean parsePrettyPrintParameter(UriInfo info) {
658 String param = info.getQueryParameters(false).getFirst(UriParameters.PRETTY_PRINT.toString());
659 return Boolean.parseBoolean(param);
663 public Response updateConfigurationData(final String identifier, final CompositeNode payload) {
664 final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
666 validateInput(iiWithData.getSchemaNode(), payload);
668 MountInstance mountPoint = iiWithData.getMountPoint();
669 final CompositeNode value = this.normalizeNode(payload, iiWithData.getSchemaNode(), mountPoint);
670 validateListKeysEqualityInPayloadAndUri(iiWithData, payload);
671 RpcResult<TransactionStatus> status = null;
674 if (mountPoint != null) {
675 status = broker.commitConfigurationDataPutBehindMountPoint(mountPoint,
676 iiWithData.getInstanceIdentifier(), value).get();
678 status = broker.commitConfigurationDataPut(iiWithData.getInstanceIdentifier(), value).get();
680 } catch (Exception e) {
681 throw new RestconfDocumentedException("Error updating data", e);
684 if (status.getResult() == TransactionStatus.COMMITED) {
685 return Response.status(Status.OK).build();
688 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
692 * Validates whether keys in {@code payload} are equal to values of keys in {@code iiWithData} for list schema node
694 * @throws RestconfDocumentedException
695 * if key values or key count in payload and URI isn't equal
698 private void validateListKeysEqualityInPayloadAndUri(final InstanceIdWithSchemaNode iiWithData,
699 final CompositeNode payload) {
700 if (iiWithData.getSchemaNode() instanceof ListSchemaNode) {
701 final List<QName> keyDefinitions = ((ListSchemaNode) iiWithData.getSchemaNode()).getKeyDefinition();
702 final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
703 if (lastPathArgument instanceof NodeIdentifierWithPredicates) {
704 final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument)
706 isEqualUriAndPayloadKeyValues(uriKeyValues, payload, keyDefinitions);
711 private void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final CompositeNode payload,
712 final List<QName> keyDefinitions) {
713 for (QName keyDefinition : keyDefinitions) {
714 final Object uriKeyValue = uriKeyValues.get(keyDefinition);
715 // should be caught during parsing URI to InstanceIdentifier
716 if (uriKeyValue == null) {
717 throw new RestconfDocumentedException("Missing key " + keyDefinition + " in URI.", ErrorType.PROTOCOL,
718 ErrorTag.DATA_MISSING);
720 final List<SimpleNode<?>> payloadKeyValues = payload.getSimpleNodesByName(keyDefinition.getLocalName());
721 if (payloadKeyValues.isEmpty()) {
722 throw new RestconfDocumentedException("Missing key " + keyDefinition.getLocalName()
723 + " in the message body.", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
726 Object payloadKeyValue = payloadKeyValues.iterator().next().getValue();
727 if (!uriKeyValue.equals(payloadKeyValue)) {
728 throw new RestconfDocumentedException("The value '" + uriKeyValue + "' for key '"
729 + keyDefinition.getLocalName() + "' specified in the URI doesn't match the value '"
730 + payloadKeyValue + "' specified in the message body. ", ErrorType.PROTOCOL,
731 ErrorTag.INVALID_VALUE);
737 public Response createConfigurationData(final String identifier, final CompositeNode payload) {
738 if (payload == null) {
739 throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
742 URI payloadNS = this.namespace(payload);
743 if (payloadNS == null) {
744 throw new RestconfDocumentedException(
745 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)",
746 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE);
749 InstanceIdWithSchemaNode iiWithData = null;
750 CompositeNode value = null;
751 if (this.representsMountPointRootData(payload)) {
752 // payload represents mount point data and URI represents path to the mount point
754 if (this.endsWithMountPoint(identifier)) {
755 throw new RestconfDocumentedException("URI has bad format. URI should be without \""
756 + ControllerContext.MOUNT + "\" for POST operation.", ErrorType.PROTOCOL,
757 ErrorTag.INVALID_VALUE);
760 final String completeIdentifier = this.addMountPointIdentifier(identifier);
761 iiWithData = this.controllerContext.toInstanceIdentifier(completeIdentifier);
763 value = this.normalizeNode(payload, iiWithData.getSchemaNode(), iiWithData.getMountPoint());
765 final InstanceIdWithSchemaNode incompleteInstIdWithData = this.controllerContext
766 .toInstanceIdentifier(identifier);
767 final DataNodeContainer parentSchema = (DataNodeContainer) incompleteInstIdWithData.getSchemaNode();
768 MountInstance mountPoint = incompleteInstIdWithData.getMountPoint();
769 final Module module = this.findModule(mountPoint, payload);
770 if (module == null) {
771 throw new RestconfDocumentedException("Module was not found for \"" + payloadNS + "\"",
772 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
775 String payloadName = this.getName(payload);
776 final DataSchemaNode schemaNode = this.controllerContext.findInstanceDataChildByNameAndNamespace(
777 parentSchema, payloadName, module.getNamespace());
778 value = this.normalizeNode(payload, schemaNode, mountPoint);
780 iiWithData = this.addLastIdentifierFromData(incompleteInstIdWithData, value, schemaNode);
783 RpcResult<TransactionStatus> status = null;
784 MountInstance mountPoint = iiWithData.getMountPoint();
786 if (mountPoint != null) {
787 Future<RpcResult<TransactionStatus>> future = broker.commitConfigurationDataPostBehindMountPoint(
788 mountPoint, iiWithData.getInstanceIdentifier(), value);
789 status = future == null ? null : future.get();
791 Future<RpcResult<TransactionStatus>> future = broker.commitConfigurationDataPost(
792 iiWithData.getInstanceIdentifier(), value);
793 status = future == null ? null : future.get();
795 } catch (Exception e) {
796 throw new RestconfDocumentedException("Error creating data", e);
799 if (status == null) {
800 return Response.status(Status.ACCEPTED).build();
803 if (status.getResult() == TransactionStatus.COMMITED) {
804 return Response.status(Status.NO_CONTENT).build();
807 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
811 public Response createConfigurationData(final CompositeNode payload) {
812 if (payload == null) {
813 throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
816 URI payloadNS = this.namespace(payload);
817 if (payloadNS == null) {
818 throw new RestconfDocumentedException(
819 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)",
820 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE);
823 final Module module = this.findModule(null, payload);
824 if (module == null) {
825 throw new RestconfDocumentedException(
826 "Data has bad format. Root element node has incorrect namespace (XML format) or module name(JSON format)",
827 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE);
830 String payloadName = this.getName(payload);
831 final DataSchemaNode schemaNode = this.controllerContext.findInstanceDataChildByNameAndNamespace(module,
832 payloadName, module.getNamespace());
833 final CompositeNode value = this.normalizeNode(payload, schemaNode, null);
834 final InstanceIdWithSchemaNode iiWithData = this.addLastIdentifierFromData(null, value, schemaNode);
835 RpcResult<TransactionStatus> status = null;
836 MountInstance mountPoint = iiWithData.getMountPoint();
839 if (mountPoint != null) {
840 Future<RpcResult<TransactionStatus>> future = broker.commitConfigurationDataPostBehindMountPoint(
841 mountPoint, iiWithData.getInstanceIdentifier(), value);
842 status = future == null ? null : future.get();
844 Future<RpcResult<TransactionStatus>> future = broker.commitConfigurationDataPost(
845 iiWithData.getInstanceIdentifier(), value);
846 status = future == null ? null : future.get();
848 } catch (Exception e) {
849 throw new RestconfDocumentedException("Error creating data", e);
852 if (status == null) {
853 return Response.status(Status.ACCEPTED).build();
856 if (status.getResult() == TransactionStatus.COMMITED) {
857 return Response.status(Status.NO_CONTENT).build();
860 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
864 public Response deleteConfigurationData(final String identifier) {
865 final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
866 RpcResult<TransactionStatus> status = null;
867 MountInstance mountPoint = iiWithData.getMountPoint();
870 if (mountPoint != null) {
871 status = broker.commitConfigurationDataDeleteBehindMountPoint(mountPoint,
872 iiWithData.getInstanceIdentifier()).get();
874 status = broker.commitConfigurationDataDelete(iiWithData.getInstanceIdentifier()).get();
876 } catch (Exception e) {
877 throw new RestconfDocumentedException("Error creating data", e);
880 if (status.getResult() == TransactionStatus.COMMITED) {
881 return Response.status(Status.OK).build();
884 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
888 public Response subscribeToStream(final String identifier, final UriInfo uriInfo) {
889 final String streamName = Notificator.createStreamNameFromUri(identifier);
890 if (Strings.isNullOrEmpty(streamName)) {
891 throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
894 final ListenerAdapter listener = Notificator.getListenerFor(streamName);
895 if (listener == null) {
896 throw new RestconfDocumentedException("Stream was not found.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
899 broker.registerToListenDataChanges(listener);
901 final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
902 UriBuilder port = uriBuilder.port(WebSocketServer.getInstance().getPort());
903 final URI uriToWebsocketServer = port.replacePath(streamName).build();
905 return Response.status(Status.OK).location(uriToWebsocketServer).build();
908 private Module findModule(final MountInstance mountPoint, final CompositeNode data) {
909 if (data instanceof CompositeNodeWrapper) {
910 return findModule(mountPoint, (CompositeNodeWrapper) data);
911 } else if (data != null) {
912 URI namespace = data.getNodeType().getNamespace();
913 if (mountPoint != null) {
914 return this.controllerContext.findModuleByNamespace(mountPoint, namespace);
916 return this.controllerContext.findModuleByNamespace(namespace);
919 throw new IllegalArgumentException("Unhandled parameter types: "
920 + Arrays.<Object> asList(mountPoint, data).toString());
924 private Module findModule(final MountInstance mountPoint, final CompositeNodeWrapper data) {
925 URI namespace = data.getNamespace();
926 Preconditions.<URI> checkNotNull(namespace);
928 Module module = null;
929 if (mountPoint != null) {
930 module = this.controllerContext.findModuleByNamespace(mountPoint, namespace);
931 if (module == null) {
932 module = this.controllerContext.findModuleByName(mountPoint, namespace.toString());
935 module = this.controllerContext.findModuleByNamespace(namespace);
936 if (module == null) {
937 module = this.controllerContext.findModuleByName(namespace.toString());
944 private InstanceIdWithSchemaNode addLastIdentifierFromData(final InstanceIdWithSchemaNode identifierWithSchemaNode,
945 final CompositeNode data, final DataSchemaNode schemaOfData) {
946 InstanceIdentifier instanceIdentifier = null;
947 if (identifierWithSchemaNode != null) {
948 instanceIdentifier = identifierWithSchemaNode.getInstanceIdentifier();
951 final InstanceIdentifier iiOriginal = instanceIdentifier;
952 InstanceIdentifierBuilder iiBuilder = null;
953 if (iiOriginal == null) {
954 iiBuilder = InstanceIdentifier.builder();
956 iiBuilder = InstanceIdentifier.builder(iiOriginal);
959 if ((schemaOfData instanceof ListSchemaNode)) {
960 HashMap<QName, Object> keys = this.resolveKeysFromData(((ListSchemaNode) schemaOfData), data);
961 iiBuilder.nodeWithKey(schemaOfData.getQName(), keys);
963 iiBuilder.node(schemaOfData.getQName());
966 InstanceIdentifier instance = iiBuilder.toInstance();
967 MountInstance mountPoint = null;
968 if (identifierWithSchemaNode != null) {
969 mountPoint = identifierWithSchemaNode.getMountPoint();
972 return new InstanceIdWithSchemaNode(instance, schemaOfData, mountPoint);
975 private HashMap<QName, Object> resolveKeysFromData(final ListSchemaNode listNode, final CompositeNode dataNode) {
976 final HashMap<QName, Object> keyValues = new HashMap<QName, Object>();
977 List<QName> _keyDefinition = listNode.getKeyDefinition();
978 for (final QName key : _keyDefinition) {
979 SimpleNode<? extends Object> head = null;
980 String localName = key.getLocalName();
981 List<SimpleNode<? extends Object>> simpleNodesByName = dataNode.getSimpleNodesByName(localName);
982 if (simpleNodesByName != null) {
983 head = Iterables.getFirst(simpleNodesByName, null);
986 Object dataNodeKeyValueObject = null;
988 dataNodeKeyValueObject = head.getValue();
991 if (dataNodeKeyValueObject == null) {
992 throw new RestconfDocumentedException("Data contains list \"" + dataNode.getNodeType().getLocalName()
993 + "\" which does not contain key: \"" + key.getLocalName() + "\"", ErrorType.PROTOCOL,
994 ErrorTag.INVALID_VALUE);
997 keyValues.put(key, dataNodeKeyValueObject);
1003 private boolean endsWithMountPoint(final String identifier) {
1004 return identifier.endsWith(ControllerContext.MOUNT) || identifier.endsWith(ControllerContext.MOUNT + "/");
1007 private boolean representsMountPointRootData(final CompositeNode data) {
1008 URI namespace = this.namespace(data);
1009 return (SchemaContext.NAME.getNamespace().equals(namespace) /*
1010 * || MOUNT_POINT_MODULE_NAME .equals( namespace .
1013 && SchemaContext.NAME.getLocalName().equals(this.localName(data));
1016 private String addMountPointIdentifier(final String identifier) {
1017 boolean endsWith = identifier.endsWith("/");
1019 return (identifier + ControllerContext.MOUNT);
1022 return identifier + "/" + ControllerContext.MOUNT;
1025 private CompositeNode normalizeNode(final CompositeNode node, final DataSchemaNode schema,
1026 final MountInstance mountPoint) {
1027 if (schema == null) {
1028 QName nodeType = node == null ? null : node.getNodeType();
1029 String localName = nodeType == null ? null : nodeType.getLocalName();
1031 throw new RestconfDocumentedException("Data schema node was not found for " + localName,
1032 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1035 if (!(schema instanceof DataNodeContainer)) {
1036 throw new RestconfDocumentedException("Root element has to be container or list yang datatype.",
1037 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1040 if ((node instanceof CompositeNodeWrapper)) {
1041 boolean isChangeAllowed = ((CompositeNodeWrapper) node).isChangeAllowed();
1042 if (isChangeAllowed) {
1044 this.normalizeNode(((CompositeNodeWrapper) node), schema, null, mountPoint);
1045 } catch (IllegalArgumentException e) {
1046 throw new RestconfDocumentedException(e.getMessage(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1050 return ((CompositeNodeWrapper) node).unwrap();
1056 private void normalizeNode(final NodeWrapper<? extends Object> nodeBuilder, final DataSchemaNode schema,
1057 final QName previousAugment, final MountInstance mountPoint) {
1058 if (schema == null) {
1059 throw new RestconfDocumentedException("Data has bad format.\n\"" + nodeBuilder.getLocalName()
1060 + "\" does not exist in yang schema.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1063 QName currentAugment = null;
1064 if (nodeBuilder.getQname() != null) {
1065 currentAugment = previousAugment;
1067 currentAugment = this.normalizeNodeName(nodeBuilder, schema, previousAugment, mountPoint);
1068 if (nodeBuilder.getQname() == null) {
1069 throw new RestconfDocumentedException(
1070 "Data has bad format.\nIf data is in XML format then namespace for \""
1071 + nodeBuilder.getLocalName() + "\" should be \"" + schema.getQName().getNamespace()
1072 + "\".\n" + "If data is in JSON format then module name for \""
1073 + nodeBuilder.getLocalName() + "\" should be corresponding to namespace \""
1074 + schema.getQName().getNamespace() + "\".", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1078 if (nodeBuilder instanceof CompositeNodeWrapper) {
1079 if (schema instanceof DataNodeContainer) {
1080 normalizeCompositeNode((CompositeNodeWrapper) nodeBuilder, (DataNodeContainer) schema, mountPoint,
1082 } else if (schema instanceof AnyXmlSchemaNode) {
1083 normalizeAnyXmlNode((CompositeNodeWrapper) nodeBuilder, (AnyXmlSchemaNode) schema);
1085 } else if (nodeBuilder instanceof SimpleNodeWrapper) {
1086 normalizeSimpleNode((SimpleNodeWrapper) nodeBuilder, schema, mountPoint);
1087 } else if ((nodeBuilder instanceof EmptyNodeWrapper)) {
1088 normalizeEmptyNode((EmptyNodeWrapper) nodeBuilder, schema);
1092 private void normalizeAnyXmlNode(final CompositeNodeWrapper compositeNode, final AnyXmlSchemaNode schema) {
1093 List<NodeWrapper<?>> children = compositeNode.getValues();
1094 for (NodeWrapper<? extends Object> child : children) {
1095 child.setNamespace(schema.getQName().getNamespace());
1096 if (child instanceof CompositeNodeWrapper) {
1097 normalizeAnyXmlNode((CompositeNodeWrapper) child, schema);
1102 private void normalizeEmptyNode(final EmptyNodeWrapper emptyNodeBuilder, final DataSchemaNode schema) {
1103 if ((schema instanceof LeafSchemaNode)) {
1104 emptyNodeBuilder.setComposite(false);
1106 if ((schema instanceof ContainerSchemaNode)) {
1107 // FIXME: Add presence check
1108 emptyNodeBuilder.setComposite(true);
1113 private void normalizeSimpleNode(final SimpleNodeWrapper simpleNode, final DataSchemaNode schema,
1114 final MountInstance mountPoint) {
1115 final Object value = simpleNode.getValue();
1116 Object inputValue = value;
1117 TypeDefinition<? extends Object> typeDefinition = this.typeDefinition(schema);
1118 if ((typeDefinition instanceof IdentityrefTypeDefinition)) {
1119 if ((value instanceof String)) {
1120 inputValue = new IdentityValuesDTO(simpleNode.getNamespace().toString(), (String) value, null,
1122 } // else value is already instance of IdentityValuesDTO
1125 Object outputValue = inputValue;
1127 if (typeDefinition != null) {
1128 Codec<Object, Object> codec = RestCodec.from(typeDefinition, mountPoint);
1129 outputValue = codec == null ? null : codec.deserialize(inputValue);
1132 simpleNode.setValue(outputValue);
1135 private void normalizeCompositeNode(final CompositeNodeWrapper compositeNodeBuilder,
1136 final DataNodeContainer schema, final MountInstance mountPoint, final QName currentAugment) {
1137 final List<NodeWrapper<?>> children = compositeNodeBuilder.getValues();
1138 checkNodeMultiplicityAccordingToSchema(schema, children);
1139 for (final NodeWrapper<? extends Object> child : children) {
1140 final List<DataSchemaNode> potentialSchemaNodes = this.controllerContext.findInstanceDataChildrenByName(
1141 schema, child.getLocalName());
1143 if (potentialSchemaNodes.size() > 1 && child.getNamespace() == null) {
1144 StringBuilder builder = new StringBuilder();
1145 for (final DataSchemaNode potentialSchemaNode : potentialSchemaNodes) {
1146 builder.append(" ").append(potentialSchemaNode.getQName().getNamespace().toString()).append("\n");
1149 throw new RestconfDocumentedException("Node \"" + child.getLocalName()
1150 + "\" is added as augment from more than one module. "
1151 + "Therefore node must have namespace (XML format) or module name (JSON format)."
1152 + "\nThe node is added as augment from modules with namespaces:\n" + builder,
1153 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1156 boolean rightNodeSchemaFound = false;
1157 for (final DataSchemaNode potentialSchemaNode : potentialSchemaNodes) {
1158 if (!rightNodeSchemaFound) {
1159 final QName potentialCurrentAugment = this.normalizeNodeName(child, potentialSchemaNode,
1160 currentAugment, mountPoint);
1161 if (child.getQname() != null) {
1162 this.normalizeNode(child, potentialSchemaNode, potentialCurrentAugment, mountPoint);
1163 rightNodeSchemaFound = true;
1168 if (!rightNodeSchemaFound) {
1169 throw new RestconfDocumentedException("Schema node \"" + child.getLocalName()
1170 + "\" was not found in module.", ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT);
1174 if ((schema instanceof ListSchemaNode)) {
1175 ListSchemaNode listSchemaNode = (ListSchemaNode) schema;
1176 final List<QName> listKeys = listSchemaNode.getKeyDefinition();
1177 for (final QName listKey : listKeys) {
1178 boolean foundKey = false;
1179 for (final NodeWrapper<? extends Object> child : children) {
1180 if (Objects.equal(child.unwrap().getNodeType().getLocalName(), listKey.getLocalName())) {
1186 throw new RestconfDocumentedException("Missing key in URI \"" + listKey.getLocalName()
1187 + "\" of list \"" + listSchemaNode.getQName().getLocalName() + "\"", ErrorType.PROTOCOL,
1188 ErrorTag.DATA_MISSING);
1194 private void checkNodeMultiplicityAccordingToSchema(final DataNodeContainer dataNodeContainer,
1195 final List<NodeWrapper<?>> nodes) {
1196 Map<String, Integer> equalNodeNamesToCounts = new HashMap<String, Integer>();
1197 for (NodeWrapper<?> child : nodes) {
1198 Integer count = equalNodeNamesToCounts.get(child.getLocalName());
1199 equalNodeNamesToCounts.put(child.getLocalName(), count == null ? 1 : ++count);
1202 for (DataSchemaNode childSchemaNode : dataNodeContainer.getChildNodes()) {
1203 if (childSchemaNode instanceof ContainerSchemaNode || childSchemaNode instanceof LeafSchemaNode) {
1204 String localName = childSchemaNode.getQName().getLocalName();
1205 Integer count = equalNodeNamesToCounts.get(localName);
1206 if (count != null && count > 1) {
1207 throw new RestconfDocumentedException("Multiple input data elements were specified for '"
1208 + childSchemaNode.getQName().getLocalName()
1209 + "'. The data for this element type can only be specified once.", ErrorType.APPLICATION,
1210 ErrorTag.BAD_ELEMENT);
1216 private QName normalizeNodeName(final NodeWrapper<? extends Object> nodeBuilder, final DataSchemaNode schema,
1217 final QName previousAugment, final MountInstance mountPoint) {
1218 QName validQName = schema.getQName();
1219 QName currentAugment = previousAugment;
1220 if (schema.isAugmenting()) {
1221 currentAugment = schema.getQName();
1222 } else if (previousAugment != null
1223 && !Objects.equal(schema.getQName().getNamespace(), previousAugment.getNamespace())) {
1224 validQName = QName.create(currentAugment, schema.getQName().getLocalName());
1227 String moduleName = null;
1228 if (mountPoint == null) {
1229 moduleName = controllerContext.findModuleNameByNamespace(validQName.getNamespace());
1231 moduleName = controllerContext.findModuleNameByNamespace(mountPoint, validQName.getNamespace());
1234 if (nodeBuilder.getNamespace() == null || Objects.equal(nodeBuilder.getNamespace(), validQName.getNamespace())
1235 || Objects.equal(nodeBuilder.getNamespace().toString(), moduleName) /*
1236 * || Note : this check is wrong -
1237 * can never be true as it compares
1238 * a URI with a String not sure what
1239 * the intention is so commented out
1240 * ... Objects . equal ( nodeBuilder
1241 * . getNamespace ( ) ,
1242 * MOUNT_POINT_MODULE_NAME )
1245 nodeBuilder.setQname(validQName);
1248 return currentAugment;
1251 private URI namespace(final CompositeNode data) {
1252 if (data instanceof CompositeNodeWrapper) {
1253 return ((CompositeNodeWrapper) data).getNamespace();
1254 } else if (data != null) {
1255 return data.getNodeType().getNamespace();
1257 throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(data).toString());
1261 private String localName(final CompositeNode data) {
1262 if (data instanceof CompositeNodeWrapper) {
1263 return ((CompositeNodeWrapper) data).getLocalName();
1264 } else if (data != null) {
1265 return data.getNodeType().getLocalName();
1267 throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(data).toString());
1271 private String getName(final CompositeNode data) {
1272 if (data instanceof CompositeNodeWrapper) {
1273 return ((CompositeNodeWrapper) data).getLocalName();
1274 } else if (data != null) {
1275 return data.getNodeType().getLocalName();
1277 throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(data).toString());
1281 private TypeDefinition<? extends Object> _typeDefinition(final LeafSchemaNode node) {
1282 TypeDefinition<?> baseType = node.getType();
1283 while (baseType.getBaseType() != null) {
1284 baseType = baseType.getBaseType();
1290 private TypeDefinition<? extends Object> typeDefinition(final LeafListSchemaNode node) {
1291 TypeDefinition<?> baseType = node.getType();
1292 while (baseType.getBaseType() != null) {
1293 baseType = baseType.getBaseType();
1299 private TypeDefinition<? extends Object> typeDefinition(final DataSchemaNode node) {
1300 if (node instanceof LeafListSchemaNode) {
1301 return typeDefinition((LeafListSchemaNode) node);
1302 } else if (node instanceof LeafSchemaNode) {
1303 return _typeDefinition((LeafSchemaNode) node);
1304 } else if (node instanceof AnyXmlSchemaNode) {
1307 throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(node).toString());