Use Optional.isEmpty()
[netconf.git] / restconf / restconf-nb-bierman02 / src / main / java / org / opendaylight / netconf / sal / restconf / impl / RestconfImpl.java
1 /*
2  * Copyright (c) 2014 - 2016 Brocade Communication Systems, Inc., Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netconf.sal.restconf.impl;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkNotNull;
12 import static com.google.common.base.Preconditions.checkState;
13 import static java.util.Objects.requireNonNull;
14
15 import com.google.common.annotations.VisibleForTesting;
16 import com.google.common.base.Predicates;
17 import com.google.common.base.Splitter;
18 import com.google.common.base.Strings;
19 import com.google.common.base.Throwables;
20 import com.google.common.collect.ImmutableSet;
21 import com.google.common.collect.Iterables;
22 import com.google.common.util.concurrent.FluentFuture;
23 import com.google.common.util.concurrent.Futures;
24 import com.google.common.util.concurrent.ListenableFuture;
25 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
26 import java.net.URI;
27 import java.time.Instant;
28 import java.time.format.DateTimeFormatter;
29 import java.time.format.DateTimeFormatterBuilder;
30 import java.time.format.DateTimeParseException;
31 import java.time.temporal.ChronoField;
32 import java.time.temporal.TemporalAccessor;
33 import java.util.AbstractMap.SimpleImmutableEntry;
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.HashMap;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.Locale;
41 import java.util.Map;
42 import java.util.Map.Entry;
43 import java.util.Objects;
44 import java.util.Optional;
45 import java.util.Set;
46 import java.util.concurrent.CancellationException;
47 import java.util.concurrent.ExecutionException;
48 import javax.inject.Inject;
49 import javax.inject.Singleton;
50 import javax.ws.rs.WebApplicationException;
51 import javax.ws.rs.core.Context;
52 import javax.ws.rs.core.Response;
53 import javax.ws.rs.core.Response.ResponseBuilder;
54 import javax.ws.rs.core.Response.Status;
55 import javax.ws.rs.core.UriBuilder;
56 import javax.ws.rs.core.UriInfo;
57 import org.eclipse.jdt.annotation.NonNull;
58 import org.opendaylight.mdsal.common.api.CommitInfo;
59 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
60 import org.opendaylight.mdsal.common.api.OptimisticLockFailedException;
61 import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
62 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
63 import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException;
64 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
65 import org.opendaylight.mdsal.dom.api.DOMRpcService;
66 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
67 import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
68 import org.opendaylight.netconf.sal.rest.api.Draft02;
69 import org.opendaylight.netconf.sal.rest.api.RestconfService;
70 import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
71 import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
72 import org.opendaylight.netconf.sal.streams.listeners.Notificator;
73 import org.opendaylight.netconf.sal.streams.websockets.WebSocketServer;
74 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
75 import org.opendaylight.restconf.common.context.NormalizedNodeContext;
76 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
77 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
78 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
79 import org.opendaylight.restconf.common.patch.PatchContext;
80 import org.opendaylight.restconf.common.patch.PatchStatusContext;
81 import org.opendaylight.restconf.common.util.DataChangeScope;
82 import org.opendaylight.restconf.common.util.OperationsResourceUtils;
83 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime;
84 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
85 import org.opendaylight.yangtools.yang.common.Empty;
86 import org.opendaylight.yangtools.yang.common.QName;
87 import org.opendaylight.yangtools.yang.common.QNameModule;
88 import org.opendaylight.yangtools.yang.common.Revision;
89 import org.opendaylight.yangtools.yang.common.YangConstants;
90 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
91 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
92 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
93 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
94 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
95 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
96 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
97 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
98 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
99 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
100 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
101 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
102 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
103 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
104 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModifiedNodeDoesNotExistException;
105 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
106 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
107 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
108 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
109 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder;
110 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeBuilder;
111 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
112 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafNodeBuilder;
113 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
114 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
115 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
116 import org.opendaylight.yangtools.yang.model.api.FeatureDefinition;
117 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
118 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
119 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
120 import org.opendaylight.yangtools.yang.model.api.Module;
121 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
122 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
123 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
124 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
125 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
126 import org.slf4j.Logger;
127 import org.slf4j.LoggerFactory;
128
129 @Singleton
130 public final class RestconfImpl implements RestconfService {
131     /**
132      * Notifications are served on port 8181.
133      */
134     private static final int NOTIFICATION_PORT = 8181;
135
136     private static final int CHAR_NOT_FOUND = -1;
137
138     private static final String SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote";
139
140     private static final Logger LOG = LoggerFactory.getLogger(RestconfImpl.class);
141
142     private static final DataChangeScope DEFAULT_SCOPE = DataChangeScope.BASE;
143
144     private static final LogicalDatastoreType DEFAULT_DATASTORE = LogicalDatastoreType.CONFIGURATION;
145
146     private static final URI NAMESPACE_EVENT_SUBSCRIPTION_AUGMENT = URI.create("urn:sal:restconf:event:subscription");
147
148     private static final String DATASTORE_PARAM_NAME = "datastore";
149
150     private static final String SCOPE_PARAM_NAME = "scope";
151
152     private static final String OUTPUT_TYPE_PARAM_NAME = "notification-output-type";
153
154     private static final String NETCONF_BASE = "urn:ietf:params:xml:ns:netconf:base:1.0";
155
156     private static final String NETCONF_BASE_PAYLOAD_NAME = "data";
157
158     private static final QName NETCONF_BASE_QNAME = QName.create(QNameModule.create(URI.create(NETCONF_BASE)),
159         NETCONF_BASE_PAYLOAD_NAME).intern();
160
161     private static final QNameModule SAL_REMOTE_AUGMENT = QNameModule.create(NAMESPACE_EVENT_SUBSCRIPTION_AUGMENT,
162         Revision.of("2014-07-08"));
163
164     private static final AugmentationIdentifier SAL_REMOTE_AUG_IDENTIFIER =
165             new AugmentationIdentifier(ImmutableSet.of(
166                 QName.create(SAL_REMOTE_AUGMENT, "scope"), QName.create(SAL_REMOTE_AUGMENT, "datastore"),
167                 QName.create(SAL_REMOTE_AUGMENT, "notification-output-type")));
168
169     public static final CharSequence DATA_SUBSCR = "data-change-event-subscription";
170     private static final CharSequence CREATE_DATA_SUBSCR = "create-" + DATA_SUBSCR;
171
172     public static final CharSequence NOTIFICATION_STREAM = "notification-stream";
173     private static final CharSequence CREATE_NOTIFICATION_STREAM = "create-" + NOTIFICATION_STREAM;
174
175     private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
176             .appendValue(ChronoField.YEAR, 4).appendLiteral('-')
177             .appendValue(ChronoField.MONTH_OF_YEAR, 2).appendLiteral('-')
178             .appendValue(ChronoField.DAY_OF_MONTH, 2).appendLiteral('T')
179             .appendValue(ChronoField.HOUR_OF_DAY, 2).appendLiteral(':')
180             .appendValue(ChronoField.MINUTE_OF_HOUR, 2).appendLiteral(':')
181             .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
182             .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
183             .appendOffset("+HH:MM", "Z").toFormatter();
184
185     private final BrokerFacade broker;
186
187     private final ControllerContext controllerContext;
188
189     @Inject
190     public RestconfImpl(final BrokerFacade broker, final ControllerContext controllerContext) {
191         this.broker = broker;
192         this.controllerContext = controllerContext;
193     }
194
195     /**
196      * Factory method.
197      *
198      * @deprecated Just use {@link #RestconfImpl(BrokerFacade, ControllerContext)} constructor instead.
199      */
200     @Deprecated
201     public static RestconfImpl newInstance(final BrokerFacade broker, final ControllerContext controllerContext) {
202         return new RestconfImpl(broker, controllerContext);
203     }
204
205     @Override
206     @Deprecated
207     public NormalizedNodeContext getModules(final UriInfo uriInfo) {
208         final MapNode allModuleMap = makeModuleMapNode(controllerContext.getAllModules());
209
210         final EffectiveModelContext schemaContext = this.controllerContext.getGlobalSchema();
211
212         final Module restconfModule = getRestconfModule();
213         final DataSchemaNode modulesSchemaNode = this.controllerContext.getRestconfModuleRestConfSchemaNode(
214                 restconfModule, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
215         checkState(modulesSchemaNode instanceof ContainerSchemaNode);
216
217         final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> moduleContainerBuilder =
218                 Builders.containerBuilder((ContainerSchemaNode) modulesSchemaNode);
219         moduleContainerBuilder.withChild(allModuleMap);
220
221         return new NormalizedNodeContext(new InstanceIdentifierContext<>(null, modulesSchemaNode, null, schemaContext),
222                 moduleContainerBuilder.build(), QueryParametersParser.parseWriterParameters(uriInfo));
223     }
224
225     /**
226      * Valid only for mount point.
227      */
228     @Override
229     @Deprecated
230     public NormalizedNodeContext getModules(final String identifier, final UriInfo uriInfo) {
231         if (!identifier.contains(ControllerContext.MOUNT)) {
232             final String errMsg = "URI has bad format. If modules behind mount point should be showed,"
233                     + " URI has to end with " + ControllerContext.MOUNT;
234             LOG.debug("{} for {}", errMsg, identifier);
235             throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
236         }
237
238         final InstanceIdentifierContext<?> mountPointIdentifier =
239                 this.controllerContext.toMountPointIdentifier(identifier);
240         final DOMMountPoint mountPoint = mountPointIdentifier.getMountPoint();
241         final MapNode mountPointModulesMap = makeModuleMapNode(controllerContext.getAllModules(mountPoint));
242
243         final Module restconfModule = getRestconfModule();
244         final DataSchemaNode modulesSchemaNode = this.controllerContext.getRestconfModuleRestConfSchemaNode(
245                 restconfModule, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
246         checkState(modulesSchemaNode instanceof ContainerSchemaNode);
247
248         final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> moduleContainerBuilder =
249                 Builders.containerBuilder((ContainerSchemaNode) modulesSchemaNode);
250         moduleContainerBuilder.withChild(mountPointModulesMap);
251
252         return new NormalizedNodeContext(
253                 new InstanceIdentifierContext<>(null, modulesSchemaNode, mountPoint,
254                         this.controllerContext.getGlobalSchema()),
255                 moduleContainerBuilder.build(), QueryParametersParser.parseWriterParameters(uriInfo));
256     }
257
258     @Override
259     @Deprecated
260     public NormalizedNodeContext getModule(final String identifier, final UriInfo uriInfo) {
261         final Entry<String, Revision> nameRev = getModuleNameAndRevision(requireNonNull(identifier));
262         Module module = null;
263         DOMMountPoint mountPoint = null;
264         final EffectiveModelContext schemaContext;
265         if (identifier.contains(ControllerContext.MOUNT)) {
266             final InstanceIdentifierContext<?> mountPointIdentifier =
267                     this.controllerContext.toMountPointIdentifier(identifier);
268             mountPoint = mountPointIdentifier.getMountPoint();
269             module = this.controllerContext.findModuleByNameAndRevision(mountPoint, nameRev.getKey(),
270                 nameRev.getValue());
271             schemaContext = modelContext(mountPoint);
272         } else {
273             module = this.controllerContext.findModuleByNameAndRevision(nameRev.getKey(), nameRev.getValue());
274             schemaContext = this.controllerContext.getGlobalSchema();
275         }
276
277         if (module == null) {
278             LOG.debug("Module with name '{}' and revision '{}' was not found.", nameRev.getKey(), nameRev.getValue());
279             throw new RestconfDocumentedException("Module with name '" + nameRev.getKey() + "' and revision '"
280                     + nameRev.getValue() + "' was not found.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
281         }
282
283         final Module restconfModule = getRestconfModule();
284         final Set<Module> modules = Collections.singleton(module);
285         final MapNode moduleMap = makeModuleMapNode(modules);
286
287         final DataSchemaNode moduleSchemaNode = this.controllerContext
288                 .getRestconfModuleRestConfSchemaNode(restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
289         checkState(moduleSchemaNode instanceof ListSchemaNode);
290
291         return new NormalizedNodeContext(
292                 new InstanceIdentifierContext<>(null, moduleSchemaNode, mountPoint, schemaContext), moduleMap,
293                 QueryParametersParser.parseWriterParameters(uriInfo));
294     }
295
296     @Override
297     @Deprecated
298     public NormalizedNodeContext getAvailableStreams(final UriInfo uriInfo) {
299         final EffectiveModelContext schemaContext = this.controllerContext.getGlobalSchema();
300         final Set<String> availableStreams = Notificator.getStreamNames();
301         final Module restconfModule = getRestconfModule();
302         final DataSchemaNode streamSchemaNode = this.controllerContext
303                 .getRestconfModuleRestConfSchemaNode(restconfModule, Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
304         checkState(streamSchemaNode instanceof ListSchemaNode);
305
306         final CollectionNodeBuilder<MapEntryNode, MapNode> listStreamsBuilder =
307                 Builders.mapBuilder((ListSchemaNode) streamSchemaNode);
308
309         for (final String streamName : availableStreams) {
310             listStreamsBuilder.withChild(toStreamEntryNode(streamName, streamSchemaNode));
311         }
312
313         final DataSchemaNode streamsContainerSchemaNode = this.controllerContext.getRestconfModuleRestConfSchemaNode(
314                 restconfModule, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
315         checkState(streamsContainerSchemaNode instanceof ContainerSchemaNode);
316
317         final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> streamsContainerBuilder =
318                 Builders.containerBuilder((ContainerSchemaNode) streamsContainerSchemaNode);
319         streamsContainerBuilder.withChild(listStreamsBuilder.build());
320
321         return new NormalizedNodeContext(
322                 new InstanceIdentifierContext<>(null, streamsContainerSchemaNode, null, schemaContext),
323                 streamsContainerBuilder.build(), QueryParametersParser.parseWriterParameters(uriInfo));
324     }
325
326     @Override
327     @Deprecated
328     public NormalizedNodeContext getOperations(final UriInfo uriInfo) {
329         return OperationsResourceUtils.contextForModelContext(controllerContext.getGlobalSchema(), null);
330     }
331
332     @Override
333     @Deprecated
334     public NormalizedNodeContext getOperations(final String identifier, final UriInfo uriInfo) {
335         if (!identifier.contains(ControllerContext.MOUNT)) {
336             final String errMsg = "URI has bad format. If operations behind mount point should be showed, URI has to "
337                     + " end with " +  ControllerContext.MOUNT;
338             LOG.debug("{} for {}", errMsg, identifier);
339             throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
340         }
341
342         final InstanceIdentifierContext<?> mountPointIdentifier =
343                 this.controllerContext.toMountPointIdentifier(identifier);
344         final DOMMountPoint mountPoint = mountPointIdentifier.getMountPoint();
345         return OperationsResourceUtils.contextForModelContext(modelContext(mountPoint), mountPoint);
346     }
347
348     private Module getRestconfModule() {
349         final Module restconfModule = this.controllerContext.getRestconfModule();
350         if (restconfModule == null) {
351             LOG.debug("ietf-restconf module was not found.");
352             throw new RestconfDocumentedException("ietf-restconf module was not found.", ErrorType.APPLICATION,
353                     ErrorTag.OPERATION_NOT_SUPPORTED);
354         }
355
356         return restconfModule;
357     }
358
359     private static Entry<String, Revision> getModuleNameAndRevision(final String identifier) {
360         final int mountIndex = identifier.indexOf(ControllerContext.MOUNT);
361         String moduleNameAndRevision = "";
362         if (mountIndex >= 0) {
363             moduleNameAndRevision = identifier.substring(mountIndex + ControllerContext.MOUNT.length());
364         } else {
365             moduleNameAndRevision = identifier;
366         }
367
368         final Splitter splitter = Splitter.on('/').omitEmptyStrings();
369         final List<String> pathArgs = splitter.splitToList(moduleNameAndRevision);
370         if (pathArgs.size() < 2) {
371             LOG.debug("URI has bad format. It should be \'moduleName/yyyy-MM-dd\' {}", identifier);
372             throw new RestconfDocumentedException(
373                     "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'", ErrorType.PROTOCOL,
374                     ErrorTag.INVALID_VALUE);
375         }
376
377         try {
378             return new SimpleImmutableEntry<>(pathArgs.get(0), Revision.of(pathArgs.get(1)));
379         } catch (final DateTimeParseException e) {
380             LOG.debug("URI has bad format. It should be \'moduleName/yyyy-MM-dd\' {}", identifier);
381             throw new RestconfDocumentedException("URI has bad format. It should be \'moduleName/yyyy-MM-dd\'",
382                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
383         }
384     }
385
386     @Override
387     public Object getRoot() {
388         return null;
389     }
390
391     @Override
392     public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload,
393             final UriInfo uriInfo) {
394         if (payload == null) {
395             // no payload specified, reroute this to no payload invokeRpc implementation
396             return invokeRpc(identifier, uriInfo);
397         }
398
399         final SchemaNode schema = payload.getInstanceIdentifierContext().getSchemaNode();
400         final ListenableFuture<? extends DOMRpcResult> response;
401         final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
402         final NormalizedNode<?, ?> input =  nonnullInput(schema, payload.getData());
403         final EffectiveModelContext schemaContext;
404
405         if (mountPoint != null) {
406             final Optional<DOMRpcService> mountRpcServices = mountPoint.getService(DOMRpcService.class);
407             if (mountRpcServices.isEmpty()) {
408                 LOG.debug("Error: Rpc service is missing.");
409                 throw new RestconfDocumentedException("Rpc service is missing.");
410             }
411             schemaContext = modelContext(mountPoint);
412             response = mountRpcServices.get().invokeRpc(schema.getQName(), input);
413         } else {
414             final URI namespace = schema.getQName().getNamespace();
415             if (namespace.toString().equals(SAL_REMOTE_NAMESPACE)) {
416                 if (identifier.contains(CREATE_DATA_SUBSCR)) {
417                     response = invokeSalRemoteRpcSubscribeRPC(payload);
418                 } else if (identifier.contains(CREATE_NOTIFICATION_STREAM)) {
419                     response = invokeSalRemoteRpcNotifiStrRPC(payload);
420                 } else {
421                     final String msg = "Not supported operation";
422                     LOG.warn(msg);
423                     throw new RestconfDocumentedException(msg, ErrorType.RPC, ErrorTag.OPERATION_NOT_SUPPORTED);
424                 }
425             } else {
426                 response = this.broker.invokeRpc(schema.getQName(), input);
427             }
428             schemaContext = this.controllerContext.getGlobalSchema();
429         }
430
431         final DOMRpcResult result = checkRpcResponse(response);
432
433         RpcDefinition resultNodeSchema = null;
434         final NormalizedNode<?, ?> resultData;
435         if (result != null && result.getResult() != null) {
436             resultData = result.getResult();
437             resultNodeSchema = (RpcDefinition) payload.getInstanceIdentifierContext().getSchemaNode();
438         } else {
439             resultData = null;
440         }
441
442         if (resultData != null && ((ContainerNode) resultData).getValue().isEmpty()) {
443             throw new WebApplicationException(Response.Status.NO_CONTENT);
444         } else {
445             return new NormalizedNodeContext(
446                     new InstanceIdentifierContext<>(null, resultNodeSchema, mountPoint, schemaContext),
447                     resultData, QueryParametersParser.parseWriterParameters(uriInfo));
448         }
449     }
450
451     @SuppressFBWarnings(value = "NP_LOAD_OF_KNOWN_NULL_VALUE",
452             justification = "Looks like a false positive, see below FIXME")
453     private NormalizedNodeContext invokeRpc(final String identifier, final UriInfo uriInfo) {
454         final DOMMountPoint mountPoint;
455         final String identifierEncoded;
456         final EffectiveModelContext schemaContext;
457         if (identifier.contains(ControllerContext.MOUNT)) {
458             // mounted RPC call - look up mount instance.
459             final InstanceIdentifierContext<?> mountPointId = this.controllerContext.toMountPointIdentifier(identifier);
460             mountPoint = mountPointId.getMountPoint();
461             schemaContext = modelContext(mountPoint);
462             final int startOfRemoteRpcName =
463                     identifier.lastIndexOf(ControllerContext.MOUNT) + ControllerContext.MOUNT.length() + 1;
464             final String remoteRpcName = identifier.substring(startOfRemoteRpcName);
465             identifierEncoded = remoteRpcName;
466
467         } else if (identifier.indexOf('/') == CHAR_NOT_FOUND) {
468             identifierEncoded = identifier;
469             mountPoint = null;
470             schemaContext = this.controllerContext.getGlobalSchema();
471         } else {
472             LOG.debug("Identifier {} cannot contain slash character (/).", identifier);
473             throw new RestconfDocumentedException(String.format("Identifier %n%s%ncan\'t contain slash character (/).%n"
474                     + "If slash is part of identifier name then use %%2F placeholder.", identifier), ErrorType.PROTOCOL,
475                 ErrorTag.INVALID_VALUE);
476         }
477
478         final String identifierDecoded = ControllerContext.urlPathArgDecode(identifierEncoded);
479         final RpcDefinition rpc;
480         if (mountPoint == null) {
481             rpc = this.controllerContext.getRpcDefinition(identifierDecoded);
482         } else {
483             rpc = findRpc(modelContext(mountPoint), identifierDecoded);
484         }
485
486         if (rpc == null) {
487             LOG.debug("RPC {} does not exist.", identifierDecoded);
488             throw new RestconfDocumentedException("RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT);
489         }
490
491         if (!rpc.getInput().getChildNodes().isEmpty()) {
492             LOG.debug("No input specified for RPC {} with an input section", rpc);
493             throw new RestconfDocumentedException("No input specified for RPC " + rpc
494                     + " with an input section defined", ErrorType.RPC, ErrorTag.MISSING_ELEMENT);
495         }
496
497         final ContainerNode input = defaultInput(rpc.getQName());
498         final ListenableFuture<? extends DOMRpcResult> response;
499         if (mountPoint != null) {
500             final Optional<DOMRpcService> mountRpcServices = mountPoint.getService(DOMRpcService.class);
501             if (mountRpcServices.isEmpty()) {
502                 throw new RestconfDocumentedException("Rpc service is missing.");
503             }
504             response = mountRpcServices.get().invokeRpc(rpc.getQName(), input);
505         } else {
506             response = this.broker.invokeRpc(rpc.getQName(), input);
507         }
508
509         final NormalizedNode<?, ?> result = checkRpcResponse(response).getResult();
510         if (result != null && ((ContainerNode) result).getValue().isEmpty()) {
511             throw new WebApplicationException(Response.Status.NO_CONTENT);
512         }
513
514         // FIXME: in reference to the above @SupressFBWarnings: "mountPoint" reference here trips up SpotBugs, as it
515         //        thinks it can only ever be null. Except it can very much be non-null. The core problem is the horrible
516         //        structure of this code where we have a sh*tload of checks for mountpoint above and all over the
517         //        codebase where all that difference should have been properly encapsulated.
518         //
519         //        This is legacy code, so if anybody cares to do that refactor, feel free to contribute, but I am not
520         //        doing that work.
521         return new NormalizedNodeContext(new InstanceIdentifierContext<>(null, rpc, mountPoint, schemaContext), result,
522             QueryParametersParser.parseWriterParameters(uriInfo));
523     }
524
525     private static @NonNull NormalizedNode<?, ?> nonnullInput(final SchemaNode rpc, final NormalizedNode<?, ?> input) {
526         return input != null ? input : defaultInput(rpc.getQName());
527     }
528
529     private static @NonNull ContainerNode defaultInput(final QName rpcName) {
530         return ImmutableNodes.containerNode(YangConstants.operationInputQName(rpcName.getModule()));
531     }
532
533     @SuppressWarnings("checkstyle:avoidHidingCauseException")
534     private static DOMRpcResult checkRpcResponse(final ListenableFuture<? extends DOMRpcResult> response) {
535         if (response == null) {
536             return null;
537         }
538         try {
539             final DOMRpcResult retValue = response.get();
540             if (retValue.getErrors().isEmpty()) {
541                 return retValue;
542             }
543             LOG.debug("RpcError message {}", retValue.getErrors());
544             throw new RestconfDocumentedException("RpcError message", null, retValue.getErrors());
545         } catch (final InterruptedException e) {
546             final String errMsg = "The operation was interrupted while executing and did not complete.";
547             LOG.debug("Rpc Interrupt - {}", errMsg, e);
548             throw new RestconfDocumentedException(errMsg, ErrorType.RPC, ErrorTag.PARTIAL_OPERATION, e);
549         } catch (final ExecutionException e) {
550             LOG.debug("Execution RpcError: ", e);
551             Throwable cause = e.getCause();
552             if (cause == null) {
553                 throw new RestconfDocumentedException("The operation encountered an unexpected error while executing.",
554                     e);
555             }
556             while (cause.getCause() != null) {
557                 cause = cause.getCause();
558             }
559
560             if (cause instanceof IllegalArgumentException) {
561                 throw new RestconfDocumentedException(cause.getMessage(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
562             } else if (cause instanceof DOMRpcImplementationNotAvailableException) {
563                 throw new RestconfDocumentedException(cause.getMessage(), ErrorType.APPLICATION,
564                     ErrorTag.OPERATION_NOT_SUPPORTED);
565             }
566             throw new RestconfDocumentedException("The operation encountered an unexpected error while executing.",
567                 cause);
568         } catch (final CancellationException e) {
569             final String errMsg = "The operation was cancelled while executing.";
570             LOG.debug("Cancel RpcExecution: {}", errMsg, e);
571             throw new RestconfDocumentedException(errMsg, ErrorType.RPC, ErrorTag.PARTIAL_OPERATION);
572         }
573     }
574
575     private static void validateInput(final SchemaNode inputSchema, final NormalizedNodeContext payload) {
576         if (inputSchema != null && payload.getData() == null) {
577             // expected a non null payload
578             throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
579         } else if (inputSchema == null && payload.getData() != null) {
580             // did not expect any input
581             throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
582         }
583     }
584
585     private ListenableFuture<DOMRpcResult> invokeSalRemoteRpcSubscribeRPC(final NormalizedNodeContext payload) {
586         final ContainerNode value = (ContainerNode) payload.getData();
587         final QName rpcQName = payload.getInstanceIdentifierContext().getSchemaNode().getQName();
588         final Optional<DataContainerChild<? extends PathArgument, ?>> path = value.getChild(
589             new NodeIdentifier(QName.create(payload.getInstanceIdentifierContext().getSchemaNode().getQName(),
590                 "path")));
591         final Object pathValue = path.isPresent() ? path.get().getValue() : null;
592
593         if (!(pathValue instanceof YangInstanceIdentifier)) {
594             LOG.debug("Instance identifier {} was not normalized correctly", rpcQName);
595             throw new RestconfDocumentedException("Instance identifier was not normalized correctly",
596                 ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
597         }
598
599         final YangInstanceIdentifier pathIdentifier = (YangInstanceIdentifier) pathValue;
600         String streamName = (String) CREATE_DATA_SUBSCR;
601         NotificationOutputType outputType = null;
602         if (!pathIdentifier.isEmpty()) {
603             final String fullRestconfIdentifier =
604                     DATA_SUBSCR + this.controllerContext.toFullRestconfIdentifier(pathIdentifier, null);
605
606             LogicalDatastoreType datastore =
607                     parseEnumTypeParameter(value, LogicalDatastoreType.class, DATASTORE_PARAM_NAME);
608             datastore = datastore == null ? DEFAULT_DATASTORE : datastore;
609
610             DataChangeScope scope = parseEnumTypeParameter(value, DataChangeScope.class, SCOPE_PARAM_NAME);
611             scope = scope == null ? DEFAULT_SCOPE : scope;
612
613             outputType = parseEnumTypeParameter(value, NotificationOutputType.class, OUTPUT_TYPE_PARAM_NAME);
614             outputType = outputType == null ? NotificationOutputType.XML : outputType;
615
616             streamName = Notificator
617                     .createStreamNameFromUri(fullRestconfIdentifier + "/datastore=" + datastore + "/scope=" + scope);
618         }
619
620         if (Strings.isNullOrEmpty(streamName)) {
621             LOG.debug("Path is empty or contains value node which is not Container or List built-in type at {}",
622                 pathIdentifier);
623             throw new RestconfDocumentedException("Path is empty or contains value node which is not Container or List "
624                     + "built-in type.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
625         }
626
627         final QName outputQname = QName.create(rpcQName, "output");
628         final QName streamNameQname = QName.create(rpcQName, "stream-name");
629
630         final ContainerNode output =
631                 ImmutableContainerNodeBuilder.create().withNodeIdentifier(new NodeIdentifier(outputQname))
632                         .withChild(ImmutableNodes.leafNode(streamNameQname, streamName)).build();
633
634         if (!Notificator.existListenerFor(streamName)) {
635             Notificator.createListener(pathIdentifier, streamName, outputType, controllerContext);
636         }
637
638         return Futures.immediateFuture(new DefaultDOMRpcResult(output));
639     }
640
641     private static RpcDefinition findRpc(final SchemaContext schemaContext, final String identifierDecoded) {
642         final String[] splittedIdentifier = identifierDecoded.split(":");
643         if (splittedIdentifier.length != 2) {
644             LOG.debug("{} could not be split to 2 parts (module:rpc name)", identifierDecoded);
645             throw new RestconfDocumentedException(identifierDecoded + " could not be split to 2 parts "
646                     + "(module:rpc name)", ErrorType.APPLICATION, ErrorTag.INVALID_VALUE);
647         }
648         for (final Module module : schemaContext.getModules()) {
649             if (module.getName().equals(splittedIdentifier[0])) {
650                 for (final RpcDefinition rpcDefinition : module.getRpcs()) {
651                     if (rpcDefinition.getQName().getLocalName().equals(splittedIdentifier[1])) {
652                         return rpcDefinition;
653                     }
654                 }
655             }
656         }
657         return null;
658     }
659
660     @Override
661     public NormalizedNodeContext readConfigurationData(final String identifier, final UriInfo uriInfo) {
662         boolean withDefaUsed = false;
663         String withDefa = null;
664
665         for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
666             switch (entry.getKey()) {
667                 case "with-defaults":
668                     if (!withDefaUsed) {
669                         withDefaUsed = true;
670                         withDefa = entry.getValue().iterator().next();
671                     } else {
672                         throw new RestconfDocumentedException("With-defaults parameter can be used only once.");
673                     }
674                     break;
675                 default:
676                     LOG.info("Unknown key : {}.", entry.getKey());
677                     break;
678             }
679         }
680         boolean tagged = false;
681         if (withDefaUsed) {
682             if ("report-all-tagged".equals(withDefa)) {
683                 tagged = true;
684                 withDefa = null;
685             }
686             if ("report-all".equals(withDefa)) {
687                 withDefa = null;
688             }
689         }
690
691         final InstanceIdentifierContext<?> iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
692         final DOMMountPoint mountPoint = iiWithData.getMountPoint();
693         NormalizedNode<?, ?> data = null;
694         final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
695         if (mountPoint != null) {
696             data = this.broker.readConfigurationData(mountPoint, normalizedII, withDefa);
697         } else {
698             data = this.broker.readConfigurationData(normalizedII, withDefa);
699         }
700         if (data == null) {
701             throw dataMissing(identifier);
702         }
703         return new NormalizedNodeContext(iiWithData, data,
704                 QueryParametersParser.parseWriterParameters(uriInfo, tagged));
705     }
706
707     @Override
708     public NormalizedNodeContext readOperationalData(final String identifier, final UriInfo uriInfo) {
709         final InstanceIdentifierContext<?> iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
710         final DOMMountPoint mountPoint = iiWithData.getMountPoint();
711         NormalizedNode<?, ?> data = null;
712         final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
713         if (mountPoint != null) {
714             data = this.broker.readOperationalData(mountPoint, normalizedII);
715         } else {
716             data = this.broker.readOperationalData(normalizedII);
717         }
718         if (data == null) {
719             throw dataMissing(identifier);
720         }
721         return new NormalizedNodeContext(iiWithData, data, QueryParametersParser.parseWriterParameters(uriInfo));
722     }
723
724     private static RestconfDocumentedException dataMissing(final String identifier) {
725         LOG.debug("Request could not be completed because the relevant data model content does not exist {}",
726             identifier);
727         return new RestconfDocumentedException("Request could not be completed because the relevant data model content "
728             + "does not exist", ErrorType.APPLICATION, ErrorTag.DATA_MISSING);
729     }
730
731     @Override
732     public Response updateConfigurationData(final String identifier, final NormalizedNodeContext payload,
733             final UriInfo uriInfo) {
734         boolean insertUsed = false;
735         boolean pointUsed = false;
736         String insert = null;
737         String point = null;
738
739         for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
740             switch (entry.getKey()) {
741                 case "insert":
742                     if (!insertUsed) {
743                         insertUsed = true;
744                         insert = entry.getValue().iterator().next();
745                     } else {
746                         throw new RestconfDocumentedException("Insert parameter can be used only once.");
747                     }
748                     break;
749                 case "point":
750                     if (!pointUsed) {
751                         pointUsed = true;
752                         point = entry.getValue().iterator().next();
753                     } else {
754                         throw new RestconfDocumentedException("Point parameter can be used only once.");
755                     }
756                     break;
757                 default:
758                     throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey());
759             }
760         }
761
762         if (pointUsed && !insertUsed) {
763             throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.");
764         }
765         if (pointUsed && (insert.equals("first") || insert.equals("last"))) {
766             throw new RestconfDocumentedException(
767                     "Point parameter can be used only with 'after' or 'before' values of Insert parameter.");
768         }
769
770         requireNonNull(identifier);
771
772         final InstanceIdentifierContext<?> iiWithData = payload.getInstanceIdentifierContext();
773
774         validateInput(iiWithData.getSchemaNode(), payload);
775         validateTopLevelNodeName(payload, iiWithData.getInstanceIdentifier());
776         validateListKeysEqualityInPayloadAndUri(payload);
777
778         final DOMMountPoint mountPoint = iiWithData.getMountPoint();
779         final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
780
781         /*
782          * There is a small window where another write transaction could be
783          * updating the same data simultaneously and we get an
784          * OptimisticLockFailedException. This error is likely transient and The
785          * WriteTransaction#submit API docs state that a retry will likely
786          * succeed. So we'll try again if that scenario occurs. If it fails a
787          * third time then it probably will never succeed so we'll fail in that
788          * case.
789          *
790          * By retrying we're attempting to hide the internal implementation of
791          * the data store and how it handles concurrent updates from the
792          * restconf client. The client has instructed us to put the data and we
793          * should make every effort to do so without pushing optimistic lock
794          * failures back to the client and forcing them to handle it via retry
795          * (and having to document the behavior).
796          */
797         PutResult result = null;
798         int tries = 2;
799         while (true) {
800             if (mountPoint != null) {
801
802                 result = this.broker.commitMountPointDataPut(mountPoint, normalizedII, payload.getData(), insert,
803                         point);
804             } else {
805                 result = this.broker.commitConfigurationDataPut(this.controllerContext.getGlobalSchema(), normalizedII,
806                         payload.getData(), insert, point);
807             }
808
809             try {
810                 result.getFutureOfPutData().get();
811             } catch (final InterruptedException e) {
812                 LOG.debug("Update failed for {}", identifier, e);
813                 throw new RestconfDocumentedException(e.getMessage(), e);
814             } catch (final ExecutionException e) {
815                 final TransactionCommitFailedException failure = Throwables.getCauseAs(e,
816                     TransactionCommitFailedException.class);
817                 if (failure instanceof OptimisticLockFailedException) {
818                     if (--tries <= 0) {
819                         LOG.debug("Got OptimisticLockFailedException on last try - failing {}", identifier);
820                         throw new RestconfDocumentedException(e.getMessage(), e, failure.getErrorList());
821                     }
822
823                     LOG.debug("Got OptimisticLockFailedException - trying again {}", identifier);
824                     continue;
825                 }
826
827                 LOG.debug("Update failed for {}", identifier, e);
828                 throw RestconfDocumentedException.decodeAndThrow(e.getMessage(), failure);
829             }
830
831             return Response.status(result.getStatus()).build();
832         }
833     }
834
835     private static void validateTopLevelNodeName(final NormalizedNodeContext node,
836             final YangInstanceIdentifier identifier) {
837
838         final String payloadName = node.getData().getNodeType().getLocalName();
839
840         // no arguments
841         if (identifier.isEmpty()) {
842             // no "data" payload
843             if (!node.getData().getNodeType().equals(NETCONF_BASE_QNAME)) {
844                 throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument",
845                         ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
846             }
847             // any arguments
848         } else {
849             final String identifierName = identifier.getLastPathArgument().getNodeType().getLocalName();
850             if (!payloadName.equals(identifierName)) {
851                 throw new RestconfDocumentedException(
852                         "Payload name (" + payloadName + ") is different from identifier name (" + identifierName + ")",
853                         ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
854             }
855         }
856     }
857
858     /**
859      * Validates whether keys in {@code payload} are equal to values of keys in
860      * {@code iiWithData} for list schema node.
861      *
862      * @throws RestconfDocumentedException
863      *             if key values or key count in payload and URI isn't equal
864      *
865      */
866     private static void validateListKeysEqualityInPayloadAndUri(final NormalizedNodeContext payload) {
867         checkArgument(payload != null);
868         final InstanceIdentifierContext<?> iiWithData = payload.getInstanceIdentifierContext();
869         final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
870         final SchemaNode schemaNode = iiWithData.getSchemaNode();
871         final NormalizedNode<?, ?> data = payload.getData();
872         if (schemaNode instanceof ListSchemaNode) {
873             final List<QName> keyDefinitions = ((ListSchemaNode) schemaNode).getKeyDefinition();
874             if (lastPathArgument instanceof NodeIdentifierWithPredicates && data instanceof MapEntryNode) {
875                 final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument).asMap();
876                 isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions);
877             }
878         }
879     }
880
881     @VisibleForTesting
882     public static void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final MapEntryNode payload,
883             final List<QName> keyDefinitions) {
884
885         final Map<QName, Object> mutableCopyUriKeyValues = new HashMap<>(uriKeyValues);
886         for (final QName keyDefinition : keyDefinitions) {
887             final Object uriKeyValue = RestconfDocumentedException.throwIfNull(
888                 // should be caught during parsing URI to InstanceIdentifier
889                 mutableCopyUriKeyValues.remove(keyDefinition), ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
890                 "Missing key %s in URI.", keyDefinition);
891
892             final Object dataKeyValue = payload.getIdentifier().getValue(keyDefinition);
893
894             if (!Objects.deepEquals(uriKeyValue, dataKeyValue)) {
895                 final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName()
896                         + "' specified in the URI doesn't match the value '" + dataKeyValue
897                         + "' specified in the message body. ";
898                 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
899             }
900         }
901     }
902
903     @Override
904     public Response createConfigurationData(final String identifier, final NormalizedNodeContext payload,
905             final UriInfo uriInfo) {
906         return createConfigurationData(payload, uriInfo);
907     }
908
909     @Override
910     public Response createConfigurationData(final NormalizedNodeContext payload, final UriInfo uriInfo) {
911         if (payload == null) {
912             throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
913         }
914         final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
915         final InstanceIdentifierContext<?> iiWithData = payload.getInstanceIdentifierContext();
916         final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
917
918         boolean insertUsed = false;
919         boolean pointUsed = false;
920         String insert = null;
921         String point = null;
922
923         if (uriInfo != null) {
924             for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
925                 switch (entry.getKey()) {
926                     case "insert":
927                         if (!insertUsed) {
928                             insertUsed = true;
929                             insert = entry.getValue().iterator().next();
930                         } else {
931                             throw new RestconfDocumentedException("Insert parameter can be used only once.");
932                         }
933                         break;
934                     case "point":
935                         if (!pointUsed) {
936                             pointUsed = true;
937                             point = entry.getValue().iterator().next();
938                         } else {
939                             throw new RestconfDocumentedException("Point parameter can be used only once.");
940                         }
941                         break;
942                     default:
943                         throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey());
944                 }
945             }
946         }
947
948         if (pointUsed && !insertUsed) {
949             throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.");
950         }
951         if (pointUsed && (insert.equals("first") || insert.equals("last"))) {
952             throw new RestconfDocumentedException(
953                     "Point parameter can be used only with 'after' or 'before' values of Insert parameter.");
954         }
955
956         FluentFuture<? extends CommitInfo> future;
957         if (mountPoint != null) {
958             future = this.broker.commitConfigurationDataPost(mountPoint, normalizedII, payload.getData(), insert,
959                     point);
960         } else {
961             future = this.broker.commitConfigurationDataPost(this.controllerContext.getGlobalSchema(), normalizedII,
962                     payload.getData(), insert, point);
963         }
964
965         try {
966             future.get();
967         } catch (final InterruptedException e) {
968             LOG.info("Error creating data {}", uriInfo != null ? uriInfo.getPath() : "", e);
969             throw new RestconfDocumentedException(e.getMessage(), e);
970         } catch (final ExecutionException e) {
971             LOG.info("Error creating data {}", uriInfo != null ? uriInfo.getPath() : "", e);
972             throw RestconfDocumentedException.decodeAndThrow(e.getMessage(), Throwables.getCauseAs(e,
973                 TransactionCommitFailedException.class));
974         }
975
976         LOG.trace("Successfuly created data.");
977
978         final ResponseBuilder responseBuilder = Response.status(Status.NO_CONTENT);
979         // FIXME: Provide path to result.
980         final URI location = resolveLocation(uriInfo, "", mountPoint, normalizedII);
981         if (location != null) {
982             responseBuilder.location(location);
983         }
984         return responseBuilder.build();
985     }
986
987     @SuppressWarnings("checkstyle:IllegalCatch")
988     private URI resolveLocation(final UriInfo uriInfo, final String uriBehindBase, final DOMMountPoint mountPoint,
989             final YangInstanceIdentifier normalizedII) {
990         if (uriInfo == null) {
991             // This is null if invoked internally
992             return null;
993         }
994
995         final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
996         uriBuilder.path("config");
997         try {
998             uriBuilder.path(this.controllerContext.toFullRestconfIdentifier(normalizedII, mountPoint));
999         } catch (final Exception e) {
1000             LOG.info("Location for instance identifier {} was not created", normalizedII, e);
1001             return null;
1002         }
1003         return uriBuilder.build();
1004     }
1005
1006     @Override
1007     public Response deleteConfigurationData(final String identifier) {
1008         final InstanceIdentifierContext<?> iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
1009         final DOMMountPoint mountPoint = iiWithData.getMountPoint();
1010         final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
1011
1012         final FluentFuture<? extends CommitInfo> future;
1013         if (mountPoint != null) {
1014             future = this.broker.commitConfigurationDataDelete(mountPoint, normalizedII);
1015         } else {
1016             future = this.broker.commitConfigurationDataDelete(normalizedII);
1017         }
1018
1019         try {
1020             future.get();
1021         } catch (final InterruptedException e) {
1022             throw new RestconfDocumentedException(e.getMessage(), e);
1023         } catch (final ExecutionException e) {
1024             final Optional<Throwable> searchedException = Iterables.tryFind(Throwables.getCausalChain(e),
1025                     Predicates.instanceOf(ModifiedNodeDoesNotExistException.class)).toJavaUtil();
1026             if (searchedException.isPresent()) {
1027                 throw new RestconfDocumentedException("Data specified for delete doesn't exist.", ErrorType.APPLICATION,
1028                     ErrorTag.DATA_MISSING, e);
1029             }
1030
1031             throw RestconfDocumentedException.decodeAndThrow(e.getMessage(), Throwables.getCauseAs(e,
1032                 TransactionCommitFailedException.class));
1033         }
1034
1035         return Response.status(Status.OK).build();
1036     }
1037
1038     /**
1039      * Subscribes to some path in schema context (stream) to listen on changes
1040      * on this stream.
1041      *
1042      * <p>
1043      * Additional parameters for subscribing to stream are loaded via rpc input
1044      * parameters:
1045      * <ul>
1046      * <li>datastore - default CONFIGURATION (other values of
1047      * {@link LogicalDatastoreType} enum type)</li>
1048      * <li>scope - default BASE (other values of {@link DataChangeScope})</li>
1049      * </ul>
1050      */
1051     @Override
1052     public NormalizedNodeContext subscribeToStream(final String identifier, final UriInfo uriInfo) {
1053         boolean startTimeUsed = false;
1054         boolean stopTimeUsed = false;
1055         Instant start = Instant.now();
1056         Instant stop = null;
1057         boolean filterUsed = false;
1058         String filter = null;
1059         boolean leafNodesOnlyUsed = false;
1060         boolean leafNodesOnly = false;
1061         boolean skipNotificationDataUsed = false;
1062         boolean skipNotificationData = false;
1063
1064         for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
1065             switch (entry.getKey()) {
1066                 case "start-time":
1067                     if (!startTimeUsed) {
1068                         startTimeUsed = true;
1069                         start = parseDateFromQueryParam(entry);
1070                     } else {
1071                         throw new RestconfDocumentedException("Start-time parameter can be used only once.");
1072                     }
1073                     break;
1074                 case "stop-time":
1075                     if (!stopTimeUsed) {
1076                         stopTimeUsed = true;
1077                         stop = parseDateFromQueryParam(entry);
1078                     } else {
1079                         throw new RestconfDocumentedException("Stop-time parameter can be used only once.");
1080                     }
1081                     break;
1082                 case "filter":
1083                     if (!filterUsed) {
1084                         filterUsed = true;
1085                         filter = entry.getValue().iterator().next();
1086                     } else {
1087                         throw new RestconfDocumentedException("Filter parameter can be used only once.");
1088                     }
1089                     break;
1090                 case "odl-leaf-nodes-only":
1091                     if (!leafNodesOnlyUsed) {
1092                         leafNodesOnlyUsed = true;
1093                         leafNodesOnly = Boolean.parseBoolean(entry.getValue().iterator().next());
1094                     } else {
1095                         throw new RestconfDocumentedException("Odl-leaf-nodes-only parameter can be used only once.");
1096                     }
1097                     break;
1098                 case "odl-skip-notification-data":
1099                     if (!skipNotificationDataUsed) {
1100                         skipNotificationDataUsed = true;
1101                         skipNotificationData = Boolean.parseBoolean(entry.getValue().iterator().next());
1102                     } else {
1103                         throw new RestconfDocumentedException(
1104                                 "Odl-skip-notification-data parameter can be used only once.");
1105                     }
1106                     break;
1107                 default:
1108                     throw new RestconfDocumentedException("Bad parameter used with notifications: " + entry.getKey());
1109             }
1110         }
1111         if (!startTimeUsed && stopTimeUsed) {
1112             throw new RestconfDocumentedException("Stop-time parameter has to be used with start-time parameter.");
1113         }
1114         URI response = null;
1115         if (identifier.contains(DATA_SUBSCR)) {
1116             response = dataSubs(identifier, uriInfo, start, stop, filter, leafNodesOnly, skipNotificationData);
1117         } else if (identifier.contains(NOTIFICATION_STREAM)) {
1118             response = notifStream(identifier, uriInfo, start, stop, filter);
1119         }
1120
1121         if (response != null) {
1122             // prepare node with value of location
1123             final InstanceIdentifierContext<?> iid = prepareIIDSubsStreamOutput();
1124             final NormalizedNodeBuilder<NodeIdentifier, Object, LeafNode<Object>> builder =
1125                     ImmutableLeafNodeBuilder.create().withValue(response.toString());
1126             builder.withNodeIdentifier(
1127                     NodeIdentifier.create(QName.create("subscribe:to:notification", "2016-10-28", "location")));
1128
1129             // prepare new header with location
1130             final Map<String, Object> headers = new HashMap<>();
1131             headers.put("Location", response);
1132
1133             return new NormalizedNodeContext(iid, builder.build(), headers);
1134         }
1135
1136         final String msg = "Bad type of notification of sal-remote";
1137         LOG.warn(msg);
1138         throw new RestconfDocumentedException(msg);
1139     }
1140
1141     private static Instant parseDateFromQueryParam(final Entry<String, List<String>> entry) {
1142         final DateAndTime event = new DateAndTime(entry.getValue().iterator().next());
1143         final String value = event.getValue();
1144         final TemporalAccessor p;
1145         try {
1146             p = FORMATTER.parse(value);
1147         } catch (final DateTimeParseException e) {
1148             throw new RestconfDocumentedException("Cannot parse of value in date: " + value, e);
1149         }
1150         return Instant.from(p);
1151     }
1152
1153     /**
1154      * Prepare instance identifier.
1155      *
1156      * @return {@link InstanceIdentifierContext} of location leaf for
1157      *         notification
1158      */
1159     private InstanceIdentifierContext<?> prepareIIDSubsStreamOutput() {
1160         final QName qnameBase = QName.create("subscribe:to:notification", "2016-10-28", "notifi");
1161         final EffectiveModelContext schemaCtx = controllerContext.getGlobalSchema();
1162         final DataSchemaNode location = ((ContainerSchemaNode) schemaCtx
1163                 .findModule(qnameBase.getModule()).orElse(null)
1164                 .getDataChildByName(qnameBase)).getDataChildByName(QName.create(qnameBase, "location"));
1165         final List<PathArgument> path = new ArrayList<>();
1166         path.add(NodeIdentifier.create(qnameBase));
1167         path.add(NodeIdentifier.create(QName.create(qnameBase, "location")));
1168
1169         return new InstanceIdentifierContext<SchemaNode>(YangInstanceIdentifier.create(path), location, null,
1170                 schemaCtx);
1171     }
1172
1173     /**
1174      * Register notification listener by stream name.
1175      *
1176      * @param identifier
1177      *            stream name
1178      * @param uriInfo
1179      *            uriInfo
1180      * @param stop
1181      *            stop-time of getting notification
1182      * @param start
1183      *            start-time of getting notification
1184      * @param filter
1185      *            indicate which subset of all possible events are of interest
1186      * @return {@link URI} of location
1187      */
1188     private URI notifStream(final String identifier, final UriInfo uriInfo, final Instant start,
1189             final Instant stop, final String filter) {
1190         final String streamName = Notificator.createStreamNameFromUri(identifier);
1191         if (Strings.isNullOrEmpty(streamName)) {
1192             throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1193         }
1194         final List<NotificationListenerAdapter> listeners = Notificator.getNotificationListenerFor(streamName);
1195         if (listeners == null || listeners.isEmpty()) {
1196             throw new RestconfDocumentedException("Stream was not found.", ErrorType.PROTOCOL,
1197                     ErrorTag.UNKNOWN_ELEMENT);
1198         }
1199
1200         for (final NotificationListenerAdapter listener : listeners) {
1201             this.broker.registerToListenNotification(listener);
1202             listener.setQueryParams(start, Optional.ofNullable(stop), Optional.ofNullable(filter), false, false);
1203         }
1204
1205         final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
1206
1207         final WebSocketServer webSocketServerInstance = WebSocketServer.getInstance(NOTIFICATION_PORT);
1208         final int notificationPort = webSocketServerInstance.getPort();
1209
1210
1211         final UriBuilder uriToWebsocketServerBuilder = uriBuilder.port(notificationPort).scheme(getWsScheme(uriInfo));
1212
1213         return uriToWebsocketServerBuilder.replacePath(streamName).build();
1214     }
1215
1216     private static String getWsScheme(final UriInfo uriInfo) {
1217         URI uri = uriInfo.getAbsolutePath();
1218         if (uri == null) {
1219             return "ws";
1220         }
1221         String subscriptionScheme = uri.getScheme().toLowerCase(Locale.ROOT);
1222         return subscriptionScheme.equals("https") ? "wss" : "ws";
1223     }
1224
1225     /**
1226      * Register data change listener by stream name.
1227      *
1228      * @param identifier
1229      *            stream name
1230      * @param uriInfo
1231      *            uri info
1232      * @param stop
1233      *            start-time of getting notification
1234      * @param start
1235      *            stop-time of getting notification
1236      * @param filter
1237      *            indicate which subset of all possible events are of interest
1238      * @return {@link URI} of location
1239      */
1240     private URI dataSubs(final String identifier, final UriInfo uriInfo, final Instant start, final Instant stop,
1241             final String filter, final boolean leafNodesOnly, final boolean skipNotificationData) {
1242         final String streamName = Notificator.createStreamNameFromUri(identifier);
1243         if (Strings.isNullOrEmpty(streamName)) {
1244             throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
1245         }
1246
1247         final ListenerAdapter listener = Notificator.getListenerFor(streamName);
1248         if (listener == null) {
1249             throw new RestconfDocumentedException("Stream was not found.", ErrorType.PROTOCOL,
1250                     ErrorTag.UNKNOWN_ELEMENT);
1251         }
1252         listener.setQueryParams(start, Optional.ofNullable(stop), Optional.ofNullable(filter), leafNodesOnly,
1253                 skipNotificationData);
1254
1255         final Map<String, String> paramToValues = resolveValuesFromUri(identifier);
1256         final LogicalDatastoreType datastore =
1257                 parserURIEnumParameter(LogicalDatastoreType.class, paramToValues.get(DATASTORE_PARAM_NAME));
1258         if (datastore == null) {
1259             throw new RestconfDocumentedException("Stream name doesn't contains datastore value (pattern /datastore=)",
1260                     ErrorType.APPLICATION, ErrorTag.MISSING_ATTRIBUTE);
1261         }
1262         final DataChangeScope scope =
1263                 parserURIEnumParameter(DataChangeScope.class, paramToValues.get(SCOPE_PARAM_NAME));
1264         if (scope == null) {
1265             throw new RestconfDocumentedException("Stream name doesn't contains datastore value (pattern /scope=)",
1266                     ErrorType.APPLICATION, ErrorTag.MISSING_ATTRIBUTE);
1267         }
1268
1269         this.broker.registerToListenDataChanges(datastore, scope, listener);
1270
1271         final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
1272
1273         final WebSocketServer webSocketServerInstance = WebSocketServer.getInstance(NOTIFICATION_PORT);
1274         final int notificationPort = webSocketServerInstance.getPort();
1275
1276         final UriBuilder uriToWebsocketServerBuilder = uriBuilder.port(notificationPort).scheme(getWsScheme(uriInfo));
1277
1278         return uriToWebsocketServerBuilder.replacePath(streamName).build();
1279     }
1280
1281     @SuppressWarnings("checkstyle:IllegalCatch")
1282     @Override
1283     public PatchStatusContext patchConfigurationData(final String identifier, final PatchContext context,
1284                                                      final UriInfo uriInfo) {
1285         if (context == null) {
1286             throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
1287         }
1288
1289         try {
1290             return this.broker.patchConfigurationDataWithinTransaction(context);
1291         } catch (final Exception e) {
1292             LOG.debug("Patch transaction failed", e);
1293             throw new RestconfDocumentedException(e.getMessage(), e);
1294         }
1295     }
1296
1297     @SuppressWarnings("checkstyle:IllegalCatch")
1298     @Override
1299     public PatchStatusContext patchConfigurationData(final PatchContext context, @Context final UriInfo uriInfo) {
1300         if (context == null) {
1301             throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
1302         }
1303
1304         try {
1305             return this.broker.patchConfigurationDataWithinTransaction(context);
1306         } catch (final Exception e) {
1307             LOG.debug("Patch transaction failed", e);
1308             throw new RestconfDocumentedException(e.getMessage(), e);
1309         }
1310     }
1311
1312     /**
1313      * Load parameter for subscribing to stream from input composite node.
1314      *
1315      * @param value
1316      *            contains value
1317      * @return enum object if its string value is equal to {@code paramName}. In
1318      *         other cases null.
1319      */
1320     private static <T> T parseEnumTypeParameter(final ContainerNode value, final Class<T> classDescriptor,
1321             final String paramName) {
1322         final Optional<DataContainerChild<? extends PathArgument, ?>> optAugNode = value.getChild(
1323             SAL_REMOTE_AUG_IDENTIFIER);
1324         if (optAugNode.isEmpty()) {
1325             return null;
1326         }
1327         final DataContainerChild<? extends PathArgument, ?> augNode = optAugNode.get();
1328         if (!(augNode instanceof AugmentationNode)) {
1329             return null;
1330         }
1331         final Optional<DataContainerChild<? extends PathArgument, ?>> enumNode = ((AugmentationNode) augNode).getChild(
1332                 new NodeIdentifier(QName.create(SAL_REMOTE_AUGMENT, paramName)));
1333         if (enumNode.isEmpty()) {
1334             return null;
1335         }
1336         final Object rawValue = enumNode.get().getValue();
1337         if (!(rawValue instanceof String)) {
1338             return null;
1339         }
1340
1341         return resolveAsEnum(classDescriptor, (String) rawValue);
1342     }
1343
1344     /**
1345      * Checks whether {@code value} is one of the string representation of
1346      * enumeration {@code classDescriptor}.
1347      *
1348      * @return enum object if string value of {@code classDescriptor}
1349      *         enumeration is equal to {@code value}. Other cases null.
1350      */
1351     private static <T> T parserURIEnumParameter(final Class<T> classDescriptor, final String value) {
1352         if (Strings.isNullOrEmpty(value)) {
1353             return null;
1354         }
1355         return resolveAsEnum(classDescriptor, value);
1356     }
1357
1358     private static <T> T resolveAsEnum(final Class<T> classDescriptor, final String value) {
1359         final T[] enumConstants = classDescriptor.getEnumConstants();
1360         if (enumConstants != null) {
1361             for (final T enm : classDescriptor.getEnumConstants()) {
1362                 if (((Enum<?>) enm).name().equals(value)) {
1363                     return enm;
1364                 }
1365             }
1366         }
1367         return null;
1368     }
1369
1370     private static Map<String, String> resolveValuesFromUri(final String uri) {
1371         final Map<String, String> result = new HashMap<>();
1372         final String[] tokens = uri.split("/");
1373         for (int i = 1; i < tokens.length; i++) {
1374             final String[] parameterTokens = tokens[i].split("=");
1375             if (parameterTokens.length == 2) {
1376                 result.put(parameterTokens[0], parameterTokens[1]);
1377             }
1378         }
1379         return result;
1380     }
1381
1382     private MapNode makeModuleMapNode(final Collection<? extends Module> modules) {
1383         requireNonNull(modules);
1384         final Module restconfModule = getRestconfModule();
1385         final DataSchemaNode moduleSchemaNode = this.controllerContext
1386                 .getRestconfModuleRestConfSchemaNode(restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
1387         checkState(moduleSchemaNode instanceof ListSchemaNode);
1388
1389         final CollectionNodeBuilder<MapEntryNode, MapNode> listModuleBuilder =
1390                 Builders.mapBuilder((ListSchemaNode) moduleSchemaNode);
1391
1392         for (final Module module : modules) {
1393             listModuleBuilder.withChild(toModuleEntryNode(module, moduleSchemaNode));
1394         }
1395         return listModuleBuilder.build();
1396     }
1397
1398     private MapEntryNode toModuleEntryNode(final Module module, final DataSchemaNode moduleSchemaNode) {
1399         checkArgument(moduleSchemaNode instanceof ListSchemaNode,
1400                 "moduleSchemaNode has to be of type ListSchemaNode");
1401         final ListSchemaNode listModuleSchemaNode = (ListSchemaNode) moduleSchemaNode;
1402         final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> moduleNodeValues =
1403                 Builders.mapEntryBuilder(listModuleSchemaNode);
1404
1405         List<DataSchemaNode> instanceDataChildrenByName =
1406                 ControllerContext.findInstanceDataChildrenByName(listModuleSchemaNode, "name");
1407         final DataSchemaNode nameSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
1408         checkState(nameSchemaNode instanceof LeafSchemaNode);
1409         moduleNodeValues
1410                 .withChild(Builders.leafBuilder((LeafSchemaNode) nameSchemaNode).withValue(module.getName()).build());
1411
1412         final QNameModule qNameModule = module.getQNameModule();
1413
1414         instanceDataChildrenByName =
1415                 ControllerContext.findInstanceDataChildrenByName(listModuleSchemaNode, "revision");
1416         final DataSchemaNode revisionSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
1417         checkState(revisionSchemaNode instanceof LeafSchemaNode);
1418         final Optional<Revision> revision = qNameModule.getRevision();
1419         moduleNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) revisionSchemaNode)
1420                 .withValue(revision.map(Revision::toString).orElse("")).build());
1421
1422         instanceDataChildrenByName =
1423                 ControllerContext.findInstanceDataChildrenByName(listModuleSchemaNode, "namespace");
1424         final DataSchemaNode namespaceSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
1425         checkState(namespaceSchemaNode instanceof LeafSchemaNode);
1426         moduleNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) namespaceSchemaNode)
1427                 .withValue(qNameModule.getNamespace().toString()).build());
1428
1429         instanceDataChildrenByName =
1430                 ControllerContext.findInstanceDataChildrenByName(listModuleSchemaNode, "feature");
1431         final DataSchemaNode featureSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
1432         checkState(featureSchemaNode instanceof LeafListSchemaNode);
1433         final ListNodeBuilder<Object, LeafSetEntryNode<Object>> featuresBuilder =
1434                 Builders.leafSetBuilder((LeafListSchemaNode) featureSchemaNode);
1435         for (final FeatureDefinition feature : module.getFeatures()) {
1436             featuresBuilder.withChild(Builders.leafSetEntryBuilder((LeafListSchemaNode) featureSchemaNode)
1437                     .withValue(feature.getQName().getLocalName()).build());
1438         }
1439         moduleNodeValues.withChild(featuresBuilder.build());
1440
1441         return moduleNodeValues.build();
1442     }
1443
1444     protected MapEntryNode toStreamEntryNode(final String streamName, final DataSchemaNode streamSchemaNode) {
1445         checkArgument(streamSchemaNode instanceof ListSchemaNode,
1446                 "streamSchemaNode has to be of type ListSchemaNode");
1447         final ListSchemaNode listStreamSchemaNode = (ListSchemaNode) streamSchemaNode;
1448         final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> streamNodeValues =
1449                 Builders.mapEntryBuilder(listStreamSchemaNode);
1450
1451         List<DataSchemaNode> instanceDataChildrenByName =
1452                 ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "name");
1453         final DataSchemaNode nameSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
1454         checkState(nameSchemaNode instanceof LeafSchemaNode);
1455         streamNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) nameSchemaNode).withValue(streamName).build());
1456
1457         instanceDataChildrenByName =
1458                 ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "description");
1459         final DataSchemaNode descriptionSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
1460         checkState(descriptionSchemaNode instanceof LeafSchemaNode);
1461         streamNodeValues.withChild(
1462                 Builders.leafBuilder((LeafSchemaNode) nameSchemaNode).withValue("DESCRIPTION_PLACEHOLDER").build());
1463
1464         instanceDataChildrenByName =
1465                 ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "replay-support");
1466         final DataSchemaNode replaySupportSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
1467         checkState(replaySupportSchemaNode instanceof LeafSchemaNode);
1468         streamNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) replaySupportSchemaNode)
1469                 .withValue(Boolean.TRUE).build());
1470
1471         instanceDataChildrenByName =
1472                 ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "replay-log-creation-time");
1473         final DataSchemaNode replayLogCreationTimeSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
1474         checkState(replayLogCreationTimeSchemaNode instanceof LeafSchemaNode);
1475         streamNodeValues.withChild(
1476                 Builders.leafBuilder((LeafSchemaNode) replayLogCreationTimeSchemaNode).withValue("").build());
1477
1478         instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "events");
1479         final DataSchemaNode eventsSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null);
1480         checkState(eventsSchemaNode instanceof LeafSchemaNode);
1481         streamNodeValues.withChild(
1482                 Builders.leafBuilder((LeafSchemaNode) eventsSchemaNode).withValue(Empty.getInstance()).build());
1483
1484         return streamNodeValues.build();
1485     }
1486
1487     /**
1488      * Prepare stream for notification.
1489      *
1490      * @param payload
1491      *            contains list of qnames of notifications
1492      * @return - checked future object
1493      */
1494     private ListenableFuture<DOMRpcResult> invokeSalRemoteRpcNotifiStrRPC(final NormalizedNodeContext payload) {
1495         final ContainerNode data = (ContainerNode) payload.getData();
1496         LeafSetNode leafSet = null;
1497         String outputType = "XML";
1498         for (final DataContainerChild<? extends PathArgument, ?> dataChild : data.getValue()) {
1499             if (dataChild instanceof LeafSetNode) {
1500                 leafSet = (LeafSetNode) dataChild;
1501             } else if (dataChild instanceof AugmentationNode) {
1502                 outputType = (String) ((AugmentationNode) dataChild).getValue().iterator().next().getValue();
1503             }
1504         }
1505
1506         final Collection<LeafSetEntryNode> entryNodes = leafSet.getValue();
1507         final List<SchemaPath> paths = new ArrayList<>();
1508         String streamName = CREATE_NOTIFICATION_STREAM + "/";
1509
1510         StringBuilder streamNameBuilder = new StringBuilder(streamName);
1511         final Iterator<LeafSetEntryNode> iterator = entryNodes.iterator();
1512         while (iterator.hasNext()) {
1513             final QName valueQName = QName.create((String) iterator.next().getValue());
1514             final URI namespace = valueQName.getModule().getNamespace();
1515             final Module module = controllerContext.findModuleByNamespace(namespace);
1516             checkNotNull(module, "Module for namespace %s does not exist", namespace);
1517             NotificationDefinition notifiDef = null;
1518             for (final NotificationDefinition notification : module.getNotifications()) {
1519                 if (notification.getQName().equals(valueQName)) {
1520                     notifiDef = notification;
1521                     break;
1522                 }
1523             }
1524             final String moduleName = module.getName();
1525             checkNotNull(notifiDef, "Notification %s does not exist in module %s", valueQName, moduleName);
1526             paths.add(notifiDef.getPath());
1527             streamNameBuilder.append(moduleName).append(':').append(valueQName.getLocalName());
1528             if (iterator.hasNext()) {
1529                 streamNameBuilder.append(',');
1530             }
1531         }
1532
1533         streamName = streamNameBuilder.toString();
1534
1535         final QName rpcQName = payload.getInstanceIdentifierContext().getSchemaNode().getQName();
1536         final QName outputQname = QName.create(rpcQName, "output");
1537         final QName streamNameQname = QName.create(rpcQName, "notification-stream-identifier");
1538
1539         final ContainerNode output =
1540                 ImmutableContainerNodeBuilder.create().withNodeIdentifier(new NodeIdentifier(outputQname))
1541                         .withChild(ImmutableNodes.leafNode(streamNameQname, streamName)).build();
1542
1543         if (!Notificator.existNotificationListenerFor(streamName)) {
1544             Notificator.createNotificationListener(paths, streamName, outputType, controllerContext);
1545         }
1546
1547         return Futures.immediateFuture(new DefaultDOMRpcResult(output));
1548     }
1549
1550     private static EffectiveModelContext modelContext(final DOMMountPoint mountPoint) {
1551         return mountPoint.getService(DOMSchemaService.class)
1552             .flatMap(svc -> Optional.ofNullable(svc.getGlobalContext()))
1553             .orElse(null);
1554     }
1555 }