BUG 1330 - list key counts|values diff in payload and URI
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / restconf / impl / RestconfImpl.java
1 /**
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  * Copyright (c) 2014 Brocade Communication Systems, Inc.
4  *
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
8  */
9 package org.opendaylight.controller.sal.restconf.impl;
10
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;
18 import java.net.URI;
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;
28 import java.util.Map;
29 import java.util.Set;
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;
79
80 public class RestconfImpl implements RestconfService {
81     private enum UriParameters {
82         PRETTY_PRINT( "prettyPrint"),
83         DEPTH( "depth");
84
85         private String uriParameterName;
86         UriParameters(String uriParameterName) {
87             this.uriParameterName = uriParameterName;
88         }
89
90         @Override
91         public String toString() {
92             return uriParameterName;
93         }
94     }
95
96     private final static RestconfImpl INSTANCE = new RestconfImpl();
97
98     private static final int CHAR_NOT_FOUND = -1;
99
100     private final static String MOUNT_POINT_MODULE_NAME = "ietf-netconf";
101
102     private final static SimpleDateFormat REVISION_FORMAT =  new SimpleDateFormat("yyyy-MM-dd");
103
104     private final static String SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote";
105
106     private final static String SAL_REMOTE_RPC_SUBSRCIBE = "create-data-change-event-subscription";
107
108     private BrokerFacade broker;
109
110     private ControllerContext controllerContext;
111
112     public void setBroker(final BrokerFacade broker) {
113         this.broker = broker;
114     }
115
116     public void setControllerContext(final ControllerContext controllerContext) {
117         this.controllerContext = controllerContext;
118     }
119
120     private RestconfImpl() {
121     }
122
123     public static RestconfImpl getInstance() {
124         return INSTANCE;
125     }
126
127     @Override
128     public StructuredData getModules(final UriInfo uriInfo) {
129         final Module restconfModule = this.getRestconfModule();
130
131         final List<Node<?>> modulesAsData = new ArrayList<Node<?>>();
132         final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
133                 restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
134
135         Set<Module> allModules = this.controllerContext.getAllModules();
136         for (final Module module : allModules) {
137             CompositeNode moduleCompositeNode = this.toModuleCompositeNode(module, moduleSchemaNode);
138             modulesAsData.add(moduleCompositeNode);
139         }
140
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 ));
146     }
147
148     @Override
149     public StructuredData getAvailableStreams(final UriInfo uriInfo) {
150         Set<String> availableStreams = Notificator.getStreamNames();
151
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));
158         }
159
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 ));
165     }
166
167     @Override
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);
176         }
177         else {
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 );
181         }
182
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);
187
188         for (final Module module : modules) {
189             modulesAsData.add(this.toModuleCompositeNode(module, moduleSchemaNode));
190         }
191
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 ));
197     }
198
199     @Override
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);
209         }
210         else {
211             module = this.controllerContext.findModuleByNameAndRevision(moduleNameAndRevision);
212         }
213
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 );
219         }
220
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 ));
226     }
227
228     @Override
229     public StructuredData getOperations(final UriInfo uriInfo) {
230         Set<Module> allModules = this.controllerContext.getAllModules();
231         return this.operationsFromModulesToStructuredData(allModules, null,parsePrettyPrintParameter(uriInfo));
232     }
233
234     @Override
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);
243         }
244         else {
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 );
248         }
249
250         return this.operationsFromModulesToStructuredData(modules, mountPoint,parsePrettyPrintParameter(uriInfo));
251     }
252
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);
271
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);
277
278                 EmptyType instance = EmptyType.getInstance();
279                 fakeRpcSchemaNode.setType(instance);
280                 fakeOperationsSchemaNode.addChildNode(fakeRpcSchemaNode.build());
281             }
282         }
283
284         final CompositeNode operationsNode =
285                 NodeFactory.createImmutableCompositeNode(qName, null, operationsAsData);
286         ContainerSchemaNode schemaNode = fakeOperationsSchemaNode.build();
287         return new StructuredData(operationsNode, schemaNode, mountPoint,prettyPrint);
288     }
289
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 );
296         }
297
298         return restconfModule;
299     }
300
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());
306         }
307         else {
308             moduleNameAndRevision = identifier;
309         }
310
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 );
318         }
319
320         try {
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);
325         }
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 );
330         }
331     }
332
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),
337                         "name");
338         final DataSchemaNode nameSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
339         streamNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(nameSchemaNode.getQName(), null,
340                 streamName));
341
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"));
347
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)));
353
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(),
358                 null, ""));
359
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(),
364                 null, ""));
365
366         return NodeFactory.createImmutableCompositeNode(streamSchemaNode.getQName(), null, streamNodeValues);
367     }
368
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()));
376
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)));
383
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()));
389
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()));
396         }
397
398         return NodeFactory.createImmutableCompositeNode(moduleSchemaNode.getQName(), null, moduleNodeValues);
399     }
400
401     @Override
402     public Object getRoot() {
403         return null;
404     }
405
406     @Override
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));
414         }
415
416         validateInput( rpc.getRpcDefinition().getInput(), payload );
417
418         return callRpc(rpc, payload,parsePrettyPrintParameter(uriInfo));
419     }
420
421     private void validateInput(final DataSchemaNode inputSchema, final CompositeNode payload) {
422         if( inputSchema != null && payload == null )
423         {
424             //expected a non null payload
425             throw new RestconfDocumentedException( "Input is required.",
426                     ErrorType.PROTOCOL,
427                     ErrorTag.MALFORMED_MESSAGE );
428         }
429         else if( inputSchema == null && payload != null )
430         {
431             //did not expect any input
432             throw new RestconfDocumentedException( "No input expected.",
433                     ErrorType.PROTOCOL,
434                     ErrorTag.MALFORMED_MESSAGE );
435         }
436         //else
437         //{
438         //TODO: Validate "mandatory" and "config" values here??? Or should those be
439         // validate in a more central location inside MD-SAL core.
440         //}
441     }
442
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();
449
450         if (!(pathValue instanceof InstanceIdentifier)) {
451             throw new RestconfDocumentedException(
452                     "Instance identifier was not normalized correctly.",
453                     ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED );
454         }
455
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);
461         }
462
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 );
467         }
468
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);
473
474         final MutableCompositeNode responseData = NodeFactory.createMutableCompositeNode(
475                 rpc.getOutput().getQName(), null, output, null, null);
476
477         if (!Notificator.existListenerFor(pathIdentifier)) {
478             Notificator.createListener(pathIdentifier, streamName);
479         }
480
481         return new StructuredData(responseData, rpc.getOutput(), null,prettyPrint);
482     }
483
484     @Override
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 );
489         }
490         return invokeRpc( identifier, (CompositeNode)null,uriInfo);
491     }
492
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();
501
502             int startOfRemoteRpcName = identifier.lastIndexOf(ControllerContext.MOUNT)
503                     + ControllerContext.MOUNT.length() + 1;
504             String remoteRpcName = identifier.substring(startOfRemoteRpcName);
505             identifierEncoded = remoteRpcName;
506
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.",
511                             identifier);
512             throw new RestconfDocumentedException(
513                     slashErrorMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
514         } else {
515             identifierEncoded = identifier;
516         }
517
518         final String identifierDecoded = controllerContext.urlPathArgDecode(identifierEncoded);
519         RpcDefinition rpc = controllerContext.getRpcDefinition(identifierDecoded);
520
521         if (rpc == null) {
522             throw new RestconfDocumentedException(
523                     "RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT );
524         }
525
526         if (mountPoint == null) {
527             return new BrokerRpcExecutor(rpc, broker);
528         } else {
529             return new MountPointRpcExecutor(rpc, mountPoint);
530         }
531
532     }
533
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 );
538         }
539
540         CompositeNode rpcRequest = null;
541         RpcDefinition rpc = rpcExecutor.getRpcDefinition();
542         QName rpcName = rpc.getQName();
543
544         if (payload == null) {
545             rpcRequest = NodeFactory.createMutableCompositeNode(rpcName, null, null, null, null);
546         } else {
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);
550         }
551
552         RpcResult<CompositeNode> rpcResult = rpcExecutor.invokeRpc(rpcRequest);
553
554         checkRpcSuccessAndThrowException(rpcResult);
555
556         if (rpcResult.getResult() == null) {
557             return null;
558         }
559
560         if( rpc.getOutput() == null )
561         {
562             return null; //no output, nothing to send back.
563         }
564
565         return new StructuredData(rpcResult.getResult(), rpc.getOutput(), null,prettyPrint);
566     }
567
568     private void checkRpcSuccessAndThrowException(final RpcResult<CompositeNode> rpcResult) {
569         if (rpcResult.isSuccessful() == false) {
570
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 );
576             }
577
578             List<RestconfError> errorList = Lists.newArrayList();
579             for( RpcError rpcError: rpcErrors ) {
580                 errorList.add( new RestconfError( rpcError ) );
581             }
582
583             throw new RestconfDocumentedException( errorList );
584         }
585     }
586
587     @Override
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());
594         }
595         else {
596             data = broker.readConfigurationData(iiWithData.getInstanceIdentifier());
597         }
598
599         data = pruneDataAtDepth( data, parseDepthParameter( uriInfo ) );
600         boolean prettyPrintMode = parsePrettyPrintParameter( uriInfo );
601         return new StructuredData(data, iiWithData.getSchemaNode(), iiWithData.getMountPoint(),prettyPrintMode);
602     }
603
604     @SuppressWarnings("unchecked")
605     private <T extends Node<?>> T pruneDataAtDepth( final T node, final Integer depth ) {
606         if( depth == null ) {
607             return node;
608         }
609
610         if( node instanceof CompositeNode ) {
611             ImmutableList.Builder<Node<?>> newChildNodes = ImmutableList.<Node<?>> builder();
612             if( depth > 1 ) {
613                 for( Node<?> childNode: ((CompositeNode)node).getValue() ) {
614                     newChildNodes.add( pruneDataAtDepth( childNode, depth - 1 ) );
615                 }
616             }
617
618             return (T) ImmutableCompositeNode.create( node.getNodeType(), newChildNodes.build() );
619         }
620         else { // SimpleNode
621             return node;
622         }
623     }
624
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 ) ) {
628             return null;
629         }
630
631         try {
632             Integer depth = Integer.valueOf( param );
633             if( depth < 1 ) {
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\"" ) );
637             }
638
639             return depth;
640         }
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\"" ) );
646         }
647     }
648
649     @Override
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());
656         }
657         else {
658             data = broker.readOperationalData(iiWithData.getInstanceIdentifier());
659         }
660
661         data = pruneDataAtDepth( data, parseDepthParameter( info ) );
662         boolean prettyPrintMode = parsePrettyPrintParameter( info );
663         return new StructuredData(data, iiWithData.getSchemaNode(), mountPoint,prettyPrintMode);
664     }
665
666     private boolean parsePrettyPrintParameter(UriInfo info) {
667         String param = info.getQueryParameters(false).getFirst(UriParameters.PRETTY_PRINT.toString());
668         return Boolean.parseBoolean(param);
669     }
670
671     @Override
672     public Response updateConfigurationData(final String identifier, final CompositeNode payload) {
673         final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
674
675         validateInput(iiWithData.getSchemaNode(), payload);
676
677         MountInstance mountPoint = iiWithData.getMountPoint();
678         final CompositeNode value = this.normalizeNode(payload, iiWithData.getSchemaNode(), mountPoint);
679         validateListKeysEqualityInPayloadAndUri(iiWithData, payload);
680         RpcResult<TransactionStatus> status = null;
681
682         try {
683             if (mountPoint != null) {
684                 status = broker.commitConfigurationDataPutBehindMountPoint(
685                         mountPoint, iiWithData.getInstanceIdentifier(), value).get();
686             } else {
687                 status = broker.commitConfigurationDataPut(iiWithData.getInstanceIdentifier(), value).get();
688             }
689         }
690         catch( Exception e ) {
691             throw new RestconfDocumentedException( "Error updating data", e );
692         }
693
694         if( status.getResult() == TransactionStatus.COMMITED ) {
695             return Response.status(Status.OK).build();
696         }
697
698         return Response.status(Status.INTERNAL_SERVER_ERROR).build();
699     }
700
701     /**
702      * Validates whether keys in {@code payload} are equal to values of keys in
703      * {@code iiWithData} for list schema node
704      *
705      * @throws RestconfDocumentedException
706      *             if key values or key count in payload and URI isn't equal
707      *
708      */
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)
716                         .getKeyValues();
717                 isEqualUriAndPayloadKeyValues(uriKeyValues, payload, keyDefinitions);
718             }
719         }
720     }
721
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);
730             }
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);
736             }
737
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);
743             }
744         }
745     }
746
747     @Override
748     public Response createConfigurationData(final String identifier, final CompositeNode payload) {
749         if( payload == null ) {
750             throw new RestconfDocumentedException( "Input is required.",
751                     ErrorType.PROTOCOL,
752                     ErrorTag.MALFORMED_MESSAGE );
753         }
754
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 );
760         }
761
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
766
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 );
772             }
773
774             final String completeIdentifier = this.addMountPointIdentifier(identifier);
775             iiWithData = this.controllerContext.toInstanceIdentifier(completeIdentifier);
776
777             value = this.normalizeNode(payload, iiWithData.getSchemaNode(), iiWithData.getMountPoint());
778         }
779         else {
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 );
789             }
790
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);
795
796             iiWithData = this.addLastIdentifierFromData(incompleteInstIdWithData, value, schemaNode);
797         }
798
799         RpcResult<TransactionStatus> status = null;
800         MountInstance mountPoint = iiWithData.getMountPoint();
801         try {
802             if (mountPoint != null) {
803                 Future<RpcResult<TransactionStatus>> future =
804                         broker.commitConfigurationDataPostBehindMountPoint(
805                                 mountPoint, iiWithData.getInstanceIdentifier(), value);
806                 status = future == null ? null : future.get();
807             }
808             else {
809                 Future<RpcResult<TransactionStatus>> future =
810                         broker.commitConfigurationDataPost(iiWithData.getInstanceIdentifier(), value);
811                 status = future == null ? null : future.get();
812             }
813         }
814         catch( Exception e ) {
815             throw new RestconfDocumentedException( "Error creating data", e );
816         }
817
818         if (status == null) {
819             return Response.status(Status.ACCEPTED).build();
820         }
821
822         if( status.getResult() == TransactionStatus.COMMITED ) {
823             return Response.status(Status.NO_CONTENT).build();
824         }
825
826         return Response.status(Status.INTERNAL_SERVER_ERROR).build();
827     }
828
829     @Override
830     public Response createConfigurationData(final CompositeNode payload) {
831         if( payload == null ) {
832             throw new RestconfDocumentedException( "Input is required.",
833                     ErrorType.PROTOCOL,
834                     ErrorTag.MALFORMED_MESSAGE );
835         }
836
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 );
842         }
843
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 );
849         }
850
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();
858
859         try {
860             if (mountPoint != null) {
861                 Future<RpcResult<TransactionStatus>> future =
862                         broker.commitConfigurationDataPostBehindMountPoint(
863                                 mountPoint, iiWithData.getInstanceIdentifier(), value);
864                 status = future == null ? null : future.get();
865             }
866             else {
867                 Future<RpcResult<TransactionStatus>> future =
868                         broker.commitConfigurationDataPost(iiWithData.getInstanceIdentifier(), value);
869                 status = future == null ? null : future.get();
870             }
871         }
872         catch( Exception e ) {
873             throw new RestconfDocumentedException( "Error creating data", e );
874         }
875
876         if (status == null) {
877             return Response.status(Status.ACCEPTED).build();
878         }
879
880         if( status.getResult() == TransactionStatus.COMMITED ) {
881             return Response.status(Status.NO_CONTENT).build();
882         }
883
884         return Response.status(Status.INTERNAL_SERVER_ERROR).build();
885     }
886
887     @Override
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();
892
893         try {
894             if (mountPoint != null) {
895                 status = broker.commitConfigurationDataDeleteBehindMountPoint(
896                         mountPoint, iiWithData.getInstanceIdentifier()).get();
897             }
898             else {
899                 status = broker.commitConfigurationDataDelete(iiWithData.getInstanceIdentifier()).get();
900             }
901         }
902         catch( Exception e ) {
903             throw new RestconfDocumentedException( "Error creating data", e );
904         }
905
906         if( status.getResult() == TransactionStatus.COMMITED ) {
907             return Response.status(Status.OK).build();
908         }
909
910         return Response.status(Status.INTERNAL_SERVER_ERROR).build();
911     }
912
913     @Override
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 );
919         }
920
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 );
925         }
926
927         broker.registerToListenDataChanges(listener);
928
929         final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
930         UriBuilder port = uriBuilder.port(WebSocketServer.getInstance().getPort());
931         final URI uriToWebsocketServer = port.replacePath(streamName).build();
932
933         return Response.status(Status.OK).location(uriToWebsocketServer).build();
934     }
935
936     private Module findModule(final MountInstance mountPoint, final CompositeNode data) {
937         if (data instanceof CompositeNodeWrapper) {
938             return findModule(mountPoint, (CompositeNodeWrapper)data);
939         }
940         else if (data != null) {
941             URI namespace = data.getNodeType().getNamespace();
942             if (mountPoint != null) {
943                 return this.controllerContext.findModuleByNamespace(mountPoint, namespace);
944             }
945             else {
946                 return this.controllerContext.findModuleByNamespace(namespace);
947             }
948         }
949         else {
950             throw new IllegalArgumentException("Unhandled parameter types: " +
951                     Arrays.<Object>asList(mountPoint, data).toString());
952         }
953     }
954
955     private Module findModule(final MountInstance mountPoint, final CompositeNodeWrapper data) {
956         URI namespace = data.getNamespace();
957         Preconditions.<URI>checkNotNull(namespace);
958
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());
964             }
965         }
966         else {
967             module = this.controllerContext.findModuleByNamespace(namespace);
968             if (module == null) {
969                 module = this.controllerContext.findModuleByName(namespace.toString());
970             }
971         }
972
973         return module;
974     }
975
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();
982         }
983
984         final InstanceIdentifier iiOriginal = instanceIdentifier;
985         InstanceIdentifierBuilder iiBuilder = null;
986         if (iiOriginal == null) {
987             iiBuilder = InstanceIdentifier.builder();
988         }
989         else {
990             iiBuilder = InstanceIdentifier.builder(iiOriginal);
991         }
992
993         if ((schemaOfData instanceof ListSchemaNode)) {
994             HashMap<QName,Object> keys = this.resolveKeysFromData(((ListSchemaNode) schemaOfData), data);
995             iiBuilder.nodeWithKey(schemaOfData.getQName(), keys);
996         }
997         else {
998             iiBuilder.node(schemaOfData.getQName());
999         }
1000
1001         InstanceIdentifier instance = iiBuilder.toInstance();
1002         MountInstance mountPoint = null;
1003         if (identifierWithSchemaNode != null) {
1004             mountPoint=identifierWithSchemaNode.getMountPoint();
1005         }
1006
1007         return new InstanceIdWithSchemaNode(instance, schemaOfData, mountPoint);
1008     }
1009
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);
1020             }
1021
1022             Object dataNodeKeyValueObject = null;
1023             if (head != null) {
1024                 dataNodeKeyValueObject = head.getValue();
1025             }
1026
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 );
1032             }
1033
1034             keyValues.put(key, dataNodeKeyValueObject);
1035         }
1036
1037         return keyValues;
1038     }
1039
1040     private boolean endsWithMountPoint(final String identifier) {
1041         return identifier.endsWith(ControllerContext.MOUNT) ||
1042                 identifier.endsWith(ControllerContext.MOUNT + "/");
1043     }
1044
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) );
1050     }
1051
1052     private String addMountPointIdentifier(final String identifier) {
1053         boolean endsWith = identifier.endsWith("/");
1054         if (endsWith) {
1055             return (identifier + ControllerContext.MOUNT);
1056         }
1057
1058         return identifier + "/" + ControllerContext.MOUNT;
1059     }
1060
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();
1066
1067             throw new RestconfDocumentedException(
1068                     "Data schema node was not found for " + localName,
1069                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1070         }
1071
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 );
1076         }
1077
1078         if ((node instanceof CompositeNodeWrapper)) {
1079             boolean isChangeAllowed = ((CompositeNodeWrapper) node).isChangeAllowed();
1080             if (isChangeAllowed) {
1081                 try {
1082                     this.normalizeNode(((CompositeNodeWrapper) node), schema, null, mountPoint);
1083                 }
1084                 catch (IllegalArgumentException e) {
1085                     throw new RestconfDocumentedException(
1086                             e.getMessage(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1087                 }
1088             }
1089
1090             return ((CompositeNodeWrapper) node).unwrap();
1091         }
1092
1093         return node;
1094     }
1095
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 );
1104         }
1105
1106         QName currentAugment = null;
1107         if (nodeBuilder.getQname() != null) {
1108             currentAugment = previousAugment;
1109         }
1110         else {
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 );
1121             }
1122         }
1123
1124         if ( nodeBuilder instanceof CompositeNodeWrapper ) {
1125             if( schema instanceof DataNodeContainer ) {
1126                 normalizeCompositeNode( (CompositeNodeWrapper)nodeBuilder, (DataNodeContainer)schema,
1127                         mountPoint, currentAugment );
1128             }
1129             else if( schema instanceof AnyXmlSchemaNode ) {
1130                 normalizeAnyXmlNode( (CompositeNodeWrapper)nodeBuilder, (AnyXmlSchemaNode)schema );
1131             }
1132         }
1133         else if ( nodeBuilder instanceof SimpleNodeWrapper ) {
1134             normalizeSimpleNode( (SimpleNodeWrapper) nodeBuilder, schema, mountPoint );
1135         }
1136         else if ((nodeBuilder instanceof EmptyNodeWrapper)) {
1137             normalizeEmptyNode( (EmptyNodeWrapper) nodeBuilder, schema );
1138         }
1139     }
1140
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 );
1147             }
1148         }
1149     }
1150
1151     private void normalizeEmptyNode( final EmptyNodeWrapper emptyNodeBuilder, final DataSchemaNode schema ) {
1152         if ((schema instanceof LeafSchemaNode)) {
1153             emptyNodeBuilder.setComposite(false);
1154         }
1155         else {
1156             if ((schema instanceof ContainerSchemaNode)) {
1157                 // FIXME: Add presence check
1158                 emptyNodeBuilder.setComposite(true);
1159             }
1160         }
1161     }
1162
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
1173         }
1174
1175         Object outputValue = inputValue;
1176
1177         if( typeDefinition != null ) {
1178             Codec<Object,Object> codec = RestCodec.from(typeDefinition, mountPoint);
1179             outputValue = codec == null ? null : codec.deserialize(inputValue);
1180         }
1181
1182         simpleNode.setValue(outputValue);
1183     }
1184
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());
1194
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())
1199                     .append("\n");
1200                 }
1201
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 );
1208             }
1209
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;
1218                     }
1219                 }
1220             }
1221
1222             if (!rightNodeSchemaFound) {
1223                 throw new RestconfDocumentedException(
1224                         "Schema node \"" + child.getLocalName() + "\" was not found in module.",
1225                         ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT );
1226             }
1227         }
1228
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())) {
1236                         foundKey = true;
1237                     }
1238                 }
1239
1240                 if (!foundKey) {
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 );
1245                 }
1246             }
1247         }
1248     }
1249
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);
1256         }
1257
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);
1268                 }
1269             }
1270         }
1271     }
1272
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();
1280         }
1281         else if (previousAugment != null &&
1282                 !Objects.equal( schema.getQName().getNamespace(), previousAugment.getNamespace())) {
1283             validQName = QName.create(currentAugment, schema.getQName().getLocalName());
1284         }
1285
1286         String moduleName = null;
1287         if (mountPoint == null) {
1288             moduleName = controllerContext.findModuleNameByNamespace(validQName.getNamespace());
1289         }
1290         else {
1291             moduleName = controllerContext.findModuleNameByNamespace(mountPoint, validQName.getNamespace());
1292         }
1293
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)*/ ) {
1300
1301             nodeBuilder.setQname(validQName);
1302         }
1303
1304         return currentAugment;
1305     }
1306
1307     private URI namespace(final CompositeNode data) {
1308         if (data instanceof CompositeNodeWrapper) {
1309             return ((CompositeNodeWrapper)data).getNamespace();
1310         }
1311         else if (data != null) {
1312             return data.getNodeType().getNamespace();
1313         }
1314         else {
1315             throw new IllegalArgumentException("Unhandled parameter types: " +
1316                     Arrays.<Object>asList(data).toString());
1317         }
1318     }
1319
1320     private String localName(final CompositeNode data) {
1321         if (data instanceof CompositeNodeWrapper) {
1322             return ((CompositeNodeWrapper)data).getLocalName();
1323         }
1324         else if (data != null) {
1325             return data.getNodeType().getLocalName();
1326         }
1327         else {
1328             throw new IllegalArgumentException("Unhandled parameter types: " +
1329                     Arrays.<Object>asList(data).toString());
1330         }
1331     }
1332
1333     private String getName(final CompositeNode data) {
1334         if (data instanceof CompositeNodeWrapper) {
1335             return ((CompositeNodeWrapper)data).getLocalName();
1336         }
1337         else if (data != null) {
1338             return data.getNodeType().getLocalName();
1339         }
1340         else {
1341             throw new IllegalArgumentException("Unhandled parameter types: " +
1342                     Arrays.<Object>asList(data).toString());
1343         }
1344     }
1345
1346     private TypeDefinition<? extends Object> _typeDefinition(final LeafSchemaNode node) {
1347         TypeDefinition<?> baseType = node.getType();
1348         while (baseType.getBaseType() != null) {
1349             baseType = baseType.getBaseType();
1350         }
1351
1352         return baseType;
1353     }
1354
1355     private TypeDefinition<? extends Object> typeDefinition(final LeafListSchemaNode node) {
1356         TypeDefinition<?> baseType = node.getType();
1357         while (baseType.getBaseType() != null) {
1358             baseType = baseType.getBaseType();
1359         }
1360
1361         return baseType;
1362     }
1363
1364     private TypeDefinition<? extends Object> typeDefinition(final DataSchemaNode node) {
1365         if (node instanceof LeafListSchemaNode) {
1366             return typeDefinition((LeafListSchemaNode)node);
1367         }
1368         else if (node instanceof LeafSchemaNode) {
1369             return _typeDefinition((LeafSchemaNode)node);
1370         }
1371         else if (node instanceof AnyXmlSchemaNode) {
1372             return null;
1373         }
1374         else {
1375             throw new IllegalArgumentException("Unhandled parameter types: " +
1376                     Arrays.<Object>asList(node).toString());
1377         }
1378     }
1379 }

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.