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