Merge "Bug 1328: Improved argument checks in generated RPC Router"
[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.Set;
29 import java.util.concurrent.Future;
30 import javax.ws.rs.core.Response;
31 import javax.ws.rs.core.Response.Status;
32 import javax.ws.rs.core.UriBuilder;
33 import javax.ws.rs.core.UriInfo;
34 import org.apache.commons.lang3.StringUtils;
35 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
36 import org.opendaylight.controller.sal.core.api.mount.MountInstance;
37 import org.opendaylight.controller.sal.rest.api.Draft02;
38 import org.opendaylight.controller.sal.rest.api.RestconfService;
39 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
40 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
41 import org.opendaylight.controller.sal.restconf.rpc.impl.BrokerRpcExecutor;
42 import org.opendaylight.controller.sal.restconf.rpc.impl.MountPointRpcExecutor;
43 import org.opendaylight.controller.sal.restconf.rpc.impl.RpcExecutor;
44 import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter;
45 import org.opendaylight.controller.sal.streams.listeners.Notificator;
46 import org.opendaylight.controller.sal.streams.websockets.WebSocketServer;
47 import org.opendaylight.yangtools.concepts.Codec;
48 import org.opendaylight.yangtools.yang.common.QName;
49 import org.opendaylight.yangtools.yang.common.RpcError;
50 import org.opendaylight.yangtools.yang.common.RpcResult;
51 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
52 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
53 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder;
54 import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode;
55 import org.opendaylight.yangtools.yang.data.api.Node;
56 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
57 import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
58 import org.opendaylight.yangtools.yang.data.impl.NodeFactory;
59 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
60 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
61 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
62 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
63 import org.opendaylight.yangtools.yang.model.api.FeatureDefinition;
64 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
65 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
66 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
67 import org.opendaylight.yangtools.yang.model.api.Module;
68 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
69 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
70 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
71 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
72 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
73 import org.opendaylight.yangtools.yang.model.util.EmptyType;
74 import org.opendaylight.yangtools.yang.parser.builder.impl.ContainerSchemaNodeBuilder;
75 import org.opendaylight.yangtools.yang.parser.builder.impl.LeafSchemaNodeBuilder;
76
77 public class RestconfImpl implements RestconfService {
78     private enum UriParameters {
79         PRETTY_PRINT( "prettyPrint"),
80         DEPTH( "depth");
81
82         private String uriParameterName;
83         UriParameters(String uriParameterName) {
84             this.uriParameterName = uriParameterName;
85         }
86
87         @Override
88         public String toString() {
89             return uriParameterName;
90         }
91     }
92
93     private final static RestconfImpl INSTANCE = new RestconfImpl();
94
95     private static final int CHAR_NOT_FOUND = -1;
96
97     private final static String MOUNT_POINT_MODULE_NAME = "ietf-netconf";
98
99     private final static SimpleDateFormat REVISION_FORMAT =  new SimpleDateFormat("yyyy-MM-dd");
100
101     private final static String SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote";
102
103     private final static String SAL_REMOTE_RPC_SUBSRCIBE = "create-data-change-event-subscription";
104
105     private BrokerFacade broker;
106
107     private ControllerContext controllerContext;
108
109     public void setBroker(final BrokerFacade broker) {
110         this.broker = broker;
111     }
112
113     public void setControllerContext(final ControllerContext controllerContext) {
114         this.controllerContext = controllerContext;
115     }
116
117     private RestconfImpl() {
118     }
119
120     public static RestconfImpl getInstance() {
121         return INSTANCE;
122     }
123
124     @Override
125     public StructuredData getModules(final UriInfo uriInfo) {
126         final Module restconfModule = this.getRestconfModule();
127
128         final List<Node<?>> modulesAsData = new ArrayList<Node<?>>();
129         final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
130                 restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
131
132         Set<Module> allModules = this.controllerContext.getAllModules();
133         for (final Module module : allModules) {
134             CompositeNode moduleCompositeNode = this.toModuleCompositeNode(module, moduleSchemaNode);
135             modulesAsData.add(moduleCompositeNode);
136         }
137
138         final DataSchemaNode modulesSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
139                 restconfModule, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
140         QName qName = modulesSchemaNode.getQName();
141         final CompositeNode modulesNode = NodeFactory.createImmutableCompositeNode(qName, null, modulesAsData);
142         return new StructuredData(modulesNode, modulesSchemaNode, null,parsePrettyPrintParameter( uriInfo ));
143     }
144
145     @Override
146     public StructuredData getAvailableStreams(final UriInfo uriInfo) {
147         Set<String> availableStreams = Notificator.getStreamNames();
148
149         final List<Node<?>> streamsAsData = new ArrayList<Node<?>>();
150         Module restconfModule = this.getRestconfModule();
151         final DataSchemaNode streamSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
152                 restconfModule, Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
153         for (final String streamName : availableStreams) {
154             streamsAsData.add(this.toStreamCompositeNode(streamName, streamSchemaNode));
155         }
156
157         final DataSchemaNode streamsSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
158                 restconfModule, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
159         QName qName = streamsSchemaNode.getQName();
160         final CompositeNode streamsNode = NodeFactory.createImmutableCompositeNode(qName, null, streamsAsData);
161         return new StructuredData(streamsNode, streamsSchemaNode, null,parsePrettyPrintParameter( uriInfo ));
162     }
163
164     @Override
165     public StructuredData getModules(final String identifier,final UriInfo uriInfo) {
166         Set<Module> modules = null;
167         MountInstance mountPoint = null;
168         if (identifier.contains(ControllerContext.MOUNT)) {
169             InstanceIdWithSchemaNode mountPointIdentifier =
170                     this.controllerContext.toMountPointIdentifier(identifier);
171             mountPoint = mountPointIdentifier.getMountPoint();
172             modules = this.controllerContext.getAllModules(mountPoint);
173         }
174         else {
175             throw new RestconfDocumentedException(
176                     "URI has bad format. If modules behind mount point should be showed, URI has to end with " +
177                             ControllerContext.MOUNT, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
178         }
179
180         final List<Node<?>> modulesAsData = new ArrayList<Node<?>>();
181         Module restconfModule = this.getRestconfModule();
182         final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
183                 restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
184
185         for (final Module module : modules) {
186             modulesAsData.add(this.toModuleCompositeNode(module, moduleSchemaNode));
187         }
188
189         final DataSchemaNode modulesSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
190                 restconfModule, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
191         QName qName = modulesSchemaNode.getQName();
192         final CompositeNode modulesNode = NodeFactory.createImmutableCompositeNode(qName, null, modulesAsData);
193         return new StructuredData(modulesNode, modulesSchemaNode, mountPoint,parsePrettyPrintParameter( uriInfo ));
194     }
195
196     @Override
197     public StructuredData getModule(final String identifier,final UriInfo uriInfo) {
198         final QName moduleNameAndRevision = this.getModuleNameAndRevision(identifier);
199         Module module = null;
200         MountInstance mountPoint = null;
201         if (identifier.contains(ControllerContext.MOUNT)) {
202             InstanceIdWithSchemaNode mountPointIdentifier =
203                     this.controllerContext.toMountPointIdentifier(identifier);
204             mountPoint = mountPointIdentifier.getMountPoint();
205             module = this.controllerContext.findModuleByNameAndRevision(mountPoint, moduleNameAndRevision);
206         }
207         else {
208             module = this.controllerContext.findModuleByNameAndRevision(moduleNameAndRevision);
209         }
210
211         if (module == null) {
212             throw new RestconfDocumentedException(
213                     "Module with name '" + moduleNameAndRevision.getLocalName() + "' and revision '" +
214                             moduleNameAndRevision.getRevision() + "' was not found.",
215                             ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
216         }
217
218         Module restconfModule = this.getRestconfModule();
219         final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
220                 restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
221         final CompositeNode moduleNode = this.toModuleCompositeNode(module, moduleSchemaNode);
222         return new StructuredData(moduleNode, moduleSchemaNode, mountPoint,parsePrettyPrintParameter( uriInfo ));
223     }
224
225     @Override
226     public StructuredData getOperations(final UriInfo uriInfo) {
227         Set<Module> allModules = this.controllerContext.getAllModules();
228         return this.operationsFromModulesToStructuredData(allModules, null,parsePrettyPrintParameter(uriInfo));
229     }
230
231     @Override
232     public StructuredData getOperations(final String identifier,final UriInfo uriInfo) {
233         Set<Module> modules = null;
234         MountInstance mountPoint = null;
235         if (identifier.contains(ControllerContext.MOUNT)) {
236             InstanceIdWithSchemaNode mountPointIdentifier =
237                     this.controllerContext.toMountPointIdentifier(identifier);
238             mountPoint = mountPointIdentifier.getMountPoint();
239             modules = this.controllerContext.getAllModules(mountPoint);
240         }
241         else {
242             throw new RestconfDocumentedException(
243                     "URI has bad format. If operations behind mount point should be showed, URI has to end with " +
244                             ControllerContext.MOUNT, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
245         }
246
247         return this.operationsFromModulesToStructuredData(modules, mountPoint,parsePrettyPrintParameter(uriInfo));
248     }
249
250     private StructuredData operationsFromModulesToStructuredData(final Set<Module> modules,
251                                                                  final MountInstance mountPoint,boolean prettyPrint) {
252         final List<Node<?>> operationsAsData = new ArrayList<Node<?>>();
253         Module restconfModule = this.getRestconfModule();
254         final DataSchemaNode operationsSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
255                 restconfModule, Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE);
256         QName qName = operationsSchemaNode.getQName();
257         SchemaPath path = operationsSchemaNode.getPath();
258         ContainerSchemaNodeBuilder containerSchemaNodeBuilder =
259                 new ContainerSchemaNodeBuilder(Draft02.RestConfModule.NAME, 0, qName, path);
260         final ContainerSchemaNodeBuilder fakeOperationsSchemaNode = containerSchemaNodeBuilder;
261         for (final Module module : modules) {
262             Set<RpcDefinition> rpcs = module.getRpcs();
263             for (final RpcDefinition rpc : rpcs) {
264                 QName rpcQName = rpc.getQName();
265                 SimpleNode<Object> immutableSimpleNode =
266                         NodeFactory.<Object>createImmutableSimpleNode(rpcQName, null, null);
267                 operationsAsData.add(immutableSimpleNode);
268
269                 String name = module.getName();
270                 LeafSchemaNodeBuilder leafSchemaNodeBuilder = new LeafSchemaNodeBuilder(name, 0, rpcQName,
271                         SchemaPath.create(true, QName.create("dummy")));
272                 final LeafSchemaNodeBuilder fakeRpcSchemaNode = leafSchemaNodeBuilder;
273                 fakeRpcSchemaNode.setAugmenting(true);
274
275                 EmptyType instance = EmptyType.getInstance();
276                 fakeRpcSchemaNode.setType(instance);
277                 fakeOperationsSchemaNode.addChildNode(fakeRpcSchemaNode.build());
278             }
279         }
280
281         final CompositeNode operationsNode =
282                 NodeFactory.createImmutableCompositeNode(qName, null, operationsAsData);
283         ContainerSchemaNode schemaNode = fakeOperationsSchemaNode.build();
284         return new StructuredData(operationsNode, schemaNode, mountPoint,prettyPrint);
285     }
286
287     private Module getRestconfModule() {
288         Module restconfModule = controllerContext.getRestconfModule();
289         if (restconfModule == null) {
290             throw new RestconfDocumentedException(
291                     "ietf-restconf module was not found.", ErrorType.APPLICATION,
292                     ErrorTag.OPERATION_NOT_SUPPORTED );
293         }
294
295         return restconfModule;
296     }
297
298     private QName getModuleNameAndRevision(final String identifier) {
299         final int mountIndex = identifier.indexOf(ControllerContext.MOUNT);
300         String moduleNameAndRevision = "";
301         if (mountIndex >= 0) {
302             moduleNameAndRevision = identifier.substring(mountIndex + ControllerContext.MOUNT.length());
303         }
304         else {
305             moduleNameAndRevision = identifier;
306         }
307
308         Splitter splitter = Splitter.on("/").omitEmptyStrings();
309         Iterable<String> split = splitter.split(moduleNameAndRevision);
310         final List<String> pathArgs = Lists.<String>newArrayList(split);
311         if (pathArgs.size() < 2) {
312             throw new RestconfDocumentedException(
313                     "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'",
314                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
315         }
316
317         try {
318             final String moduleName = pathArgs.get( 0 );
319             String revision = pathArgs.get(1);
320             final Date moduleRevision = REVISION_FORMAT.parse(revision);
321             return QName.create(null, moduleRevision, moduleName);
322         }
323         catch (ParseException e) {
324             throw new RestconfDocumentedException(
325                     "URI has bad format. It should be \'moduleName/yyyy-MM-dd\'",
326                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
327         }
328     }
329
330     private CompositeNode toStreamCompositeNode(final String streamName, final DataSchemaNode streamSchemaNode) {
331         final List<Node<?>> streamNodeValues = new ArrayList<Node<?>>();
332         List<DataSchemaNode> instanceDataChildrenByName =
333                 this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) streamSchemaNode),
334                         "name");
335         final DataSchemaNode nameSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
336         streamNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(nameSchemaNode.getQName(), null,
337                 streamName));
338
339         instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
340                 ((DataNodeContainer) streamSchemaNode), "description");
341         final DataSchemaNode descriptionSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
342         streamNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(descriptionSchemaNode.getQName(), null,
343                 "DESCRIPTION_PLACEHOLDER"));
344
345         instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
346                 ((DataNodeContainer) streamSchemaNode), "replay-support");
347         final DataSchemaNode replaySupportSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
348         streamNodeValues.add(NodeFactory.<Boolean>createImmutableSimpleNode(replaySupportSchemaNode.getQName(), null,
349                 Boolean.valueOf(true)));
350
351         instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
352                 ((DataNodeContainer) streamSchemaNode), "replay-log-creation-time");
353         final DataSchemaNode replayLogCreationTimeSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
354         streamNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(replayLogCreationTimeSchemaNode.getQName(),
355                 null, ""));
356
357         instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
358                 ((DataNodeContainer) streamSchemaNode), "events");
359         final DataSchemaNode eventsSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
360         streamNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(eventsSchemaNode.getQName(),
361                 null, ""));
362
363         return NodeFactory.createImmutableCompositeNode(streamSchemaNode.getQName(), null, streamNodeValues);
364     }
365
366     private CompositeNode toModuleCompositeNode(final Module module, final DataSchemaNode moduleSchemaNode) {
367         final List<Node<?>> moduleNodeValues = new ArrayList<Node<?>>();
368         List<DataSchemaNode> instanceDataChildrenByName =
369                 this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) moduleSchemaNode), "name");
370         final DataSchemaNode nameSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
371         moduleNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(nameSchemaNode.getQName(),
372                 null, module.getName()));
373
374         instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
375                 ((DataNodeContainer) moduleSchemaNode), "revision");
376         final DataSchemaNode revisionSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
377         Date _revision = module.getRevision();
378         moduleNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(revisionSchemaNode.getQName(), null,
379                 REVISION_FORMAT.format(_revision)));
380
381         instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
382                 ((DataNodeContainer) moduleSchemaNode), "namespace");
383         final DataSchemaNode namespaceSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
384         moduleNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(namespaceSchemaNode.getQName(), null,
385                 module.getNamespace().toString()));
386
387         instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName(
388                 ((DataNodeContainer) moduleSchemaNode), "feature");
389         final DataSchemaNode featureSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
390         for (final FeatureDefinition feature : module.getFeatures()) {
391             moduleNodeValues.add(NodeFactory.<String>createImmutableSimpleNode(featureSchemaNode.getQName(), null,
392                     feature.getQName().getLocalName()));
393         }
394
395         return NodeFactory.createImmutableCompositeNode(moduleSchemaNode.getQName(), null, moduleNodeValues);
396     }
397
398     @Override
399     public Object getRoot() {
400         return null;
401     }
402
403     @Override
404     public StructuredData invokeRpc(final String identifier, final CompositeNode payload,final UriInfo uriInfo) {
405         final RpcExecutor rpc = this.resolveIdentifierInInvokeRpc(identifier);
406         QName rpcName = rpc.getRpcDefinition().getQName();
407         URI rpcNamespace = rpcName.getNamespace();
408         if (Objects.equal(rpcNamespace.toString(), SAL_REMOTE_NAMESPACE) &&
409                 Objects.equal(rpcName.getLocalName(), SAL_REMOTE_RPC_SUBSRCIBE)) {
410             return invokeSalRemoteRpcSubscribeRPC(payload, rpc.getRpcDefinition(),parsePrettyPrintParameter(uriInfo));
411         }
412
413         validateInput( rpc.getRpcDefinition().getInput(), payload );
414
415         return callRpc(rpc, payload,parsePrettyPrintParameter(uriInfo));
416     }
417
418     private void validateInput(final DataSchemaNode inputSchema, final CompositeNode payload) {
419         if( inputSchema != null && payload == null )
420         {
421             //expected a non null payload
422             throw new RestconfDocumentedException( "Input is required.",
423                     ErrorType.PROTOCOL,
424                     ErrorTag.MALFORMED_MESSAGE );
425         }
426         else if( inputSchema == null && payload != null )
427         {
428             //did not expect any input
429             throw new RestconfDocumentedException( "No input expected.",
430                     ErrorType.PROTOCOL,
431                     ErrorTag.MALFORMED_MESSAGE );
432         }
433         //else
434         //{
435         //TODO: Validate "mandatory" and "config" values here??? Or should those be
436         // validate in a more central location inside MD-SAL core.
437         //}
438     }
439
440     private StructuredData invokeSalRemoteRpcSubscribeRPC(final CompositeNode payload,
441                                                           final RpcDefinition rpc,final boolean prettyPrint) {
442         final CompositeNode value = this.normalizeNode(payload, rpc.getInput(), null);
443         final SimpleNode<? extends Object> pathNode = value == null ? null :
444             value.getFirstSimpleByName( QName.create(rpc.getQName(), "path") );
445         final Object pathValue = pathNode == null ? null : pathNode.getValue();
446
447         if (!(pathValue instanceof InstanceIdentifier)) {
448             throw new RestconfDocumentedException(
449                     "Instance identifier was not normalized correctly.",
450                     ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED );
451         }
452
453         final InstanceIdentifier pathIdentifier = ((InstanceIdentifier) pathValue);
454         String streamName = null;
455         if (!Iterables.isEmpty(pathIdentifier.getPathArguments())) {
456             String fullRestconfIdentifier = this.controllerContext.toFullRestconfIdentifier(pathIdentifier);
457             streamName = Notificator.createStreamNameFromUri(fullRestconfIdentifier);
458         }
459
460         if (Strings.isNullOrEmpty(streamName)) {
461             throw new RestconfDocumentedException(
462                     "Path is empty or contains data node which is not Container or List build-in type.",
463                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
464         }
465
466         final SimpleNode<String> streamNameNode = NodeFactory.<String>createImmutableSimpleNode(
467                 QName.create(rpc.getOutput().getQName(), "stream-name"), null, streamName);
468         final List<Node<?>> output = new ArrayList<Node<?>>();
469         output.add(streamNameNode);
470
471         final MutableCompositeNode responseData = NodeFactory.createMutableCompositeNode(
472                 rpc.getOutput().getQName(), null, output, null, null);
473
474         if (!Notificator.existListenerFor(pathIdentifier)) {
475             Notificator.createListener(pathIdentifier, streamName);
476         }
477
478         return new StructuredData(responseData, rpc.getOutput(), null,prettyPrint);
479     }
480
481     @Override
482     public StructuredData invokeRpc(final String identifier, final String noPayload, final UriInfo uriInfo) {
483         if (StringUtils.isNotBlank(noPayload)) {
484             throw new RestconfDocumentedException(
485                     "Content must be empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
486         }
487         return invokeRpc( identifier, (CompositeNode)null,uriInfo);
488     }
489
490     private RpcExecutor resolveIdentifierInInvokeRpc(final String identifier) {
491         String identifierEncoded = null;
492         MountInstance mountPoint = null;
493         if (identifier.contains(ControllerContext.MOUNT)) {
494             // mounted RPC call - look up mount instance.
495             InstanceIdWithSchemaNode mountPointId = controllerContext
496                     .toMountPointIdentifier(identifier);
497             mountPoint = mountPointId.getMountPoint();
498
499             int startOfRemoteRpcName = identifier.lastIndexOf(ControllerContext.MOUNT)
500                     + ControllerContext.MOUNT.length() + 1;
501             String remoteRpcName = identifier.substring(startOfRemoteRpcName);
502             identifierEncoded = remoteRpcName;
503
504         } else if (identifier.indexOf("/") != CHAR_NOT_FOUND) {
505             final String slashErrorMsg = String
506                     .format("Identifier %n%s%ncan\'t contain slash "
507                             + "character (/).%nIf slash is part of identifier name then use %%2F placeholder.",
508                             identifier);
509             throw new RestconfDocumentedException(
510                     slashErrorMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
511         } else {
512             identifierEncoded = identifier;
513         }
514
515         final String identifierDecoded = controllerContext.urlPathArgDecode(identifierEncoded);
516         RpcDefinition rpc = controllerContext.getRpcDefinition(identifierDecoded);
517
518         if (rpc == null) {
519             throw new RestconfDocumentedException(
520                     "RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT );
521         }
522
523         if (mountPoint == null) {
524             return new BrokerRpcExecutor(rpc, broker);
525         } else {
526             return new MountPointRpcExecutor(rpc, mountPoint);
527         }
528
529     }
530
531     private StructuredData callRpc(final RpcExecutor rpcExecutor, final CompositeNode payload,boolean prettyPrint) {
532         if (rpcExecutor == null) {
533             throw new RestconfDocumentedException(
534                     "RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT );
535         }
536
537         CompositeNode rpcRequest = null;
538         RpcDefinition rpc = rpcExecutor.getRpcDefinition();
539         QName rpcName = rpc.getQName();
540
541         if (payload == null) {
542             rpcRequest = NodeFactory.createMutableCompositeNode(rpcName, null, null, null, null);
543         } else {
544             final CompositeNode value = this.normalizeNode(payload, rpc.getInput(), null);
545             List<Node<?>> input = Collections.<Node<?>> singletonList(value);
546             rpcRequest = NodeFactory.createMutableCompositeNode(rpcName, null, input, null, null);
547         }
548
549         RpcResult<CompositeNode> rpcResult = rpcExecutor.invokeRpc(rpcRequest);
550
551         checkRpcSuccessAndThrowException(rpcResult);
552
553         if (rpcResult.getResult() == null) {
554             return null;
555         }
556
557         if( rpc.getOutput() == null )
558         {
559             return null; //no output, nothing to send back.
560         }
561
562         return new StructuredData(rpcResult.getResult(), rpc.getOutput(), null,prettyPrint);
563     }
564
565     private void checkRpcSuccessAndThrowException(final RpcResult<CompositeNode> rpcResult) {
566         if (rpcResult.isSuccessful() == false) {
567
568             Collection<RpcError> rpcErrors = rpcResult.getErrors();
569             if( rpcErrors == null || rpcErrors.isEmpty() ) {
570                 throw new RestconfDocumentedException(
571                         "The operation was not successful and there were no RPC errors returned",
572                         ErrorType.RPC, ErrorTag.OPERATION_FAILED );
573             }
574
575             List<RestconfError> errorList = Lists.newArrayList();
576             for( RpcError rpcError: rpcErrors ) {
577                 errorList.add( new RestconfError( rpcError ) );
578             }
579
580             throw new RestconfDocumentedException( errorList );
581         }
582     }
583
584     @Override
585     public StructuredData readConfigurationData(final String identifier, final UriInfo uriInfo) {
586         final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
587         CompositeNode data = null;
588         MountInstance mountPoint = iiWithData.getMountPoint();
589         if (mountPoint != null) {
590             data = broker.readConfigurationDataBehindMountPoint(mountPoint, iiWithData.getInstanceIdentifier());
591         }
592         else {
593             data = broker.readConfigurationData(iiWithData.getInstanceIdentifier());
594         }
595
596         data = pruneDataAtDepth( data, parseDepthParameter( uriInfo ) );
597         boolean prettyPrintMode = parsePrettyPrintParameter( uriInfo );
598         return new StructuredData(data, iiWithData.getSchemaNode(), iiWithData.getMountPoint(),prettyPrintMode);
599     }
600
601     @SuppressWarnings("unchecked")
602     private <T extends Node<?>> T pruneDataAtDepth( final T node, final Integer depth ) {
603         if( depth == null ) {
604             return node;
605         }
606
607         if( node instanceof CompositeNode ) {
608             ImmutableList.Builder<Node<?>> newChildNodes = ImmutableList.<Node<?>> builder();
609             if( depth > 1 ) {
610                 for( Node<?> childNode: ((CompositeNode)node).getValue() ) {
611                     newChildNodes.add( pruneDataAtDepth( childNode, depth - 1 ) );
612                 }
613             }
614
615             return (T) ImmutableCompositeNode.create( node.getNodeType(), newChildNodes.build() );
616         }
617         else { // SimpleNode
618             return node;
619         }
620     }
621
622     private Integer parseDepthParameter( final UriInfo info ) {
623         String param = info.getQueryParameters( false ).getFirst( UriParameters.DEPTH.toString() );
624         if( Strings.isNullOrEmpty( param ) || "unbounded".equals( param ) ) {
625             return null;
626         }
627
628         try {
629             Integer depth = Integer.valueOf( param );
630             if( depth < 1 ) {
631                 throw new RestconfDocumentedException( new RestconfError(
632                         ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, "Invalid depth parameter: " + depth,
633                         null, "The depth parameter must be an integer > 1 or \"unbounded\"" ) );
634             }
635
636             return depth;
637         }
638         catch( NumberFormatException e ) {
639             throw new RestconfDocumentedException( new RestconfError(
640                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
641                     "Invalid depth parameter: " + e.getMessage(),
642                     null, "The depth parameter must be an integer > 1 or \"unbounded\"" ) );
643         }
644     }
645
646     @Override
647     public StructuredData readOperationalData(final String identifier, final UriInfo info) {
648         final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
649         CompositeNode data = null;
650         MountInstance mountPoint = iiWithData.getMountPoint();
651         if (mountPoint != null) {
652             data = broker.readOperationalDataBehindMountPoint(mountPoint, iiWithData.getInstanceIdentifier());
653         }
654         else {
655             data = broker.readOperationalData(iiWithData.getInstanceIdentifier());
656         }
657
658         data = pruneDataAtDepth( data, parseDepthParameter( info ) );
659         boolean prettyPrintMode = parsePrettyPrintParameter( info );
660         return new StructuredData(data, iiWithData.getSchemaNode(), mountPoint,prettyPrintMode);
661     }
662
663     private boolean parsePrettyPrintParameter(UriInfo info) {
664         String param = info.getQueryParameters(false).getFirst(UriParameters.PRETTY_PRINT.toString());
665         return Boolean.parseBoolean(param);
666     }
667
668     @Override
669     public Response updateConfigurationData(final String identifier, final CompositeNode payload) {
670         final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
671
672         validateInput(iiWithData.getSchemaNode(), payload);
673
674         MountInstance mountPoint = iiWithData.getMountPoint();
675         final CompositeNode value = this.normalizeNode(payload, iiWithData.getSchemaNode(), mountPoint);
676         RpcResult<TransactionStatus> status = null;
677
678         try {
679             if (mountPoint != null) {
680                 status = broker.commitConfigurationDataPutBehindMountPoint(
681                         mountPoint, iiWithData.getInstanceIdentifier(), value).get();
682             } else {
683                 status = broker.commitConfigurationDataPut(iiWithData.getInstanceIdentifier(), value).get();
684             }
685         }
686         catch( Exception e ) {
687             throw new RestconfDocumentedException( "Error updating data", e );
688         }
689
690         if( status.getResult() == TransactionStatus.COMMITED ) {
691             return Response.status(Status.OK).build();
692         }
693
694         return Response.status(Status.INTERNAL_SERVER_ERROR).build();
695     }
696
697     @Override
698     public Response createConfigurationData(final String identifier, final CompositeNode payload) {
699         if( payload == null ) {
700             throw new RestconfDocumentedException( "Input is required.",
701                     ErrorType.PROTOCOL,
702                     ErrorTag.MALFORMED_MESSAGE );
703         }
704
705         URI payloadNS = this.namespace(payload);
706         if (payloadNS == null) {
707             throw new RestconfDocumentedException(
708                     "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)",
709                     ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE );
710         }
711
712         InstanceIdWithSchemaNode iiWithData = null;
713         CompositeNode value = null;
714         if (this.representsMountPointRootData(payload)) {
715             // payload represents mount point data and URI represents path to the mount point
716
717             if (this.endsWithMountPoint(identifier)) {
718                 throw new RestconfDocumentedException(
719                         "URI has bad format. URI should be without \"" + ControllerContext.MOUNT +
720                         "\" for POST operation.",
721                         ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
722             }
723
724             final String completeIdentifier = this.addMountPointIdentifier(identifier);
725             iiWithData = this.controllerContext.toInstanceIdentifier(completeIdentifier);
726
727             value = this.normalizeNode(payload, iiWithData.getSchemaNode(), iiWithData.getMountPoint());
728         }
729         else {
730             final InstanceIdWithSchemaNode incompleteInstIdWithData =
731                     this.controllerContext.toInstanceIdentifier(identifier);
732             final DataNodeContainer parentSchema = (DataNodeContainer) incompleteInstIdWithData.getSchemaNode();
733             MountInstance mountPoint = incompleteInstIdWithData.getMountPoint();
734             final Module module = this.findModule(mountPoint, payload);
735             if (module == null) {
736                 throw new RestconfDocumentedException(
737                         "Module was not found for \"" + payloadNS + "\"",
738                         ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
739             }
740
741             String payloadName = this.getName(payload);
742             final DataSchemaNode schemaNode = this.controllerContext.findInstanceDataChildByNameAndNamespace(
743                     parentSchema, payloadName, module.getNamespace());
744             value = this.normalizeNode(payload, schemaNode, mountPoint);
745
746             iiWithData = this.addLastIdentifierFromData(incompleteInstIdWithData, value, schemaNode);
747         }
748
749         RpcResult<TransactionStatus> status = null;
750         MountInstance mountPoint = iiWithData.getMountPoint();
751         try {
752             if (mountPoint != null) {
753                 Future<RpcResult<TransactionStatus>> future =
754                         broker.commitConfigurationDataPostBehindMountPoint(
755                                 mountPoint, iiWithData.getInstanceIdentifier(), value);
756                 status = future == null ? null : future.get();
757             }
758             else {
759                 Future<RpcResult<TransactionStatus>> future =
760                         broker.commitConfigurationDataPost(iiWithData.getInstanceIdentifier(), value);
761                 status = future == null ? null : future.get();
762             }
763         }
764         catch( Exception e ) {
765             throw new RestconfDocumentedException( "Error creating data", e );
766         }
767
768         if (status == null) {
769             return Response.status(Status.ACCEPTED).build();
770         }
771
772         if( status.getResult() == TransactionStatus.COMMITED ) {
773             return Response.status(Status.NO_CONTENT).build();
774         }
775
776         return Response.status(Status.INTERNAL_SERVER_ERROR).build();
777     }
778
779     @Override
780     public Response createConfigurationData(final CompositeNode payload) {
781         if( payload == null ) {
782             throw new RestconfDocumentedException( "Input is required.",
783                     ErrorType.PROTOCOL,
784                     ErrorTag.MALFORMED_MESSAGE );
785         }
786
787         URI payloadNS = this.namespace(payload);
788         if (payloadNS == null) {
789             throw new RestconfDocumentedException(
790                     "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)",
791                     ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE );
792         }
793
794         final Module module = this.findModule(null, payload);
795         if (module == null) {
796             throw new RestconfDocumentedException(
797                     "Data has bad format. Root element node has incorrect namespace (XML format) or module name(JSON format)",
798                     ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE );
799         }
800
801         String payloadName = this.getName(payload);
802         final DataSchemaNode schemaNode = this.controllerContext.findInstanceDataChildByNameAndNamespace(
803                 module, payloadName, module.getNamespace());
804         final CompositeNode value = this.normalizeNode(payload, schemaNode, null);
805         final InstanceIdWithSchemaNode iiWithData = this.addLastIdentifierFromData(null, value, schemaNode);
806         RpcResult<TransactionStatus> status = null;
807         MountInstance mountPoint = iiWithData.getMountPoint();
808
809         try {
810             if (mountPoint != null) {
811                 Future<RpcResult<TransactionStatus>> future =
812                         broker.commitConfigurationDataPostBehindMountPoint(
813                                 mountPoint, iiWithData.getInstanceIdentifier(), value);
814                 status = future == null ? null : future.get();
815             }
816             else {
817                 Future<RpcResult<TransactionStatus>> future =
818                         broker.commitConfigurationDataPost(iiWithData.getInstanceIdentifier(), value);
819                 status = future == null ? null : future.get();
820             }
821         }
822         catch( Exception e ) {
823             throw new RestconfDocumentedException( "Error creating data", e );
824         }
825
826         if (status == null) {
827             return Response.status(Status.ACCEPTED).build();
828         }
829
830         if( status.getResult() == TransactionStatus.COMMITED ) {
831             return Response.status(Status.NO_CONTENT).build();
832         }
833
834         return Response.status(Status.INTERNAL_SERVER_ERROR).build();
835     }
836
837     @Override
838     public Response deleteConfigurationData(final String identifier) {
839         final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
840         RpcResult<TransactionStatus> status = null;
841         MountInstance mountPoint = iiWithData.getMountPoint();
842
843         try {
844             if (mountPoint != null) {
845                 status = broker.commitConfigurationDataDeleteBehindMountPoint(
846                         mountPoint, iiWithData.getInstanceIdentifier()).get();
847             }
848             else {
849                 status = broker.commitConfigurationDataDelete(iiWithData.getInstanceIdentifier()).get();
850             }
851         }
852         catch( Exception e ) {
853             throw new RestconfDocumentedException( "Error creating data", e );
854         }
855
856         if( status.getResult() == TransactionStatus.COMMITED ) {
857             return Response.status(Status.OK).build();
858         }
859
860         return Response.status(Status.INTERNAL_SERVER_ERROR).build();
861     }
862
863     @Override
864     public Response subscribeToStream(final String identifier, final UriInfo uriInfo) {
865         final String streamName = Notificator.createStreamNameFromUri(identifier);
866         if (Strings.isNullOrEmpty(streamName)) {
867             throw new RestconfDocumentedException(
868                     "Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
869         }
870
871         final ListenerAdapter listener = Notificator.getListenerFor(streamName);
872         if (listener == null) {
873             throw new RestconfDocumentedException(
874                     "Stream was not found.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
875         }
876
877         broker.registerToListenDataChanges(listener);
878
879         final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
880         UriBuilder port = uriBuilder.port(WebSocketServer.PORT);
881         final URI uriToWebsocketServer = port.replacePath(streamName).build();
882
883         return Response.status(Status.OK).location(uriToWebsocketServer).build();
884     }
885
886     private Module findModule(final MountInstance mountPoint, final CompositeNode data) {
887         if (data instanceof CompositeNodeWrapper) {
888             return findModule(mountPoint, (CompositeNodeWrapper)data);
889         }
890         else if (data != null) {
891             URI namespace = data.getNodeType().getNamespace();
892             if (mountPoint != null) {
893                 return this.controllerContext.findModuleByNamespace(mountPoint, namespace);
894             }
895             else {
896                 return this.controllerContext.findModuleByNamespace(namespace);
897             }
898         }
899         else {
900             throw new IllegalArgumentException("Unhandled parameter types: " +
901                     Arrays.<Object>asList(mountPoint, data).toString());
902         }
903     }
904
905     private Module findModule(final MountInstance mountPoint, final CompositeNodeWrapper data) {
906         URI namespace = data.getNamespace();
907         Preconditions.<URI>checkNotNull(namespace);
908
909         Module module = null;
910         if (mountPoint != null) {
911             module = this.controllerContext.findModuleByNamespace(mountPoint, namespace);
912             if (module == null) {
913                 module = this.controllerContext.findModuleByName(mountPoint, namespace.toString());
914             }
915         }
916         else {
917             module = this.controllerContext.findModuleByNamespace(namespace);
918             if (module == null) {
919                 module = this.controllerContext.findModuleByName(namespace.toString());
920             }
921         }
922
923         return module;
924     }
925
926     private InstanceIdWithSchemaNode addLastIdentifierFromData(
927             final InstanceIdWithSchemaNode identifierWithSchemaNode,
928             final CompositeNode data, final DataSchemaNode schemaOfData) {
929         InstanceIdentifier instanceIdentifier = null;
930         if (identifierWithSchemaNode != null) {
931             instanceIdentifier = identifierWithSchemaNode.getInstanceIdentifier();
932         }
933
934         final InstanceIdentifier iiOriginal = instanceIdentifier;
935         InstanceIdentifierBuilder iiBuilder = null;
936         if (iiOriginal == null) {
937             iiBuilder = InstanceIdentifier.builder();
938         }
939         else {
940             iiBuilder = InstanceIdentifier.builder(iiOriginal);
941         }
942
943         if ((schemaOfData instanceof ListSchemaNode)) {
944             HashMap<QName,Object> keys = this.resolveKeysFromData(((ListSchemaNode) schemaOfData), data);
945             iiBuilder.nodeWithKey(schemaOfData.getQName(), keys);
946         }
947         else {
948             iiBuilder.node(schemaOfData.getQName());
949         }
950
951         InstanceIdentifier instance = iiBuilder.toInstance();
952         MountInstance mountPoint = null;
953         if (identifierWithSchemaNode != null) {
954             mountPoint=identifierWithSchemaNode.getMountPoint();
955         }
956
957         return new InstanceIdWithSchemaNode(instance, schemaOfData, mountPoint);
958     }
959
960     private HashMap<QName,Object> resolveKeysFromData(final ListSchemaNode listNode,
961             final CompositeNode dataNode) {
962         final HashMap<QName,Object> keyValues = new HashMap<QName, Object>();
963         List<QName> _keyDefinition = listNode.getKeyDefinition();
964         for (final QName key : _keyDefinition) {
965             SimpleNode<? extends Object> head = null;
966             String localName = key.getLocalName();
967             List<SimpleNode<? extends Object>> simpleNodesByName = dataNode.getSimpleNodesByName(localName);
968             if (simpleNodesByName != null) {
969                 head = Iterables.getFirst(simpleNodesByName, null);
970             }
971
972             Object dataNodeKeyValueObject = null;
973             if (head != null) {
974                 dataNodeKeyValueObject = head.getValue();
975             }
976
977             if (dataNodeKeyValueObject == null) {
978                 throw new RestconfDocumentedException(
979                         "Data contains list \"" + dataNode.getNodeType().getLocalName() +
980                         "\" which does not contain key: \"" + key.getLocalName() + "\"",
981                         ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
982             }
983
984             keyValues.put(key, dataNodeKeyValueObject);
985         }
986
987         return keyValues;
988     }
989
990     private boolean endsWithMountPoint(final String identifier) {
991         return identifier.endsWith(ControllerContext.MOUNT) ||
992                 identifier.endsWith(ControllerContext.MOUNT + "/");
993     }
994
995     private boolean representsMountPointRootData(final CompositeNode data) {
996         URI namespace = this.namespace(data);
997         return (SchemaContext.NAME.getNamespace().equals( namespace ) /* ||
998                 MOUNT_POINT_MODULE_NAME.equals( namespace.toString() )*/ ) &&
999                 SchemaContext.NAME.getLocalName().equals( this.localName(data) );
1000     }
1001
1002     private String addMountPointIdentifier(final String identifier) {
1003         boolean endsWith = identifier.endsWith("/");
1004         if (endsWith) {
1005             return (identifier + ControllerContext.MOUNT);
1006         }
1007
1008         return identifier + "/" + ControllerContext.MOUNT;
1009     }
1010
1011     private CompositeNode normalizeNode(final CompositeNode node, final DataSchemaNode schema,
1012             final MountInstance mountPoint) {
1013         if (schema == null) {
1014             QName nodeType = node == null ? null : node.getNodeType();
1015             String localName = nodeType == null ? null : nodeType.getLocalName();
1016
1017             throw new RestconfDocumentedException(
1018                     "Data schema node was not found for " + localName,
1019                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1020         }
1021
1022         if (!(schema instanceof DataNodeContainer)) {
1023             throw new RestconfDocumentedException(
1024                     "Root element has to be container or list yang datatype.",
1025                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1026         }
1027
1028         if ((node instanceof CompositeNodeWrapper)) {
1029             boolean isChangeAllowed = ((CompositeNodeWrapper) node).isChangeAllowed();
1030             if (isChangeAllowed) {
1031                 try {
1032                     this.normalizeNode(((CompositeNodeWrapper) node), schema, null, mountPoint);
1033                 }
1034                 catch (IllegalArgumentException e) {
1035                     throw new RestconfDocumentedException(
1036                             e.getMessage(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1037                 }
1038             }
1039
1040             return ((CompositeNodeWrapper) node).unwrap();
1041         }
1042
1043         return node;
1044     }
1045
1046     private void normalizeNode(final NodeWrapper<? extends Object> nodeBuilder,
1047             final DataSchemaNode schema, final QName previousAugment,
1048             final MountInstance mountPoint) {
1049         if (schema == null) {
1050             throw new RestconfDocumentedException(
1051                     "Data has bad format.\n\"" + nodeBuilder.getLocalName() +
1052                     "\" does not exist in yang schema.",
1053                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1054         }
1055
1056         QName currentAugment = null;
1057         if (nodeBuilder.getQname() != null) {
1058             currentAugment = previousAugment;
1059         }
1060         else {
1061             currentAugment = this.normalizeNodeName(nodeBuilder, schema, previousAugment, mountPoint);
1062             if (nodeBuilder.getQname() == null) {
1063                 throw new RestconfDocumentedException(
1064                         "Data has bad format.\nIf data is in XML format then namespace for \"" +
1065                                 nodeBuilder.getLocalName() +
1066                                 "\" should be \"" + schema.getQName().getNamespace() + "\".\n" +
1067                                 "If data is in JSON format then module name for \"" + nodeBuilder.getLocalName() +
1068                                 "\" should be corresponding to namespace \"" +
1069                                 schema.getQName().getNamespace() + "\".",
1070                                 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1071             }
1072         }
1073
1074         if ( nodeBuilder instanceof CompositeNodeWrapper ) {
1075             if( schema instanceof DataNodeContainer ) {
1076                 normalizeCompositeNode( (CompositeNodeWrapper)nodeBuilder, (DataNodeContainer)schema,
1077                         mountPoint, currentAugment );
1078             }
1079             else if( schema instanceof AnyXmlSchemaNode ) {
1080                 normalizeAnyXmlNode( (CompositeNodeWrapper)nodeBuilder, (AnyXmlSchemaNode)schema );
1081             }
1082         }
1083         else if ( nodeBuilder instanceof SimpleNodeWrapper ) {
1084             normalizeSimpleNode( (SimpleNodeWrapper) nodeBuilder, schema, mountPoint );
1085         }
1086         else if ((nodeBuilder instanceof EmptyNodeWrapper)) {
1087             normalizeEmptyNode( (EmptyNodeWrapper) nodeBuilder, schema );
1088         }
1089     }
1090
1091     private void normalizeAnyXmlNode( final CompositeNodeWrapper compositeNode, final AnyXmlSchemaNode schema ) {
1092         List<NodeWrapper<?>> children = compositeNode.getValues();
1093         for( NodeWrapper<? extends Object> child : children ) {
1094             child.setNamespace( schema.getQName().getNamespace() );
1095             if( child instanceof CompositeNodeWrapper ) {
1096                 normalizeAnyXmlNode( (CompositeNodeWrapper)child, schema );
1097             }
1098         }
1099     }
1100
1101     private void normalizeEmptyNode( final EmptyNodeWrapper emptyNodeBuilder, final DataSchemaNode schema ) {
1102         if ((schema instanceof LeafSchemaNode)) {
1103             emptyNodeBuilder.setComposite(false);
1104         }
1105         else {
1106             if ((schema instanceof ContainerSchemaNode)) {
1107                 // FIXME: Add presence check
1108                 emptyNodeBuilder.setComposite(true);
1109             }
1110         }
1111     }
1112
1113     private void normalizeSimpleNode( final SimpleNodeWrapper simpleNode, final DataSchemaNode schema,
1114             final MountInstance mountPoint ) {
1115         final Object value = simpleNode.getValue();
1116         Object inputValue = value;
1117         TypeDefinition<? extends Object> typeDefinition = this.typeDefinition(schema);
1118         if ((typeDefinition instanceof IdentityrefTypeDefinition)) {
1119             if ((value instanceof String)) {
1120                 inputValue = new IdentityValuesDTO( simpleNode.getNamespace().toString(),
1121                         (String) value, null, (String) value );
1122             } // else value is already instance of IdentityValuesDTO
1123         }
1124
1125         Object outputValue = inputValue;
1126
1127         if( typeDefinition != null ) {
1128             Codec<Object,Object> codec = RestCodec.from(typeDefinition, mountPoint);
1129             outputValue = codec == null ? null : codec.deserialize(inputValue);
1130         }
1131
1132         simpleNode.setValue(outputValue);
1133     }
1134
1135     private void normalizeCompositeNode( final CompositeNodeWrapper compositeNodeBuilder,
1136             final DataNodeContainer schema, final MountInstance mountPoint,
1137             final QName currentAugment ) {
1138         final List<NodeWrapper<?>> children = compositeNodeBuilder.getValues();
1139         for (final NodeWrapper<? extends Object> child : children) {
1140             final List<DataSchemaNode> potentialSchemaNodes =
1141                     this.controllerContext.findInstanceDataChildrenByName(
1142                             schema, child.getLocalName());
1143
1144             if (potentialSchemaNodes.size() > 1 && child.getNamespace() == null) {
1145                 StringBuilder builder = new StringBuilder();
1146                 for (final DataSchemaNode potentialSchemaNode : potentialSchemaNodes) {
1147                     builder.append("   ").append(potentialSchemaNode.getQName().getNamespace().toString())
1148                     .append("\n");
1149                 }
1150
1151                 throw new RestconfDocumentedException(
1152                         "Node \"" + child.getLocalName() +
1153                         "\" is added as augment from more than one module. " +
1154                         "Therefore node must have namespace (XML format) or module name (JSON format)." +
1155                         "\nThe node is added as augment from modules with namespaces:\n" + builder,
1156                         ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1157             }
1158
1159             boolean rightNodeSchemaFound = false;
1160             for (final DataSchemaNode potentialSchemaNode : potentialSchemaNodes) {
1161                 if (!rightNodeSchemaFound) {
1162                     final QName potentialCurrentAugment =
1163                             this.normalizeNodeName(child, potentialSchemaNode, currentAugment, mountPoint);
1164                     if (child.getQname() != null ) {
1165                         this.normalizeNode(child, potentialSchemaNode, potentialCurrentAugment, mountPoint);
1166                         rightNodeSchemaFound = true;
1167                     }
1168                 }
1169             }
1170
1171             if (!rightNodeSchemaFound) {
1172                 throw new RestconfDocumentedException(
1173                         "Schema node \"" + child.getLocalName() + "\" was not found in module.",
1174                         ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT );
1175             }
1176         }
1177
1178         if ((schema instanceof ListSchemaNode)) {
1179             ListSchemaNode listSchemaNode = (ListSchemaNode)schema;
1180             final List<QName> listKeys = listSchemaNode.getKeyDefinition();
1181             for (final QName listKey : listKeys) {
1182                 boolean foundKey = false;
1183                 for (final NodeWrapper<? extends Object> child : children) {
1184                     if (Objects.equal(child.unwrap().getNodeType().getLocalName(), listKey.getLocalName())) {
1185                         foundKey = true;
1186                     }
1187                 }
1188
1189                 if (!foundKey) {
1190                     throw new RestconfDocumentedException(
1191                             "Missing key in URI \"" + listKey.getLocalName() +
1192                             "\" of list \"" + listSchemaNode.getQName().getLocalName() + "\"",
1193                             ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
1194                 }
1195             }
1196         }
1197     }
1198
1199     private QName normalizeNodeName(final NodeWrapper<? extends Object> nodeBuilder,
1200             final DataSchemaNode schema, final QName previousAugment,
1201             final MountInstance mountPoint) {
1202         QName validQName = schema.getQName();
1203         QName currentAugment = previousAugment;
1204         if (schema.isAugmenting()) {
1205             currentAugment = schema.getQName();
1206         }
1207         else if (previousAugment != null &&
1208                 !Objects.equal( schema.getQName().getNamespace(), previousAugment.getNamespace())) {
1209             validQName = QName.create(currentAugment, schema.getQName().getLocalName());
1210         }
1211
1212         String moduleName = null;
1213         if (mountPoint == null) {
1214             moduleName = controllerContext.findModuleNameByNamespace(validQName.getNamespace());
1215         }
1216         else {
1217             moduleName = controllerContext.findModuleNameByNamespace(mountPoint, validQName.getNamespace());
1218         }
1219
1220         if (nodeBuilder.getNamespace() == null ||
1221                 Objects.equal(nodeBuilder.getNamespace(), validQName.getNamespace()) ||
1222                 Objects.equal(nodeBuilder.getNamespace().toString(), moduleName) /*||
1223             Note: this check is wrong - can never be true as it compares a URI with a String
1224                   not sure what the intention is so commented out...
1225             Objects.equal(nodeBuilder.getNamespace(), MOUNT_POINT_MODULE_NAME)*/ ) {
1226
1227             nodeBuilder.setQname(validQName);
1228         }
1229
1230         return currentAugment;
1231     }
1232
1233     private URI namespace(final CompositeNode data) {
1234         if (data instanceof CompositeNodeWrapper) {
1235             return ((CompositeNodeWrapper)data).getNamespace();
1236         }
1237         else if (data != null) {
1238             return data.getNodeType().getNamespace();
1239         }
1240         else {
1241             throw new IllegalArgumentException("Unhandled parameter types: " +
1242                     Arrays.<Object>asList(data).toString());
1243         }
1244     }
1245
1246     private String localName(final CompositeNode data) {
1247         if (data instanceof CompositeNodeWrapper) {
1248             return ((CompositeNodeWrapper)data).getLocalName();
1249         }
1250         else if (data != null) {
1251             return data.getNodeType().getLocalName();
1252         }
1253         else {
1254             throw new IllegalArgumentException("Unhandled parameter types: " +
1255                     Arrays.<Object>asList(data).toString());
1256         }
1257     }
1258
1259     private String getName(final CompositeNode data) {
1260         if (data instanceof CompositeNodeWrapper) {
1261             return ((CompositeNodeWrapper)data).getLocalName();
1262         }
1263         else if (data != null) {
1264             return data.getNodeType().getLocalName();
1265         }
1266         else {
1267             throw new IllegalArgumentException("Unhandled parameter types: " +
1268                     Arrays.<Object>asList(data).toString());
1269         }
1270     }
1271
1272     private TypeDefinition<? extends Object> _typeDefinition(final LeafSchemaNode node) {
1273         TypeDefinition<?> baseType = node.getType();
1274         while (baseType.getBaseType() != null) {
1275             baseType = baseType.getBaseType();
1276         }
1277
1278         return baseType;
1279     }
1280
1281     private TypeDefinition<? extends Object> typeDefinition(final LeafListSchemaNode node) {
1282         TypeDefinition<?> baseType = node.getType();
1283         while (baseType.getBaseType() != null) {
1284             baseType = baseType.getBaseType();
1285         }
1286
1287         return baseType;
1288     }
1289
1290     private TypeDefinition<? extends Object> typeDefinition(final DataSchemaNode node) {
1291         if (node instanceof LeafListSchemaNode) {
1292             return typeDefinition((LeafListSchemaNode)node);
1293         }
1294         else if (node instanceof LeafSchemaNode) {
1295             return _typeDefinition((LeafSchemaNode)node);
1296         }
1297         else if (node instanceof AnyXmlSchemaNode) {
1298             return null;
1299         }
1300         else {
1301             throw new IllegalArgumentException("Unhandled parameter types: " +
1302                     Arrays.<Object>asList(node).toString());
1303         }
1304     }
1305 }