Move SubscribeToStreamUtil to rests.services.impl
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / services / impl / RestconfDataServiceImpl.java
1 /*
2  * Copyright (c) 2016 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.restconf.nb.rfc8040.rests.services.impl;
9
10 import static java.util.Objects.requireNonNull;
11 import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.PostPutQueryParameters.INSERT;
12 import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.PostPutQueryParameters.POINT;
13 import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.NOTIFICATION_STREAM;
14 import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAMS_PATH;
15 import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_ACCESS_PATH_PART;
16 import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_LOCATION_PATH_PART;
17 import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_PATH;
18 import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_PATH_PART;
19
20 import java.net.URI;
21 import java.time.Clock;
22 import java.time.LocalDateTime;
23 import java.time.format.DateTimeFormatter;
24 import java.util.List;
25 import java.util.Map.Entry;
26 import java.util.Optional;
27 import javax.ws.rs.Path;
28 import javax.ws.rs.WebApplicationException;
29 import javax.ws.rs.core.Response;
30 import javax.ws.rs.core.UriInfo;
31 import org.eclipse.jdt.annotation.NonNull;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.opendaylight.mdsal.dom.api.DOMActionResult;
34 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
35 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
36 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
37 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
38 import org.opendaylight.restconf.common.context.NormalizedNodeContext;
39 import org.opendaylight.restconf.common.context.WriterParameters;
40 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
41 import org.opendaylight.restconf.common.errors.RestconfError;
42 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
43 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
44 import org.opendaylight.restconf.common.patch.PatchContext;
45 import org.opendaylight.restconf.common.patch.PatchStatusContext;
46 import org.opendaylight.restconf.nb.rfc8040.handlers.ActionServiceHandler;
47 import org.opendaylight.restconf.nb.rfc8040.handlers.DOMMountPointServiceHandler;
48 import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
49 import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler;
50 import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfDataService;
51 import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfStreamsSubscriptionService;
52 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
53 import org.opendaylight.restconf.nb.rfc8040.rests.utils.CreateStreamUtil;
54 import org.opendaylight.restconf.nb.rfc8040.rests.utils.DeleteDataTransactionUtil;
55 import org.opendaylight.restconf.nb.rfc8040.rests.utils.PatchDataTransactionUtil;
56 import org.opendaylight.restconf.nb.rfc8040.rests.utils.PlainPatchDataTransactionUtil;
57 import org.opendaylight.restconf.nb.rfc8040.rests.utils.PostDataTransactionUtil;
58 import org.opendaylight.restconf.nb.rfc8040.rests.utils.PutDataTransactionUtil;
59 import org.opendaylight.restconf.nb.rfc8040.rests.utils.ReadDataTransactionUtil;
60 import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant;
61 import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfInvokeOperationsUtil;
62 import org.opendaylight.restconf.nb.rfc8040.streams.listeners.NotificationListenerAdapter;
63 import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
64 import org.opendaylight.restconf.nb.rfc8040.utils.mapping.RestconfMappingNodeUtil;
65 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
66 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
67 import org.opendaylight.yangtools.concepts.Immutable;
68 import org.opendaylight.yangtools.yang.common.QName;
69 import org.opendaylight.yangtools.yang.common.Revision;
70 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
71 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
72 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
73 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
74 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
75 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
76 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
77 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
78 import org.slf4j.Logger;
79 import org.slf4j.LoggerFactory;
80
81 /**
82  * Implementation of {@link RestconfDataService}.
83  */
84 @Path("/")
85 public class RestconfDataServiceImpl implements RestconfDataService {
86     private static final class QueryParams implements Immutable {
87         final @Nullable String point;
88         final @Nullable String insert;
89
90         QueryParams(final @Nullable String insert, final @Nullable String point) {
91             this.insert = insert;
92             this.point = point;
93         }
94     }
95
96     private static final Logger LOG = LoggerFactory.getLogger(RestconfDataServiceImpl.class);
97     private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss");
98
99     private final RestconfStreamsSubscriptionService delegRestconfSubscrService;
100
101     // FIXME: evaluate thread-safety of updates (synchronized) vs. access (mostly unsynchronized) here
102     private SchemaContextHandler schemaContextHandler;
103     private TransactionChainHandler transactionChainHandler;
104     private DOMMountPointServiceHandler mountPointServiceHandler;
105     private volatile ActionServiceHandler actionServiceHandler;
106
107     public RestconfDataServiceImpl(final SchemaContextHandler schemaContextHandler,
108             final TransactionChainHandler transactionChainHandler,
109             final DOMMountPointServiceHandler mountPointServiceHandler,
110             final RestconfStreamsSubscriptionService delegRestconfSubscrService,
111             final ActionServiceHandler actionServiceHandler) {
112         this.actionServiceHandler = requireNonNull(actionServiceHandler);
113         this.schemaContextHandler = requireNonNull(schemaContextHandler);
114         this.transactionChainHandler = requireNonNull(transactionChainHandler);
115         this.mountPointServiceHandler = requireNonNull(mountPointServiceHandler);
116         this.delegRestconfSubscrService = requireNonNull(delegRestconfSubscrService);
117     }
118
119     @Override
120     public synchronized void updateHandlers(final Object... handlers) {
121         for (final Object object : handlers) {
122             if (object instanceof SchemaContextHandler) {
123                 schemaContextHandler = (SchemaContextHandler) object;
124             } else if (object instanceof ActionServiceHandler) {
125                 actionServiceHandler = (ActionServiceHandler) object;
126             } else if (object instanceof DOMMountPointServiceHandler) {
127                 mountPointServiceHandler = (DOMMountPointServiceHandler) object;
128             } else if (object instanceof TransactionChainHandler) {
129                 transactionChainHandler = (TransactionChainHandler) object;
130             }
131         }
132     }
133
134     @Override
135     public Response readData(final UriInfo uriInfo) {
136         return readData(null, uriInfo);
137     }
138
139     @Override
140     public Response readData(final String identifier, final UriInfo uriInfo) {
141         final EffectiveModelContext schemaContextRef = this.schemaContextHandler.get();
142         final InstanceIdentifierContext<?> instanceIdentifier = ParserIdentifier.toInstanceIdentifier(
143                 identifier, schemaContextRef, Optional.of(this.mountPointServiceHandler.get()));
144         final WriterParameters parameters = ReadDataTransactionUtil.parseUriParameters(instanceIdentifier, uriInfo);
145
146         final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
147         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
148                 instanceIdentifier, mountPoint, getTransactionChainHandler(mountPoint));
149         final NormalizedNode<?, ?> node = readData(identifier, parameters.getContent(),
150                 transactionNode, parameters.getWithDefault(), schemaContextRef, uriInfo);
151         if (identifier != null && identifier.contains(STREAM_PATH) && identifier.contains(STREAM_ACCESS_PATH_PART)
152                 && identifier.contains(STREAM_LOCATION_PATH_PART)) {
153             final String value = (String) node.getValue();
154             final String streamName = value.substring(
155                     value.indexOf(NOTIFICATION_STREAM + RestconfConstants.SLASH));
156             this.delegRestconfSubscrService.subscribeToStream(streamName, uriInfo);
157         }
158         if (node == null) {
159             throw new RestconfDocumentedException(
160                     "Request could not be completed because the relevant data model content does not exist",
161                     RestconfError.ErrorType.PROTOCOL,
162                     RestconfError.ErrorTag.DATA_MISSING);
163         }
164
165         if (parameters.getContent().equals(RestconfDataServiceConstant.ReadData.ALL)
166                     || parameters.getContent().equals(RestconfDataServiceConstant.ReadData.CONFIG)) {
167             final QName type = node.getNodeType();
168             return Response.status(200)
169                     .entity(new NormalizedNodeContext(instanceIdentifier, node, parameters))
170                     .header("ETag", '"' + type.getModule().getRevision().map(Revision::toString).orElse(null)
171                         + type.getLocalName() + '"')
172                     .header("Last-Modified", FORMATTER.format(LocalDateTime.now(Clock.systemUTC())))
173                     .build();
174         }
175
176         return Response.status(200).entity(new NormalizedNodeContext(instanceIdentifier, node, parameters)).build();
177     }
178
179
180     /**
181      * Read specific type of data from data store via transaction and if identifier read data from
182      * streams then put streams from actual schema context to datastore.
183      *
184      * @param identifier
185      *             identifier of data to read
186      * @param content
187      *             type of data to read (config, state, all)
188      * @param transactionNode
189      *             {@link TransactionVarsWrapper} - wrapper for variables
190      * @param withDefa
191      *             vaule of with-defaults parameter
192      * @param schemaContext
193      *             schema context
194      * @param uriInfo
195      *             uri info
196      * @return {@link NormalizedNode}
197      */
198     private static NormalizedNode<?, ?> readData(final String identifier, final String content,
199                                                 final TransactionVarsWrapper transactionNode, final String withDefa,
200                                                 final EffectiveModelContext schemaContext, final UriInfo uriInfo) {
201         if (identifier != null && identifier.contains(STREAMS_PATH) && !identifier.contains(STREAM_PATH_PART)) {
202             createAllYangNotificationStreams(transactionNode, schemaContext, uriInfo);
203         }
204         return ReadDataTransactionUtil.readData(content, transactionNode, withDefa, schemaContext);
205     }
206
207     private static void createAllYangNotificationStreams(final TransactionVarsWrapper transactionNode,
208             final EffectiveModelContext schemaContext, final UriInfo uriInfo) {
209         final DOMDataTreeReadWriteTransaction wTx = transactionNode.getTransactionChain().newReadWriteTransaction();
210         final boolean exist = SubscribeToStreamUtil.checkExist(schemaContext, wTx);
211
212         for (final NotificationDefinition notificationDefinition : schemaContext.getNotifications()) {
213             final NotificationListenerAdapter notifiStreamXML =
214                     CreateStreamUtil.createYangNotifiStream(notificationDefinition, schemaContext,
215                             NotificationOutputType.XML);
216             final NotificationListenerAdapter notifiStreamJSON =
217                     CreateStreamUtil.createYangNotifiStream(notificationDefinition, schemaContext,
218                             NotificationOutputType.JSON);
219             writeNotificationStreamToDatastore(schemaContext, uriInfo, wTx, exist, notifiStreamXML);
220             writeNotificationStreamToDatastore(schemaContext, uriInfo, wTx, exist, notifiStreamJSON);
221         }
222         SubscribeToStreamUtil.submitData(wTx);
223     }
224
225     private static void writeNotificationStreamToDatastore(final EffectiveModelContext schemaContext,
226             final UriInfo uriInfo, final DOMDataTreeReadWriteTransaction readWriteTransaction, final boolean exist,
227             final NotificationListenerAdapter listener) {
228         final URI uri = SubscribeToStreamUtil.prepareUriByStreamName(uriInfo, listener.getStreamName());
229         final NormalizedNode<?, ?> mapToStreams =
230                 RestconfMappingNodeUtil.mapYangNotificationStreamByIetfRestconfMonitoring(
231                     listener.getSchemaPath().getLastComponent(), schemaContext.getNotifications(), null,
232                     listener.getOutputType(), uri, SubscribeToStreamUtil.getMonitoringModule(schemaContext), exist);
233         SubscribeToStreamUtil.writeDataToDS(schemaContext,
234                 listener.getSchemaPath().getLastComponent().getLocalName(), readWriteTransaction, exist, mapToStreams);
235     }
236
237     @Override
238     public Response putData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
239         requireNonNull(payload);
240
241         final QueryParams checkedParms = checkQueryParameters(uriInfo);
242
243         final InstanceIdentifierContext<? extends SchemaNode> iid = payload
244                 .getInstanceIdentifierContext();
245
246         PutDataTransactionUtil.validInputData(iid.getSchemaNode(), payload);
247         PutDataTransactionUtil.validTopLevelNodeName(iid.getInstanceIdentifier(), payload);
248         PutDataTransactionUtil.validateListKeysEqualityInPayloadAndUri(payload);
249
250         final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
251         final TransactionChainHandler localTransactionChainHandler;
252         final EffectiveModelContext ref;
253         if (mountPoint == null) {
254             localTransactionChainHandler = this.transactionChainHandler;
255             ref = this.schemaContextHandler.get();
256         } else {
257             localTransactionChainHandler = transactionChainOfMountPoint(mountPoint);
258             ref = mountPoint.getEffectiveModelContext();
259         }
260
261         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
262                 payload.getInstanceIdentifierContext(), mountPoint, localTransactionChainHandler);
263         return PutDataTransactionUtil.putData(payload, ref, transactionNode, checkedParms.insert, checkedParms.point);
264     }
265
266     private static QueryParams checkQueryParameters(final UriInfo uriInfo) {
267         boolean insertUsed = false;
268         boolean pointUsed = false;
269         String insert = null;
270         String point = null;
271
272         for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
273             switch (entry.getKey()) {
274                 case INSERT:
275                     if (!insertUsed) {
276                         insertUsed = true;
277                         insert = entry.getValue().get(0);
278                     } else {
279                         throw new RestconfDocumentedException("Insert parameter can be used only once.",
280                                 RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT);
281                     }
282                     break;
283                 case POINT:
284                     if (!pointUsed) {
285                         pointUsed = true;
286                         point = entry.getValue().get(0);
287                     } else {
288                         throw new RestconfDocumentedException("Point parameter can be used only once.",
289                                 RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT);
290                     }
291                     break;
292                 default:
293                     throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey(),
294                             RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT);
295             }
296         }
297
298         checkQueryParams(insertUsed, pointUsed, insert);
299         return new QueryParams(insert, point);
300     }
301
302     private static void checkQueryParams(final boolean insertUsed, final boolean pointUsed, final String insert) {
303         if (pointUsed && !insertUsed) {
304             throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.",
305                     RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT);
306         }
307         if (pointUsed && (insert.equals("first") || insert.equals("last"))) {
308             throw new RestconfDocumentedException(
309                     "Point parameter can be used only with 'after' or 'before' values of Insert parameter.",
310                     RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT);
311         }
312     }
313
314     @Override
315     public Response postData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
316         return postData(payload, uriInfo);
317     }
318
319     @Override
320     public Response postData(final NormalizedNodeContext payload, final UriInfo uriInfo) {
321         requireNonNull(payload);
322         if (payload.getInstanceIdentifierContext().getSchemaNode() instanceof ActionDefinition) {
323             return invokeAction(payload, uriInfo);
324         }
325
326         final QueryParams checkedParms = checkQueryParameters(uriInfo);
327
328         final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
329         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
330                 payload.getInstanceIdentifierContext(), mountPoint, getTransactionChainHandler(mountPoint));
331         return PostDataTransactionUtil.postData(uriInfo, payload, transactionNode,
332                 getSchemaContext(mountPoint), checkedParms.insert, checkedParms.point);
333     }
334
335     @Override
336     public Response deleteData(final String identifier) {
337         final InstanceIdentifierContext<?> instanceIdentifier = ParserIdentifier.toInstanceIdentifier(
338                 identifier, this.schemaContextHandler.get(), Optional.of(this.mountPointServiceHandler.get()));
339
340         final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
341         final TransactionChainHandler localTransactionChainHandler;
342         if (mountPoint == null) {
343             localTransactionChainHandler = this.transactionChainHandler;
344         } else {
345             localTransactionChainHandler = transactionChainOfMountPoint(mountPoint);
346         }
347
348         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(instanceIdentifier, mountPoint,
349                 localTransactionChainHandler);
350         return DeleteDataTransactionUtil.deleteData(transactionNode);
351     }
352
353     @Override
354     public PatchStatusContext patchData(final String identifier, final PatchContext context, final UriInfo uriInfo) {
355         return patchData(context, uriInfo);
356     }
357
358     @Override
359     public PatchStatusContext patchData(final PatchContext context, final UriInfo uriInfo) {
360         final DOMMountPoint mountPoint = requireNonNull(context).getInstanceIdentifierContext().getMountPoint();
361         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
362                 context.getInstanceIdentifierContext(), mountPoint, getTransactionChainHandler(mountPoint));
363         return PatchDataTransactionUtil.patchData(context, transactionNode, getSchemaContext(mountPoint));
364     }
365
366     @Override
367     public Response patchData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
368         requireNonNull(payload);
369
370         final InstanceIdentifierContext<? extends SchemaNode> iid = payload
371                 .getInstanceIdentifierContext();
372
373         PutDataTransactionUtil.validInputData(iid.getSchemaNode(), payload);
374         PutDataTransactionUtil.validTopLevelNodeName(iid.getInstanceIdentifier(), payload);
375         PutDataTransactionUtil.validateListKeysEqualityInPayloadAndUri(payload);
376
377         final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
378         final TransactionChainHandler localTransactionChainHandler;
379         final EffectiveModelContext ref;
380         if (mountPoint == null) {
381             localTransactionChainHandler = this.transactionChainHandler;
382             ref = this.schemaContextHandler.get();
383         } else {
384             localTransactionChainHandler = transactionChainOfMountPoint(mountPoint);
385             ref = mountPoint.getEffectiveModelContext();
386         }
387
388         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
389                 payload.getInstanceIdentifierContext(), mountPoint, localTransactionChainHandler);
390
391         return PlainPatchDataTransactionUtil.patchData(payload, transactionNode, ref);
392     }
393
394     private TransactionChainHandler getTransactionChainHandler(final DOMMountPoint mountPoint) {
395         return mountPoint == null ? transactionChainHandler : transactionChainOfMountPoint(mountPoint);
396     }
397
398     private EffectiveModelContext getSchemaContext(final DOMMountPoint mountPoint) {
399         return mountPoint == null ? schemaContextHandler.get() : mountPoint.getEffectiveModelContext();
400     }
401
402     /**
403      * Prepare transaction chain to access data of mount point.
404      * @param mountPoint
405      *            mount point reference
406      * @return {@link TransactionChainHandler}
407      */
408     private static TransactionChainHandler transactionChainOfMountPoint(final @NonNull DOMMountPoint mountPoint) {
409         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
410         if (domDataBrokerService.isPresent()) {
411             return new TransactionChainHandler(domDataBrokerService.get());
412         }
413
414         final String errMsg = "DOM data broker service isn't available for mount point " + mountPoint.getIdentifier();
415         LOG.warn(errMsg);
416         throw new RestconfDocumentedException(errMsg);
417     }
418
419     /**
420      * Invoke Action operation.
421      *
422      * @param payload
423      *             {@link NormalizedNodeContext} - the body of the operation
424      * @param uriInfo
425      *             URI info
426      * @return {@link NormalizedNodeContext} wrapped in {@link Response}
427      */
428     public Response invokeAction(final NormalizedNodeContext payload, final UriInfo uriInfo) {
429         final InstanceIdentifierContext<?> context = payload.getInstanceIdentifierContext();
430         final DOMMountPoint mountPoint = context.getMountPoint();
431         final SchemaPath schemaPath = context.getSchemaNode().getPath();
432         final YangInstanceIdentifier yangIIdContext = context.getInstanceIdentifier();
433         final NormalizedNode<?, ?> data = payload.getData();
434
435         if (yangIIdContext.isEmpty() && !RestconfDataServiceConstant.NETCONF_BASE_QNAME.equals(data.getNodeType())) {
436             throw new RestconfDocumentedException("Instance identifier need to contain at least one path argument",
437                 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
438         }
439
440         final DOMActionResult response;
441         final EffectiveModelContext schemaContextRef;
442         if (mountPoint != null) {
443             response = RestconfInvokeOperationsUtil.invokeActionViaMountPoint(mountPoint, (ContainerNode) data,
444                 schemaPath, yangIIdContext);
445             schemaContextRef = mountPoint.getEffectiveModelContext();
446         } else {
447             response = RestconfInvokeOperationsUtil.invokeAction((ContainerNode) data, schemaPath,
448                 this.actionServiceHandler, yangIIdContext);
449             schemaContextRef = this.schemaContextHandler.get();
450         }
451         final DOMActionResult result = RestconfInvokeOperationsUtil.checkActionResponse(response);
452
453         ActionDefinition resultNodeSchema = null;
454         ContainerNode resultData = null;
455         if (result != null) {
456             final Optional<ContainerNode> optOutput = result.getOutput();
457             if (optOutput.isPresent()) {
458                 resultData = optOutput.get();
459                 resultNodeSchema = (ActionDefinition) context.getSchemaNode();
460             }
461         }
462
463         if (resultData != null && resultData.getValue().isEmpty()) {
464             throw new WebApplicationException(Response.Status.NO_CONTENT);
465         }
466
467         return Response.status(200).entity(new NormalizedNodeContext(new InstanceIdentifierContext<>(yangIIdContext,
468                 resultNodeSchema, mountPoint, schemaContextRef), resultData)).build();
469     }
470 }