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;
86 UriParameters(String uriParameterName) {
87 this.uriParameterName = uriParameterName;
91 public String toString() {
92 return uriParameterName;
96 private final static RestconfImpl INSTANCE = new RestconfImpl();
98 private static final int CHAR_NOT_FOUND = -1;
100 private final static String MOUNT_POINT_MODULE_NAME = "ietf-netconf";
102 private final static SimpleDateFormat REVISION_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
104 private final static String SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote";
106 private final static String SAL_REMOTE_RPC_SUBSRCIBE = "create-data-change-event-subscription";
108 private BrokerFacade broker;
110 private ControllerContext controllerContext;
112 public void setBroker(final BrokerFacade broker) {
113 this.broker = broker;
116 public void setControllerContext(final ControllerContext controllerContext) {
117 this.controllerContext = controllerContext;
120 private RestconfImpl() {
123 public static RestconfImpl getInstance() {
128 public StructuredData getModules(final UriInfo uriInfo) {
129 final Module restconfModule = this.getRestconfModule();
131 final List<Node<?>> modulesAsData = new ArrayList<Node<?>>();
132 final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
133 restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
135 Set<Module> allModules = this.controllerContext.getAllModules();
136 for (final Module module : allModules) {
137 CompositeNode moduleCompositeNode = this.toModuleCompositeNode(module, moduleSchemaNode);
138 modulesAsData.add(moduleCompositeNode);
141 final DataSchemaNode modulesSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
142 restconfModule, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
143 QName qName = modulesSchemaNode.getQName();
144 final CompositeNode modulesNode = NodeFactory.createImmutableCompositeNode(qName, null, modulesAsData);
145 return new StructuredData(modulesNode, modulesSchemaNode, null,parsePrettyPrintParameter( uriInfo ));
149 public StructuredData getAvailableStreams(final UriInfo uriInfo) {
150 Set<String> availableStreams = Notificator.getStreamNames();
152 final List<Node<?>> streamsAsData = new ArrayList<Node<?>>();
153 Module restconfModule = this.getRestconfModule();
154 final DataSchemaNode streamSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
155 restconfModule, Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
156 for (final String streamName : availableStreams) {
157 streamsAsData.add(this.toStreamCompositeNode(streamName, streamSchemaNode));
160 final DataSchemaNode streamsSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
161 restconfModule, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
162 QName qName = streamsSchemaNode.getQName();
163 final CompositeNode streamsNode = NodeFactory.createImmutableCompositeNode(qName, null, streamsAsData);
164 return new StructuredData(streamsNode, streamsSchemaNode, null,parsePrettyPrintParameter( uriInfo ));
168 public StructuredData getModules(final String identifier,final UriInfo uriInfo) {
169 Set<Module> modules = null;
170 MountInstance mountPoint = null;
171 if (identifier.contains(ControllerContext.MOUNT)) {
172 InstanceIdWithSchemaNode mountPointIdentifier =
173 this.controllerContext.toMountPointIdentifier(identifier);
174 mountPoint = mountPointIdentifier.getMountPoint();
175 modules = this.controllerContext.getAllModules(mountPoint);
178 throw new RestconfDocumentedException(
179 "URI has bad format. If modules behind mount point should be showed, URI has to end with " +
180 ControllerContext.MOUNT, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
183 final List<Node<?>> modulesAsData = new ArrayList<Node<?>>();
184 Module restconfModule = this.getRestconfModule();
185 final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
186 restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
188 for (final Module module : modules) {
189 modulesAsData.add(this.toModuleCompositeNode(module, moduleSchemaNode));
192 final DataSchemaNode modulesSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
193 restconfModule, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
194 QName qName = modulesSchemaNode.getQName();
195 final CompositeNode modulesNode = NodeFactory.createImmutableCompositeNode(qName, null, modulesAsData);
196 return new StructuredData(modulesNode, modulesSchemaNode, mountPoint,parsePrettyPrintParameter( uriInfo ));
200 public StructuredData getModule(final String identifier,final UriInfo uriInfo) {
201 final QName moduleNameAndRevision = this.getModuleNameAndRevision(identifier);
202 Module module = null;
203 MountInstance mountPoint = null;
204 if (identifier.contains(ControllerContext.MOUNT)) {
205 InstanceIdWithSchemaNode mountPointIdentifier =
206 this.controllerContext.toMountPointIdentifier(identifier);
207 mountPoint = mountPointIdentifier.getMountPoint();
208 module = this.controllerContext.findModuleByNameAndRevision(mountPoint, moduleNameAndRevision);
211 module = this.controllerContext.findModuleByNameAndRevision(moduleNameAndRevision);
214 if (module == null) {
215 throw new RestconfDocumentedException(
216 "Module with name '" + moduleNameAndRevision.getLocalName() + "' and revision '" +
217 moduleNameAndRevision.getRevision() + "' was not found.",
218 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
221 Module restconfModule = this.getRestconfModule();
222 final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
223 restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
224 final CompositeNode moduleNode = this.toModuleCompositeNode(module, moduleSchemaNode);
225 return new StructuredData(moduleNode, moduleSchemaNode, mountPoint,parsePrettyPrintParameter( uriInfo ));
229 public StructuredData getOperations(final UriInfo uriInfo) {
230 Set<Module> allModules = this.controllerContext.getAllModules();
231 return this.operationsFromModulesToStructuredData(allModules, null,parsePrettyPrintParameter(uriInfo));
235 public StructuredData getOperations(final String identifier,final UriInfo uriInfo) {
236 Set<Module> modules = null;
237 MountInstance mountPoint = null;
238 if (identifier.contains(ControllerContext.MOUNT)) {
239 InstanceIdWithSchemaNode mountPointIdentifier =
240 this.controllerContext.toMountPointIdentifier(identifier);
241 mountPoint = mountPointIdentifier.getMountPoint();
242 modules = this.controllerContext.getAllModules(mountPoint);
245 throw new RestconfDocumentedException(
246 "URI has bad format. If operations behind mount point should be showed, URI has to end with " +
247 ControllerContext.MOUNT, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
250 return this.operationsFromModulesToStructuredData(modules, mountPoint,parsePrettyPrintParameter(uriInfo));
253 private StructuredData operationsFromModulesToStructuredData(final Set<Module> modules,
254 final MountInstance mountPoint,boolean prettyPrint) {
255 final List<Node<?>> operationsAsData = new ArrayList<Node<?>>();
256 Module restconfModule = this.getRestconfModule();
257 final DataSchemaNode operationsSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
258 restconfModule, Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE);
259 QName qName = operationsSchemaNode.getQName();
260 SchemaPath path = operationsSchemaNode.getPath();
261 ContainerSchemaNodeBuilder containerSchemaNodeBuilder =
262 new ContainerSchemaNodeBuilder(Draft02.RestConfModule.NAME, 0, qName, path);
263 final ContainerSchemaNodeBuilder fakeOperationsSchemaNode = containerSchemaNodeBuilder;
264 for (final Module module : modules) {
265 Set<RpcDefinition> rpcs = module.getRpcs();
266 for (final RpcDefinition rpc : rpcs) {
267 QName rpcQName = rpc.getQName();
268 SimpleNode<Object> immutableSimpleNode =
269 NodeFactory.<Object>createImmutableSimpleNode(rpcQName, null, null);
270 operationsAsData.add(immutableSimpleNode);
272 String name = module.getName();
273 LeafSchemaNodeBuilder leafSchemaNodeBuilder = new LeafSchemaNodeBuilder(name, 0, rpcQName,
274 SchemaPath.create(true, QName.create("dummy")));
275 final LeafSchemaNodeBuilder fakeRpcSchemaNode = leafSchemaNodeBuilder;
276 fakeRpcSchemaNode.setAugmenting(true);
278 EmptyType instance = EmptyType.getInstance();
279 fakeRpcSchemaNode.setType(instance);
280 fakeOperationsSchemaNode.addChildNode(fakeRpcSchemaNode.build());
284 final CompositeNode operationsNode =
285 NodeFactory.createImmutableCompositeNode(qName, null, operationsAsData);
286 ContainerSchemaNode schemaNode = fakeOperationsSchemaNode.build();
287 return new StructuredData(operationsNode, schemaNode, mountPoint,prettyPrint);
290 private Module getRestconfModule() {
291 Module restconfModule = controllerContext.getRestconfModule();
292 if (restconfModule == null) {
293 throw new RestconfDocumentedException(
294 "ietf-restconf module was not found.", ErrorType.APPLICATION,
295 ErrorTag.OPERATION_NOT_SUPPORTED );
298 return restconfModule;
301 private QName getModuleNameAndRevision(final String identifier) {
302 final int mountIndex = identifier.indexOf(ControllerContext.MOUNT);
303 String moduleNameAndRevision = "";
304 if (mountIndex >= 0) {
305 moduleNameAndRevision = identifier.substring(mountIndex + ControllerContext.MOUNT.length());
308 moduleNameAndRevision = identifier;
311 Splitter splitter = Splitter.on("/").omitEmptyStrings();
312 Iterable<String> split = splitter.split(moduleNameAndRevision);
313 final List<String> pathArgs = Lists.<String>newArrayList(split);
314 if (pathArgs.size() < 2) {
315 throw new RestconfDocumentedException(
316 "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'",
317 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
321 final String moduleName = pathArgs.get( 0 );
322 String revision = pathArgs.get(1);
323 final Date moduleRevision = REVISION_FORMAT.parse(revision);
324 return QName.create(null, moduleRevision, moduleName);
326 catch (ParseException e) {
327 throw new RestconfDocumentedException(
328 "URI has bad format. It should be \'moduleName/yyyy-MM-dd\'",
329 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
333 private CompositeNode toStreamCompositeNode(final String streamName, final DataSchemaNode streamSchemaNode) {
334 final List<Node<?>> streamNodeValues = new ArrayList<Node<?>>();
335 List<DataSchemaNode> instanceDataChildrenByName =
336 this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) streamSchemaNode),
338 final DataSchemaNode nameSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
339 streamNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(nameSchemaNode.getQName(), null,
342 instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
343 ((DataNodeContainer) streamSchemaNode), "description");
344 final DataSchemaNode descriptionSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
345 streamNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(descriptionSchemaNode.getQName(), null,
346 "DESCRIPTION_PLACEHOLDER"));
348 instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
349 ((DataNodeContainer) streamSchemaNode), "replay-support");
350 final DataSchemaNode replaySupportSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
351 streamNodeValues.add(NodeFactory.<Boolean>createImmutableSimpleNode(replaySupportSchemaNode.getQName(), null,
352 Boolean.valueOf(true)));
354 instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
355 ((DataNodeContainer) streamSchemaNode), "replay-log-creation-time");
356 final DataSchemaNode replayLogCreationTimeSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
357 streamNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(replayLogCreationTimeSchemaNode.getQName(),
360 instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
361 ((DataNodeContainer) streamSchemaNode), "events");
362 final DataSchemaNode eventsSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
363 streamNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(eventsSchemaNode.getQName(),
366 return NodeFactory.createImmutableCompositeNode(streamSchemaNode.getQName(), null, streamNodeValues);
369 private CompositeNode toModuleCompositeNode(final Module module, final DataSchemaNode moduleSchemaNode) {
370 final List<Node<?>> moduleNodeValues = new ArrayList<Node<?>>();
371 List<DataSchemaNode> instanceDataChildrenByName =
372 this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) moduleSchemaNode), "name");
373 final DataSchemaNode nameSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
374 moduleNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(nameSchemaNode.getQName(),
375 null, module.getName()));
377 instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
378 ((DataNodeContainer) moduleSchemaNode), "revision");
379 final DataSchemaNode revisionSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
380 Date _revision = module.getRevision();
381 moduleNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(revisionSchemaNode.getQName(), null,
382 REVISION_FORMAT.format(_revision)));
384 instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
385 ((DataNodeContainer) moduleSchemaNode), "namespace");
386 final DataSchemaNode namespaceSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
387 moduleNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(namespaceSchemaNode.getQName(), null,
388 module.getNamespace().toString()));
390 instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
391 ((DataNodeContainer) moduleSchemaNode), "feature");
392 final DataSchemaNode featureSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
393 for (final FeatureDefinition feature : module.getFeatures()) {
394 moduleNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(featureSchemaNode.getQName(), null,
395 feature.getQName().getLocalName()));
398 return NodeFactory.createImmutableCompositeNode(moduleSchemaNode.getQName(), null, moduleNodeValues);
402 public Object getRoot() {
407 public StructuredData invokeRpc(final String identifier, final CompositeNode payload,final UriInfo uriInfo) {
408 final RpcExecutor rpc = this.resolveIdentifierInInvokeRpc(identifier);
409 QName rpcName = rpc.getRpcDefinition().getQName();
410 URI rpcNamespace = rpcName.getNamespace();
411 if (Objects.equal(rpcNamespace.toString(), SAL_REMOTE_NAMESPACE) &&
412 Objects.equal(rpcName.getLocalName(), SAL_REMOTE_RPC_SUBSRCIBE)) {
413 return invokeSalRemoteRpcSubscribeRPC(payload, rpc.getRpcDefinition(),parsePrettyPrintParameter(uriInfo));
416 validateInput( rpc.getRpcDefinition().getInput(), payload );
418 return callRpc(rpc, payload,parsePrettyPrintParameter(uriInfo));
421 private void validateInput(final DataSchemaNode inputSchema, final CompositeNode payload) {
422 if( inputSchema != null && payload == null )
424 //expected a non null payload
425 throw new RestconfDocumentedException( "Input is required.",
427 ErrorTag.MALFORMED_MESSAGE );
429 else if( inputSchema == null && payload != null )
431 //did not expect any input
432 throw new RestconfDocumentedException( "No input expected.",
434 ErrorTag.MALFORMED_MESSAGE );
438 //TODO: Validate "mandatory" and "config" values here??? Or should those be
439 // validate in a more central location inside MD-SAL core.
443 private StructuredData invokeSalRemoteRpcSubscribeRPC(final CompositeNode payload,
444 final RpcDefinition rpc,final boolean prettyPrint) {
445 final CompositeNode value = this.normalizeNode(payload, rpc.getInput(), null);
446 final SimpleNode<? extends Object> pathNode = value == null ? null :
447 value.getFirstSimpleByName( QName.create(rpc.getQName(), "path") );
448 final Object pathValue = pathNode == null ? null : pathNode.getValue();
450 if (!(pathValue instanceof InstanceIdentifier)) {
451 throw new RestconfDocumentedException(
452 "Instance identifier was not normalized correctly.",
453 ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED );
456 final InstanceIdentifier pathIdentifier = ((InstanceIdentifier) pathValue);
457 String streamName = null;
458 if (!Iterables.isEmpty(pathIdentifier.getPathArguments())) {
459 String fullRestconfIdentifier = this.controllerContext.toFullRestconfIdentifier(pathIdentifier);
460 streamName = Notificator.createStreamNameFromUri(fullRestconfIdentifier);
463 if (Strings.isNullOrEmpty(streamName)) {
464 throw new RestconfDocumentedException(
465 "Path is empty or contains data node which is not Container or List build-in type.",
466 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
469 final SimpleNode<String> streamNameNode = NodeFactory.<String>createImmutableSimpleNode(
470 QName.create(rpc.getOutput().getQName(), "stream-name"), null, streamName);
471 final List<Node<?>> output = new ArrayList<Node<?>>();
472 output.add(streamNameNode);
474 final MutableCompositeNode responseData = NodeFactory.createMutableCompositeNode(
475 rpc.getOutput().getQName(), null, output, null, null);
477 if (!Notificator.existListenerFor(pathIdentifier)) {
478 Notificator.createListener(pathIdentifier, streamName);
481 return new StructuredData(responseData, rpc.getOutput(), null,prettyPrint);
485 public StructuredData invokeRpc(final String identifier, final String noPayload, final UriInfo uriInfo) {
486 if (StringUtils.isNotBlank(noPayload)) {
487 throw new RestconfDocumentedException(
488 "Content must be empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
490 return invokeRpc( identifier, (CompositeNode)null,uriInfo);
493 private RpcExecutor resolveIdentifierInInvokeRpc(final String identifier) {
494 String identifierEncoded = null;
495 MountInstance mountPoint = null;
496 if (identifier.contains(ControllerContext.MOUNT)) {
497 // mounted RPC call - look up mount instance.
498 InstanceIdWithSchemaNode mountPointId = controllerContext
499 .toMountPointIdentifier(identifier);
500 mountPoint = mountPointId.getMountPoint();
502 int startOfRemoteRpcName = identifier.lastIndexOf(ControllerContext.MOUNT)
503 + ControllerContext.MOUNT.length() + 1;
504 String remoteRpcName = identifier.substring(startOfRemoteRpcName);
505 identifierEncoded = remoteRpcName;
507 } else if (identifier.indexOf("/") != CHAR_NOT_FOUND) {
508 final String slashErrorMsg = String
509 .format("Identifier %n%s%ncan\'t contain slash "
510 + "character (/).%nIf slash is part of identifier name then use %%2F placeholder.",
512 throw new RestconfDocumentedException(
513 slashErrorMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
515 identifierEncoded = identifier;
518 final String identifierDecoded = controllerContext.urlPathArgDecode(identifierEncoded);
519 RpcDefinition rpc = controllerContext.getRpcDefinition(identifierDecoded);
522 throw new RestconfDocumentedException(
523 "RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT );
526 if (mountPoint == null) {
527 return new BrokerRpcExecutor(rpc, broker);
529 return new MountPointRpcExecutor(rpc, mountPoint);
534 private StructuredData callRpc(final RpcExecutor rpcExecutor, final CompositeNode payload,boolean prettyPrint) {
535 if (rpcExecutor == null) {
536 throw new RestconfDocumentedException(
537 "RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT );
540 CompositeNode rpcRequest = null;
541 RpcDefinition rpc = rpcExecutor.getRpcDefinition();
542 QName rpcName = rpc.getQName();
544 if (payload == null) {
545 rpcRequest = NodeFactory.createMutableCompositeNode(rpcName, null, null, null, null);
547 final CompositeNode value = this.normalizeNode(payload, rpc.getInput(), null);
548 List<Node<?>> input = Collections.<Node<?>> singletonList(value);
549 rpcRequest = NodeFactory.createMutableCompositeNode(rpcName, null, input, null, null);
552 RpcResult<CompositeNode> rpcResult = rpcExecutor.invokeRpc(rpcRequest);
554 checkRpcSuccessAndThrowException(rpcResult);
556 if (rpcResult.getResult() == null) {
560 if( rpc.getOutput() == null )
562 return null; //no output, nothing to send back.
565 return new StructuredData(rpcResult.getResult(), rpc.getOutput(), null,prettyPrint);
568 private void checkRpcSuccessAndThrowException(final RpcResult<CompositeNode> rpcResult) {
569 if (rpcResult.isSuccessful() == false) {
571 Collection<RpcError> rpcErrors = rpcResult.getErrors();
572 if( rpcErrors == null || rpcErrors.isEmpty() ) {
573 throw new RestconfDocumentedException(
574 "The operation was not successful and there were no RPC errors returned",
575 ErrorType.RPC, ErrorTag.OPERATION_FAILED );
578 List<RestconfError> errorList = Lists.newArrayList();
579 for( RpcError rpcError: rpcErrors ) {
580 errorList.add( new RestconfError( rpcError ) );
583 throw new RestconfDocumentedException( errorList );
588 public StructuredData readConfigurationData(final String identifier, final UriInfo uriInfo) {
589 final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
590 CompositeNode data = null;
591 MountInstance mountPoint = iiWithData.getMountPoint();
592 if (mountPoint != null) {
593 data = broker.readConfigurationDataBehindMountPoint(mountPoint, iiWithData.getInstanceIdentifier());
596 data = broker.readConfigurationData(iiWithData.getInstanceIdentifier());
599 data = pruneDataAtDepth( data, parseDepthParameter( uriInfo ) );
600 boolean prettyPrintMode = parsePrettyPrintParameter( uriInfo );
601 return new StructuredData(data, iiWithData.getSchemaNode(), iiWithData.getMountPoint(),prettyPrintMode);
604 @SuppressWarnings("unchecked")
605 private <T extends Node<?>> T pruneDataAtDepth( final T node, final Integer depth ) {
606 if( depth == null ) {
610 if( node instanceof CompositeNode ) {
611 ImmutableList.Builder<Node<?>> newChildNodes = ImmutableList.<Node<?>> builder();
613 for( Node<?> childNode: ((CompositeNode)node).getValue() ) {
614 newChildNodes.add( pruneDataAtDepth( childNode, depth - 1 ) );
618 return (T) ImmutableCompositeNode.create( node.getNodeType(), newChildNodes.build() );
625 private Integer parseDepthParameter( final UriInfo info ) {
626 String param = info.getQueryParameters( false ).getFirst( UriParameters.DEPTH.toString() );
627 if( Strings.isNullOrEmpty( param ) || "unbounded".equals( param ) ) {
632 Integer depth = Integer.valueOf( param );
634 throw new RestconfDocumentedException( new RestconfError(
635 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, "Invalid depth parameter: " + depth,
636 null, "The depth parameter must be an integer > 1 or \"unbounded\"" ) );
641 catch( NumberFormatException e ) {
642 throw new RestconfDocumentedException( new RestconfError(
643 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
644 "Invalid depth parameter: " + e.getMessage(),
645 null, "The depth parameter must be an integer > 1 or \"unbounded\"" ) );
650 public StructuredData readOperationalData(final String identifier, final UriInfo info) {
651 final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
652 CompositeNode data = null;
653 MountInstance mountPoint = iiWithData.getMountPoint();
654 if (mountPoint != null) {
655 data = broker.readOperationalDataBehindMountPoint(mountPoint, iiWithData.getInstanceIdentifier());
658 data = broker.readOperationalData(iiWithData.getInstanceIdentifier());
661 data = pruneDataAtDepth( data, parseDepthParameter( info ) );
662 boolean prettyPrintMode = parsePrettyPrintParameter( info );
663 return new StructuredData(data, iiWithData.getSchemaNode(), mountPoint,prettyPrintMode);
666 private boolean parsePrettyPrintParameter(UriInfo info) {
667 String param = info.getQueryParameters(false).getFirst(UriParameters.PRETTY_PRINT.toString());
668 return Boolean.parseBoolean(param);
672 public Response updateConfigurationData(final String identifier, final CompositeNode payload) {
673 final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
675 validateInput(iiWithData.getSchemaNode(), payload);
677 MountInstance mountPoint = iiWithData.getMountPoint();
678 final CompositeNode value = this.normalizeNode(payload, iiWithData.getSchemaNode(), mountPoint);
679 validateListKeysEqualityInPayloadAndUri(iiWithData, payload);
680 RpcResult<TransactionStatus> status = null;
683 if (mountPoint != null) {
684 status = broker.commitConfigurationDataPutBehindMountPoint(
685 mountPoint, iiWithData.getInstanceIdentifier(), value).get();
687 status = broker.commitConfigurationDataPut(iiWithData.getInstanceIdentifier(), value).get();
690 catch( Exception e ) {
691 throw new RestconfDocumentedException( "Error updating data", e );
694 if( status.getResult() == TransactionStatus.COMMITED ) {
695 return Response.status(Status.OK).build();
698 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
702 * Validates whether keys in {@code payload} are equal to values of keys in
703 * {@code iiWithData} for list schema node
705 * @throws RestconfDocumentedException
706 * if key values or key count in payload and URI isn't equal
709 private void validateListKeysEqualityInPayloadAndUri(final InstanceIdWithSchemaNode iiWithData,
710 final CompositeNode payload) {
711 if (iiWithData.getSchemaNode() instanceof ListSchemaNode) {
712 final List<QName> keyDefinitions = ((ListSchemaNode) iiWithData.getSchemaNode()).getKeyDefinition();
713 final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
714 if (lastPathArgument instanceof NodeIdentifierWithPredicates) {
715 final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument)
717 isEqualUriAndPayloadKeyValues(uriKeyValues, payload, keyDefinitions);
722 private void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final CompositeNode payload,
723 final List<QName> keyDefinitions) {
724 for (QName keyDefinition : keyDefinitions) {
725 final Object uriKeyValue = uriKeyValues.get(keyDefinition);
726 // should be caught during parsing URI to InstanceIdentifier
727 if (uriKeyValue == null) {
728 throw new RestconfDocumentedException("Missing key " + keyDefinition + " in URI.",
729 ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
731 final List<SimpleNode<?>> payloadKeyValues = payload.getSimpleNodesByName(keyDefinition.getLocalName());
732 if (payloadKeyValues.isEmpty()) {
733 throw new RestconfDocumentedException("Missing key " + keyDefinition.getLocalName()
734 + " in the message body.", ErrorType.PROTOCOL,
735 ErrorTag.DATA_MISSING);
738 Object payloadKeyValue = payloadKeyValues.iterator().next().getValue();
739 if (!uriKeyValue.equals(payloadKeyValue)) {
740 throw new RestconfDocumentedException("The value '"+uriKeyValue+ "' for key '" + keyDefinition.getLocalName() +
741 "' specified in the URI doesn't match the value '" + payloadKeyValue + "' specified in the message body. ",
742 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
748 public Response createConfigurationData(final String identifier, final CompositeNode payload) {
749 if( payload == null ) {
750 throw new RestconfDocumentedException( "Input is required.",
752 ErrorTag.MALFORMED_MESSAGE );
755 URI payloadNS = this.namespace(payload);
756 if (payloadNS == null) {
757 throw new RestconfDocumentedException(
758 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)",
759 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE );
762 InstanceIdWithSchemaNode iiWithData = null;
763 CompositeNode value = null;
764 if (this.representsMountPointRootData(payload)) {
765 // payload represents mount point data and URI represents path to the mount point
767 if (this.endsWithMountPoint(identifier)) {
768 throw new RestconfDocumentedException(
769 "URI has bad format. URI should be without \"" + ControllerContext.MOUNT +
770 "\" for POST operation.",
771 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
774 final String completeIdentifier = this.addMountPointIdentifier(identifier);
775 iiWithData = this.controllerContext.toInstanceIdentifier(completeIdentifier);
777 value = this.normalizeNode(payload, iiWithData.getSchemaNode(), iiWithData.getMountPoint());
780 final InstanceIdWithSchemaNode incompleteInstIdWithData =
781 this.controllerContext.toInstanceIdentifier(identifier);
782 final DataNodeContainer parentSchema = (DataNodeContainer) incompleteInstIdWithData.getSchemaNode();
783 MountInstance mountPoint = incompleteInstIdWithData.getMountPoint();
784 final Module module = this.findModule(mountPoint, payload);
785 if (module == null) {
786 throw new RestconfDocumentedException(
787 "Module was not found for \"" + payloadNS + "\"",
788 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
791 String payloadName = this.getName(payload);
792 final DataSchemaNode schemaNode = this.controllerContext.findInstanceDataChildByNameAndNamespace(
793 parentSchema, payloadName, module.getNamespace());
794 value = this.normalizeNode(payload, schemaNode, mountPoint);
796 iiWithData = this.addLastIdentifierFromData(incompleteInstIdWithData, value, schemaNode);
799 RpcResult<TransactionStatus> status = null;
800 MountInstance mountPoint = iiWithData.getMountPoint();
802 if (mountPoint != null) {
803 Future<RpcResult<TransactionStatus>> future =
804 broker.commitConfigurationDataPostBehindMountPoint(
805 mountPoint, iiWithData.getInstanceIdentifier(), value);
806 status = future == null ? null : future.get();
809 Future<RpcResult<TransactionStatus>> future =
810 broker.commitConfigurationDataPost(iiWithData.getInstanceIdentifier(), value);
811 status = future == null ? null : future.get();
814 catch( Exception e ) {
815 throw new RestconfDocumentedException( "Error creating data", e );
818 if (status == null) {
819 return Response.status(Status.ACCEPTED).build();
822 if( status.getResult() == TransactionStatus.COMMITED ) {
823 return Response.status(Status.NO_CONTENT).build();
826 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
830 public Response createConfigurationData(final CompositeNode payload) {
831 if( payload == null ) {
832 throw new RestconfDocumentedException( "Input is required.",
834 ErrorTag.MALFORMED_MESSAGE );
837 URI payloadNS = this.namespace(payload);
838 if (payloadNS == null) {
839 throw new RestconfDocumentedException(
840 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)",
841 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE );
844 final Module module = this.findModule(null, payload);
845 if (module == null) {
846 throw new RestconfDocumentedException(
847 "Data has bad format. Root element node has incorrect namespace (XML format) or module name(JSON format)",
848 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE );
851 String payloadName = this.getName(payload);
852 final DataSchemaNode schemaNode = this.controllerContext.findInstanceDataChildByNameAndNamespace(
853 module, payloadName, module.getNamespace());
854 final CompositeNode value = this.normalizeNode(payload, schemaNode, null);
855 final InstanceIdWithSchemaNode iiWithData = this.addLastIdentifierFromData(null, value, schemaNode);
856 RpcResult<TransactionStatus> status = null;
857 MountInstance mountPoint = iiWithData.getMountPoint();
860 if (mountPoint != null) {
861 Future<RpcResult<TransactionStatus>> future =
862 broker.commitConfigurationDataPostBehindMountPoint(
863 mountPoint, iiWithData.getInstanceIdentifier(), value);
864 status = future == null ? null : future.get();
867 Future<RpcResult<TransactionStatus>> future =
868 broker.commitConfigurationDataPost(iiWithData.getInstanceIdentifier(), value);
869 status = future == null ? null : future.get();
872 catch( Exception e ) {
873 throw new RestconfDocumentedException( "Error creating data", e );
876 if (status == null) {
877 return Response.status(Status.ACCEPTED).build();
880 if( status.getResult() == TransactionStatus.COMMITED ) {
881 return Response.status(Status.NO_CONTENT).build();
884 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
888 public Response deleteConfigurationData(final String identifier) {
889 final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
890 RpcResult<TransactionStatus> status = null;
891 MountInstance mountPoint = iiWithData.getMountPoint();
894 if (mountPoint != null) {
895 status = broker.commitConfigurationDataDeleteBehindMountPoint(
896 mountPoint, iiWithData.getInstanceIdentifier()).get();
899 status = broker.commitConfigurationDataDelete(iiWithData.getInstanceIdentifier()).get();
902 catch( Exception e ) {
903 throw new RestconfDocumentedException( "Error creating data", e );
906 if( status.getResult() == TransactionStatus.COMMITED ) {
907 return Response.status(Status.OK).build();
910 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
914 public Response subscribeToStream(final String identifier, final UriInfo uriInfo) {
915 final String streamName = Notificator.createStreamNameFromUri(identifier);
916 if (Strings.isNullOrEmpty(streamName)) {
917 throw new RestconfDocumentedException(
918 "Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
921 final ListenerAdapter listener = Notificator.getListenerFor(streamName);
922 if (listener == null) {
923 throw new RestconfDocumentedException(
924 "Stream was not found.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
927 broker.registerToListenDataChanges(listener);
929 final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
930 UriBuilder port = uriBuilder.port(WebSocketServer.getInstance().getPort());
931 final URI uriToWebsocketServer = port.replacePath(streamName).build();
933 return Response.status(Status.OK).location(uriToWebsocketServer).build();
936 private Module findModule(final MountInstance mountPoint, final CompositeNode data) {
937 if (data instanceof CompositeNodeWrapper) {
938 return findModule(mountPoint, (CompositeNodeWrapper)data);
940 else if (data != null) {
941 URI namespace = data.getNodeType().getNamespace();
942 if (mountPoint != null) {
943 return this.controllerContext.findModuleByNamespace(mountPoint, namespace);
946 return this.controllerContext.findModuleByNamespace(namespace);
950 throw new IllegalArgumentException("Unhandled parameter types: " +
951 Arrays.<Object>asList(mountPoint, data).toString());
955 private Module findModule(final MountInstance mountPoint, final CompositeNodeWrapper data) {
956 URI namespace = data.getNamespace();
957 Preconditions.<URI>checkNotNull(namespace);
959 Module module = null;
960 if (mountPoint != null) {
961 module = this.controllerContext.findModuleByNamespace(mountPoint, namespace);
962 if (module == null) {
963 module = this.controllerContext.findModuleByName(mountPoint, namespace.toString());
967 module = this.controllerContext.findModuleByNamespace(namespace);
968 if (module == null) {
969 module = this.controllerContext.findModuleByName(namespace.toString());
976 private InstanceIdWithSchemaNode addLastIdentifierFromData(
977 final InstanceIdWithSchemaNode identifierWithSchemaNode,
978 final CompositeNode data, final DataSchemaNode schemaOfData) {
979 InstanceIdentifier instanceIdentifier = null;
980 if (identifierWithSchemaNode != null) {
981 instanceIdentifier = identifierWithSchemaNode.getInstanceIdentifier();
984 final InstanceIdentifier iiOriginal = instanceIdentifier;
985 InstanceIdentifierBuilder iiBuilder = null;
986 if (iiOriginal == null) {
987 iiBuilder = InstanceIdentifier.builder();
990 iiBuilder = InstanceIdentifier.builder(iiOriginal);
993 if ((schemaOfData instanceof ListSchemaNode)) {
994 HashMap<QName,Object> keys = this.resolveKeysFromData(((ListSchemaNode) schemaOfData), data);
995 iiBuilder.nodeWithKey(schemaOfData.getQName(), keys);
998 iiBuilder.node(schemaOfData.getQName());
1001 InstanceIdentifier instance = iiBuilder.toInstance();
1002 MountInstance mountPoint = null;
1003 if (identifierWithSchemaNode != null) {
1004 mountPoint=identifierWithSchemaNode.getMountPoint();
1007 return new InstanceIdWithSchemaNode(instance, schemaOfData, mountPoint);
1010 private HashMap<QName,Object> resolveKeysFromData(final ListSchemaNode listNode,
1011 final CompositeNode dataNode) {
1012 final HashMap<QName,Object> keyValues = new HashMap<QName, Object>();
1013 List<QName> _keyDefinition = listNode.getKeyDefinition();
1014 for (final QName key : _keyDefinition) {
1015 SimpleNode<? extends Object> head = null;
1016 String localName = key.getLocalName();
1017 List<SimpleNode<? extends Object>> simpleNodesByName = dataNode.getSimpleNodesByName(localName);
1018 if (simpleNodesByName != null) {
1019 head = Iterables.getFirst(simpleNodesByName, null);
1022 Object dataNodeKeyValueObject = null;
1024 dataNodeKeyValueObject = head.getValue();
1027 if (dataNodeKeyValueObject == null) {
1028 throw new RestconfDocumentedException(
1029 "Data contains list \"" + dataNode.getNodeType().getLocalName() +
1030 "\" which does not contain key: \"" + key.getLocalName() + "\"",
1031 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1034 keyValues.put(key, dataNodeKeyValueObject);
1040 private boolean endsWithMountPoint(final String identifier) {
1041 return identifier.endsWith(ControllerContext.MOUNT) ||
1042 identifier.endsWith(ControllerContext.MOUNT + "/");
1045 private boolean representsMountPointRootData(final CompositeNode data) {
1046 URI namespace = this.namespace(data);
1047 return (SchemaContext.NAME.getNamespace().equals( namespace ) /* ||
1048 MOUNT_POINT_MODULE_NAME.equals( namespace.toString() )*/ ) &&
1049 SchemaContext.NAME.getLocalName().equals( this.localName(data) );
1052 private String addMountPointIdentifier(final String identifier) {
1053 boolean endsWith = identifier.endsWith("/");
1055 return (identifier + ControllerContext.MOUNT);
1058 return identifier + "/" + ControllerContext.MOUNT;
1061 private CompositeNode normalizeNode(final CompositeNode node, final DataSchemaNode schema,
1062 final MountInstance mountPoint) {
1063 if (schema == null) {
1064 QName nodeType = node == null ? null : node.getNodeType();
1065 String localName = nodeType == null ? null : nodeType.getLocalName();
1067 throw new RestconfDocumentedException(
1068 "Data schema node was not found for " + localName,
1069 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1072 if (!(schema instanceof DataNodeContainer)) {
1073 throw new RestconfDocumentedException(
1074 "Root element has to be container or list yang datatype.",
1075 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1078 if ((node instanceof CompositeNodeWrapper)) {
1079 boolean isChangeAllowed = ((CompositeNodeWrapper) node).isChangeAllowed();
1080 if (isChangeAllowed) {
1082 this.normalizeNode(((CompositeNodeWrapper) node), schema, null, mountPoint);
1084 catch (IllegalArgumentException e) {
1085 throw new RestconfDocumentedException(
1086 e.getMessage(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1090 return ((CompositeNodeWrapper) node).unwrap();
1096 private void normalizeNode(final NodeWrapper<? extends Object> nodeBuilder,
1097 final DataSchemaNode schema, final QName previousAugment,
1098 final MountInstance mountPoint) {
1099 if (schema == null) {
1100 throw new RestconfDocumentedException(
1101 "Data has bad format.\n\"" + nodeBuilder.getLocalName() +
1102 "\" does not exist in yang schema.",
1103 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1106 QName currentAugment = null;
1107 if (nodeBuilder.getQname() != null) {
1108 currentAugment = previousAugment;
1111 currentAugment = this.normalizeNodeName(nodeBuilder, schema, previousAugment, mountPoint);
1112 if (nodeBuilder.getQname() == null) {
1113 throw new RestconfDocumentedException(
1114 "Data has bad format.\nIf data is in XML format then namespace for \"" +
1115 nodeBuilder.getLocalName() +
1116 "\" should be \"" + schema.getQName().getNamespace() + "\".\n" +
1117 "If data is in JSON format then module name for \"" + nodeBuilder.getLocalName() +
1118 "\" should be corresponding to namespace \"" +
1119 schema.getQName().getNamespace() + "\".",
1120 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1124 if ( nodeBuilder instanceof CompositeNodeWrapper ) {
1125 if( schema instanceof DataNodeContainer ) {
1126 normalizeCompositeNode( (CompositeNodeWrapper)nodeBuilder, (DataNodeContainer)schema,
1127 mountPoint, currentAugment );
1129 else if( schema instanceof AnyXmlSchemaNode ) {
1130 normalizeAnyXmlNode( (CompositeNodeWrapper)nodeBuilder, (AnyXmlSchemaNode)schema );
1133 else if ( nodeBuilder instanceof SimpleNodeWrapper ) {
1134 normalizeSimpleNode( (SimpleNodeWrapper) nodeBuilder, schema, mountPoint );
1136 else if ((nodeBuilder instanceof EmptyNodeWrapper)) {
1137 normalizeEmptyNode( (EmptyNodeWrapper) nodeBuilder, schema );
1141 private void normalizeAnyXmlNode( final CompositeNodeWrapper compositeNode, final AnyXmlSchemaNode schema ) {
1142 List<NodeWrapper<?>> children = compositeNode.getValues();
1143 for( NodeWrapper<? extends Object> child : children ) {
1144 child.setNamespace( schema.getQName().getNamespace() );
1145 if( child instanceof CompositeNodeWrapper ) {
1146 normalizeAnyXmlNode( (CompositeNodeWrapper)child, schema );
1151 private void normalizeEmptyNode( final EmptyNodeWrapper emptyNodeBuilder, final DataSchemaNode schema ) {
1152 if ((schema instanceof LeafSchemaNode)) {
1153 emptyNodeBuilder.setComposite(false);
1156 if ((schema instanceof ContainerSchemaNode)) {
1157 // FIXME: Add presence check
1158 emptyNodeBuilder.setComposite(true);
1163 private void normalizeSimpleNode( final SimpleNodeWrapper simpleNode, final DataSchemaNode schema,
1164 final MountInstance mountPoint ) {
1165 final Object value = simpleNode.getValue();
1166 Object inputValue = value;
1167 TypeDefinition<? extends Object> typeDefinition = this.typeDefinition(schema);
1168 if ((typeDefinition instanceof IdentityrefTypeDefinition)) {
1169 if ((value instanceof String)) {
1170 inputValue = new IdentityValuesDTO( simpleNode.getNamespace().toString(),
1171 (String) value, null, (String) value );
1172 } // else value is already instance of IdentityValuesDTO
1175 Object outputValue = inputValue;
1177 if( typeDefinition != null ) {
1178 Codec<Object,Object> codec = RestCodec.from(typeDefinition, mountPoint);
1179 outputValue = codec == null ? null : codec.deserialize(inputValue);
1182 simpleNode.setValue(outputValue);
1185 private void normalizeCompositeNode( final CompositeNodeWrapper compositeNodeBuilder,
1186 final DataNodeContainer schema, final MountInstance mountPoint,
1187 final QName currentAugment ) {
1188 final List<NodeWrapper<?>> children = compositeNodeBuilder.getValues();
1189 checkNodeMultiplicityAccordingToSchema(schema,children);
1190 for (final NodeWrapper<? extends Object> child : children) {
1191 final List<DataSchemaNode> potentialSchemaNodes =
1192 this.controllerContext.findInstanceDataChildrenByName(
1193 schema, child.getLocalName());
1195 if (potentialSchemaNodes.size() > 1 && child.getNamespace() == null) {
1196 StringBuilder builder = new StringBuilder();
1197 for (final DataSchemaNode potentialSchemaNode : potentialSchemaNodes) {
1198 builder.append(" ").append(potentialSchemaNode.getQName().getNamespace().toString())
1202 throw new RestconfDocumentedException(
1203 "Node \"" + child.getLocalName() +
1204 "\" is added as augment from more than one module. " +
1205 "Therefore node must have namespace (XML format) or module name (JSON format)." +
1206 "\nThe node is added as augment from modules with namespaces:\n" + builder,
1207 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1210 boolean rightNodeSchemaFound = false;
1211 for (final DataSchemaNode potentialSchemaNode : potentialSchemaNodes) {
1212 if (!rightNodeSchemaFound) {
1213 final QName potentialCurrentAugment =
1214 this.normalizeNodeName(child, potentialSchemaNode, currentAugment, mountPoint);
1215 if (child.getQname() != null ) {
1216 this.normalizeNode(child, potentialSchemaNode, potentialCurrentAugment, mountPoint);
1217 rightNodeSchemaFound = true;
1222 if (!rightNodeSchemaFound) {
1223 throw new RestconfDocumentedException(
1224 "Schema node \"" + child.getLocalName() + "\" was not found in module.",
1225 ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT );
1229 if ((schema instanceof ListSchemaNode)) {
1230 ListSchemaNode listSchemaNode = (ListSchemaNode)schema;
1231 final List<QName> listKeys = listSchemaNode.getKeyDefinition();
1232 for (final QName listKey : listKeys) {
1233 boolean foundKey = false;
1234 for (final NodeWrapper<? extends Object> child : children) {
1235 if (Objects.equal(child.unwrap().getNodeType().getLocalName(), listKey.getLocalName())) {
1241 throw new RestconfDocumentedException(
1242 "Missing key '" + listKey.getLocalName() +
1243 "' for list '" + listSchemaNode.getQName().getLocalName() + "' in the message body",
1244 ErrorType.PROTOCOL, ErrorTag.DATA_MISSING );
1250 private void checkNodeMultiplicityAccordingToSchema(final DataNodeContainer dataNodeContainer,
1251 final List<NodeWrapper<?>> nodes) {
1252 Map<String, Integer> equalNodeNamesToCounts = new HashMap<String, Integer>();
1253 for (NodeWrapper<?> child : nodes) {
1254 Integer count = equalNodeNamesToCounts.get(child.getLocalName());
1255 equalNodeNamesToCounts.put(child.getLocalName(), count == null ? 1 : ++count);
1258 for (DataSchemaNode childSchemaNode : dataNodeContainer.getChildNodes()) {
1259 if (childSchemaNode instanceof ContainerSchemaNode || childSchemaNode instanceof LeafSchemaNode) {
1260 String localName = childSchemaNode.getQName().getLocalName();
1261 Integer count = equalNodeNamesToCounts.get(localName);
1262 if (count != null && count > 1) {
1263 throw new RestconfDocumentedException(
1264 "Multiple input data elements were specified for '"
1265 + childSchemaNode.getQName().getLocalName()
1266 + "'. The data for this element type can only be specified once.",
1267 ErrorType.APPLICATION, ErrorTag.BAD_ELEMENT);
1273 private QName normalizeNodeName(final NodeWrapper<? extends Object> nodeBuilder,
1274 final DataSchemaNode schema, final QName previousAugment,
1275 final MountInstance mountPoint) {
1276 QName validQName = schema.getQName();
1277 QName currentAugment = previousAugment;
1278 if (schema.isAugmenting()) {
1279 currentAugment = schema.getQName();
1281 else if (previousAugment != null &&
1282 !Objects.equal( schema.getQName().getNamespace(), previousAugment.getNamespace())) {
1283 validQName = QName.create(currentAugment, schema.getQName().getLocalName());
1286 String moduleName = null;
1287 if (mountPoint == null) {
1288 moduleName = controllerContext.findModuleNameByNamespace(validQName.getNamespace());
1291 moduleName = controllerContext.findModuleNameByNamespace(mountPoint, validQName.getNamespace());
1294 if (nodeBuilder.getNamespace() == null ||
1295 Objects.equal(nodeBuilder.getNamespace(), validQName.getNamespace()) ||
1296 Objects.equal(nodeBuilder.getNamespace().toString(), moduleName) /*||
1297 Note: this check is wrong - can never be true as it compares a URI with a String
1298 not sure what the intention is so commented out...
1299 Objects.equal(nodeBuilder.getNamespace(), MOUNT_POINT_MODULE_NAME)*/ ) {
1301 nodeBuilder.setQname(validQName);
1304 return currentAugment;
1307 private URI namespace(final CompositeNode data) {
1308 if (data instanceof CompositeNodeWrapper) {
1309 return ((CompositeNodeWrapper)data).getNamespace();
1311 else if (data != null) {
1312 return data.getNodeType().getNamespace();
1315 throw new IllegalArgumentException("Unhandled parameter types: " +
1316 Arrays.<Object>asList(data).toString());
1320 private String localName(final CompositeNode data) {
1321 if (data instanceof CompositeNodeWrapper) {
1322 return ((CompositeNodeWrapper)data).getLocalName();
1324 else if (data != null) {
1325 return data.getNodeType().getLocalName();
1328 throw new IllegalArgumentException("Unhandled parameter types: " +
1329 Arrays.<Object>asList(data).toString());
1333 private String getName(final CompositeNode data) {
1334 if (data instanceof CompositeNodeWrapper) {
1335 return ((CompositeNodeWrapper)data).getLocalName();
1337 else if (data != null) {
1338 return data.getNodeType().getLocalName();
1341 throw new IllegalArgumentException("Unhandled parameter types: " +
1342 Arrays.<Object>asList(data).toString());
1346 private TypeDefinition<? extends Object> _typeDefinition(final LeafSchemaNode node) {
1347 TypeDefinition<?> baseType = node.getType();
1348 while (baseType.getBaseType() != null) {
1349 baseType = baseType.getBaseType();
1355 private TypeDefinition<? extends Object> typeDefinition(final LeafListSchemaNode node) {
1356 TypeDefinition<?> baseType = node.getType();
1357 while (baseType.getBaseType() != null) {
1358 baseType = baseType.getBaseType();
1364 private TypeDefinition<? extends Object> typeDefinition(final DataSchemaNode node) {
1365 if (node instanceof LeafListSchemaNode) {
1366 return typeDefinition((LeafListSchemaNode)node);
1368 else if (node instanceof LeafSchemaNode) {
1369 return _typeDefinition((LeafSchemaNode)node);
1371 else if (node instanceof AnyXmlSchemaNode) {
1375 throw new IllegalArgumentException("Unhandled parameter types: " +
1376 Arrays.<Object>asList(node).toString());