2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.restconf.nb.rfc8040.rests.services.impl;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.util.concurrent.FutureCallback;
14 import com.google.common.util.concurrent.Futures;
15 import com.google.common.util.concurrent.MoreExecutors;
16 import java.io.IOException;
17 import java.io.InputStream;
19 import java.time.Clock;
20 import java.time.LocalDateTime;
21 import java.time.format.DateTimeFormatter;
22 import java.util.List;
23 import java.util.concurrent.CancellationException;
24 import javax.ws.rs.Consumes;
25 import javax.ws.rs.DELETE;
26 import javax.ws.rs.Encoded;
27 import javax.ws.rs.GET;
28 import javax.ws.rs.PATCH;
29 import javax.ws.rs.POST;
30 import javax.ws.rs.PUT;
31 import javax.ws.rs.Path;
32 import javax.ws.rs.PathParam;
33 import javax.ws.rs.Produces;
34 import javax.ws.rs.container.AsyncResponse;
35 import javax.ws.rs.container.Suspended;
36 import javax.ws.rs.core.Context;
37 import javax.ws.rs.core.MediaType;
38 import javax.ws.rs.core.Response;
39 import javax.ws.rs.core.Response.Status;
40 import javax.ws.rs.core.UriInfo;
41 import org.eclipse.jdt.annotation.NonNull;
42 import org.eclipse.jdt.annotation.Nullable;
43 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
44 import org.opendaylight.mdsal.dom.api.DOMActionException;
45 import org.opendaylight.mdsal.dom.api.DOMActionResult;
46 import org.opendaylight.mdsal.dom.api.DOMActionService;
47 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
48 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
49 import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult;
50 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
51 import org.opendaylight.restconf.common.errors.RestconfFuture;
52 import org.opendaylight.restconf.common.errors.SettableRestconfFuture;
53 import org.opendaylight.restconf.common.patch.PatchContext;
54 import org.opendaylight.restconf.common.patch.PatchStatusContext;
55 import org.opendaylight.restconf.common.patch.PatchStatusEntity;
56 import org.opendaylight.restconf.nb.rfc8040.MediaTypes;
57 import org.opendaylight.restconf.nb.rfc8040.ReadDataParams;
58 import org.opendaylight.restconf.nb.rfc8040.databind.ChildBody;
59 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
60 import org.opendaylight.restconf.nb.rfc8040.databind.JsonChildBody;
61 import org.opendaylight.restconf.nb.rfc8040.databind.JsonOperationInputBody;
62 import org.opendaylight.restconf.nb.rfc8040.databind.JsonPatchBody;
63 import org.opendaylight.restconf.nb.rfc8040.databind.JsonResourceBody;
64 import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
65 import org.opendaylight.restconf.nb.rfc8040.databind.PatchBody;
66 import org.opendaylight.restconf.nb.rfc8040.databind.ResourceBody;
67 import org.opendaylight.restconf.nb.rfc8040.databind.XmlChildBody;
68 import org.opendaylight.restconf.nb.rfc8040.databind.XmlOperationInputBody;
69 import org.opendaylight.restconf.nb.rfc8040.databind.XmlPatchBody;
70 import org.opendaylight.restconf.nb.rfc8040.databind.XmlResourceBody;
71 import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
72 import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
73 import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
74 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
75 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy.CreateOrReplaceResult;
76 import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
77 import org.opendaylight.yangtools.yang.common.Empty;
78 import org.opendaylight.yangtools.yang.common.ErrorTag;
79 import org.opendaylight.yangtools.yang.common.ErrorType;
80 import org.opendaylight.yangtools.yang.common.QName;
81 import org.opendaylight.yangtools.yang.common.Revision;
82 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
83 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
84 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
85 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
86 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
87 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
88 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
89 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
90 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
91 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
92 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
93 import org.slf4j.Logger;
94 import org.slf4j.LoggerFactory;
97 * The "{+restconf}/data" subtree represents the datastore resource type, which is a collection of configuration data
98 * and state data nodes.
101 public final class RestconfDataServiceImpl {
102 private static final Logger LOG = LoggerFactory.getLogger(RestconfDataServiceImpl.class);
103 private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss");
105 private final DatabindProvider databindProvider;
106 private final DOMActionService actionService;
107 private final MdsalRestconfServer server;
109 public RestconfDataServiceImpl(final DatabindProvider databindProvider, final MdsalRestconfServer server,
110 final DOMActionService actionService) {
111 this.databindProvider = requireNonNull(databindProvider);
112 this.server = requireNonNull(server);
113 this.actionService = requireNonNull(actionService);
117 * Get target data resource from data root.
119 * @param uriInfo URI info
120 * @return {@link NormalizedNodePayload}
125 MediaTypes.APPLICATION_YANG_DATA_JSON,
126 MediaTypes.APPLICATION_YANG_DATA_XML,
127 MediaType.APPLICATION_JSON,
128 MediaType.APPLICATION_XML,
131 public Response readData(@Context final UriInfo uriInfo) {
132 final var readParams = QueryParams.newReadDataParams(uriInfo);
133 return readData(server.bindRequestRoot(), readParams);
137 * Get target data resource.
139 * @param identifier path to target
140 * @param uriInfo URI info
141 * @return {@link NormalizedNodePayload}
144 @Path("/data/{identifier:.+}")
146 MediaTypes.APPLICATION_YANG_DATA_JSON,
147 MediaTypes.APPLICATION_YANG_DATA_XML,
148 MediaType.APPLICATION_JSON,
149 MediaType.APPLICATION_XML,
152 public Response readData(@Encoded @PathParam("identifier") final String identifier,
153 @Context final UriInfo uriInfo) {
154 final var readParams = QueryParams.newReadDataParams(uriInfo);
155 return readData(server.bindRequestPath(identifier), readParams);
158 private Response readData(final InstanceIdentifierContext reqPath, final ReadDataParams readParams) {
159 final var queryParams = QueryParams.newQueryParameters(readParams, reqPath);
160 final var fieldPaths = queryParams.fieldPaths();
161 final var strategy = server.getRestconfStrategy(reqPath.getSchemaContext(), reqPath.getMountPoint());
162 final NormalizedNode node;
163 if (fieldPaths != null && !fieldPaths.isEmpty()) {
164 node = strategy.readData(readParams.content(), reqPath.getInstanceIdentifier(),
165 readParams.withDefaults(), fieldPaths);
167 node = strategy.readData(readParams.content(), reqPath.getInstanceIdentifier(),
168 readParams.withDefaults());
171 throw new RestconfDocumentedException(
172 "Request could not be completed because the relevant data model content does not exist",
173 ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
176 return switch (readParams.content()) {
177 case ALL, CONFIG -> {
178 final QName type = node.name().getNodeType();
179 yield Response.status(Status.OK)
180 .entity(new NormalizedNodePayload(reqPath.inference(), node, queryParams))
181 .header("ETag", '"' + type.getModule().getRevision().map(Revision::toString).orElse(null) + "-"
182 + type.getLocalName() + '"')
183 .header("Last-Modified", FORMATTER.format(LocalDateTime.now(Clock.systemUTC())))
186 case NONCONFIG -> Response.status(Status.OK)
187 .entity(new NormalizedNodePayload(reqPath.inference(), node, queryParams))
193 * Replace the data store.
195 * @param uriInfo request URI information
196 * @param body data node for put to config DS
197 * @param ar {@link AsyncResponse} which needs to be completed
202 MediaTypes.APPLICATION_YANG_DATA_JSON,
203 MediaType.APPLICATION_JSON,
205 public void putDataJSON(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
206 try (var jsonBody = new JsonResourceBody(body)) {
207 putData(null, uriInfo, jsonBody, ar);
212 * Create or replace the target data resource.
214 * @param identifier path to target
215 * @param uriInfo request URI information
216 * @param body data node for put to config DS
217 * @param ar {@link AsyncResponse} which needs to be completed
220 @Path("/data/{identifier:.+}")
222 MediaTypes.APPLICATION_YANG_DATA_JSON,
223 MediaType.APPLICATION_JSON,
225 public void putDataJSON(@Encoded @PathParam("identifier") final String identifier,
226 @Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
227 try (var jsonBody = new JsonResourceBody(body)) {
228 putData(identifier, uriInfo, jsonBody, ar);
233 * Replace the data store.
235 * @param uriInfo request URI information
236 * @param body data node for put to config DS
237 * @param ar {@link AsyncResponse} which needs to be completed
242 MediaTypes.APPLICATION_YANG_DATA_XML,
243 MediaType.APPLICATION_XML,
246 public void putDataXML(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
247 try (var xmlBody = new XmlResourceBody(body)) {
248 putData(null, uriInfo, xmlBody, ar);
253 * Create or replace the target data resource.
255 * @param identifier path to target
256 * @param uriInfo request URI information
257 * @param body data node for put to config DS
258 * @param ar {@link AsyncResponse} which needs to be completed
261 @Path("/data/{identifier:.+}")
263 MediaTypes.APPLICATION_YANG_DATA_XML,
264 MediaType.APPLICATION_XML,
267 public void putDataXML(@Encoded @PathParam("identifier") final String identifier,
268 @Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
269 try (var xmlBody = new XmlResourceBody(body)) {
270 putData(identifier, uriInfo, xmlBody, ar);
274 private void putData(final @Nullable String identifier, final UriInfo uriInfo, final ResourceBody body,
275 final AsyncResponse ar) {
276 final var reqPath = server.bindRequestPath(identifier);
277 final var insert = QueryParams.parseInsert(reqPath.getSchemaContext(), uriInfo);
278 final var req = bindResourceRequest(reqPath, body);
280 req.strategy().putData(req.path(), req.data(), insert).addCallback(new JaxRsRestconfCallback<>(ar) {
282 Response transform(final CreateOrReplaceResult result) {
283 return switch (result) {
284 // Note: no Location header, as it matches the request path
285 case CREATED -> Response.status(Status.CREATED).build();
286 case REPLACED -> Response.noContent().build();
293 * Create a top-level data resource.
295 * @param body data node for put to config DS
296 * @param uriInfo URI info
297 * @param ar {@link AsyncResponse} which needs to be completed
302 MediaTypes.APPLICATION_YANG_DATA_JSON,
303 MediaType.APPLICATION_JSON,
305 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
306 @Suspended final AsyncResponse ar) {
307 try (var jsonBody = new JsonChildBody(body)) {
308 postData(jsonBody, uriInfo, ar);
313 * Create a data resource in target.
315 * @param identifier path to target
316 * @param body data node for put to config DS
317 * @param uriInfo URI info
318 * @param ar {@link AsyncResponse} which needs to be completed
321 @Path("/data/{identifier:.+}")
323 MediaTypes.APPLICATION_YANG_DATA_JSON,
324 MediaType.APPLICATION_JSON,
326 public void postDataJSON(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
327 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
328 final var reqPath = server.bindRequestPath(identifier);
329 if (reqPath.getSchemaNode() instanceof ActionDefinition) {
330 try (var jsonBody = new JsonOperationInputBody(body)) {
331 invokeAction(reqPath, jsonBody, ar);
334 try (var jsonBody = new JsonChildBody(body)) {
335 postData(reqPath.inference(), reqPath.getInstanceIdentifier(), jsonBody, uriInfo,
336 reqPath.getMountPoint(), ar);
342 * Create a top-level data resource.
344 * @param body data node for put to config DS
345 * @param uriInfo URI info
346 * @param ar {@link AsyncResponse} which needs to be completed
351 MediaTypes.APPLICATION_YANG_DATA_XML,
352 MediaType.APPLICATION_XML,
355 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
356 try (var xmlBody = new XmlChildBody(body)) {
357 postData(xmlBody, uriInfo, ar);
362 * Create a data resource in target.
364 * @param identifier path to target
365 * @param body data node for put to config DS
366 * @param uriInfo URI info
367 * @param ar {@link AsyncResponse} which needs to be completed
370 @Path("/data/{identifier:.+}")
372 MediaTypes.APPLICATION_YANG_DATA_XML,
373 MediaType.APPLICATION_XML,
376 public void postDataXML(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
377 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
378 final var reqPath = server.bindRequestPath(identifier);
379 if (reqPath.getSchemaNode() instanceof ActionDefinition) {
380 try (var xmlBody = new XmlOperationInputBody(body)) {
381 invokeAction(reqPath, xmlBody, ar);
384 try (var xmlBody = new XmlChildBody(body)) {
385 postData(reqPath.inference(), reqPath.getInstanceIdentifier(), xmlBody, uriInfo,
386 reqPath.getMountPoint(), ar);
391 private void postData(final ChildBody body, final UriInfo uriInfo, final AsyncResponse ar) {
392 postData(Inference.ofDataTreePath(databindProvider.currentContext().modelContext()),
393 YangInstanceIdentifier.of(), body, uriInfo, null, ar);
396 private void postData(final Inference inference, final YangInstanceIdentifier parentPath, final ChildBody body,
397 final UriInfo uriInfo, final @Nullable DOMMountPoint mountPoint, final AsyncResponse ar) {
398 final var modelContext = inference.getEffectiveModelContext();
399 final var insert = QueryParams.parseInsert(modelContext, uriInfo);
400 final var strategy = server.getRestconfStrategy(modelContext, mountPoint);
401 final var payload = body.toPayload(parentPath, inference);
402 final var data = payload.body();
403 final var path = concat(parentPath, payload.prefix());
405 strategy.postData(path, data, insert).addCallback(new JaxRsRestconfCallback<>(ar) {
407 Response transform(final Empty result) {
408 return Response.created(resolveLocation(uriInfo, path, modelContext, data)).build();
413 private static YangInstanceIdentifier concat(final YangInstanceIdentifier parent, final List<PathArgument> args) {
415 for (var arg : args) {
422 * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
424 * @param uriInfo uri info
425 * @param initialPath data path
426 * @param schemaContext reference to {@link SchemaContext}
427 * @return {@link URI}
429 private static URI resolveLocation(final UriInfo uriInfo, final YangInstanceIdentifier initialPath,
430 final EffectiveModelContext schemaContext, final NormalizedNode data) {
431 YangInstanceIdentifier path = initialPath;
432 if (data instanceof MapNode mapData) {
433 final var children = mapData.body();
434 if (!children.isEmpty()) {
435 path = path.node(children.iterator().next().name());
439 return uriInfo.getBaseUriBuilder().path("data").path(IdentifierCodec.serialize(path, schemaContext)).build();
443 * Delete the target data resource.
445 * @param identifier path to target
446 * @param ar {@link AsyncResponse} which needs to be completed
449 @Path("/data/{identifier:.+}")
450 public void deleteData(@Encoded @PathParam("identifier") final String identifier,
451 @Suspended final AsyncResponse ar) {
452 final var reqPath = server.bindRequestPath(identifier);
453 final var strategy = server.getRestconfStrategy(reqPath.getSchemaContext(), reqPath.getMountPoint());
455 strategy.delete(reqPath.getInstanceIdentifier()).addCallback(new JaxRsRestconfCallback<>(ar) {
457 Response transform(final Empty result) {
458 return Response.noContent().build();
464 * Partially modify the target data store, as defined in
465 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
467 * @param body data node for put to config DS
468 * @param ar {@link AsyncResponse} which needs to be completed
473 MediaTypes.APPLICATION_YANG_DATA_XML,
474 MediaType.APPLICATION_XML,
477 public void plainPatchDataXML(final InputStream body, @Suspended final AsyncResponse ar) {
478 try (var xmlBody = new XmlResourceBody(body)) {
479 plainPatchData(xmlBody, ar);
484 * Partially modify the target data resource, as defined in
485 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
487 * @param identifier path to target
488 * @param body data node for put to config DS
489 * @param ar {@link AsyncResponse} which needs to be completed
492 @Path("/data/{identifier:.+}")
494 MediaTypes.APPLICATION_YANG_DATA_XML,
495 MediaType.APPLICATION_XML,
498 public void plainPatchDataXML(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
499 @Suspended final AsyncResponse ar) {
500 try (var xmlBody = new XmlResourceBody(body)) {
501 plainPatchData(identifier, xmlBody, ar);
506 * Partially modify the target data store, as defined in
507 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
509 * @param body data node for put to config DS
510 * @param ar {@link AsyncResponse} which needs to be completed
515 MediaTypes.APPLICATION_YANG_DATA_JSON,
516 MediaType.APPLICATION_JSON,
518 public void plainPatchDataJSON(final InputStream body, @Suspended final AsyncResponse ar) {
519 try (var jsonBody = new JsonResourceBody(body)) {
520 plainPatchData(jsonBody, ar);
525 * Partially modify the target data resource, as defined in
526 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
528 * @param identifier path to target
529 * @param body data node for put to config DS
530 * @param ar {@link AsyncResponse} which needs to be completed
533 @Path("/data/{identifier:.+}")
535 MediaTypes.APPLICATION_YANG_DATA_JSON,
536 MediaType.APPLICATION_JSON,
538 public void plainPatchDataJSON(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
539 @Suspended final AsyncResponse ar) {
540 try (var jsonBody = new JsonResourceBody(body)) {
541 plainPatchData(identifier, jsonBody, ar);
546 * Partially modify the target data resource, as defined in
547 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
549 * @param body data node for put to config DS
550 * @param ar {@link AsyncResponse} which needs to be completed
552 private void plainPatchData(final ResourceBody body, final AsyncResponse ar) {
553 plainPatchData(server.bindRequestRoot(), body, ar);
557 * Partially modify the target data resource, as defined in
558 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
560 * @param identifier path to target
561 * @param body data node for put to config DS
562 * @param ar {@link AsyncResponse} which needs to be completed
564 private void plainPatchData(final String identifier, final ResourceBody body, final AsyncResponse ar) {
565 plainPatchData(server.bindRequestPath(identifier), body, ar);
569 * Partially modify the target data resource, as defined in
570 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
572 * @param reqPath path to target
573 * @param body data node for put to config DS
574 * @param ar {@link AsyncResponse} which needs to be completed
576 private void plainPatchData(final InstanceIdentifierContext reqPath, final ResourceBody body,
577 final AsyncResponse ar) {
578 final var req = bindResourceRequest(reqPath, body);
579 req.strategy().merge(req.path(), req.data()).addCallback(new JaxRsRestconfCallback<>(ar) {
581 Response transform(final Empty result) {
582 return Response.ok().build();
587 private @NonNull ResourceRequest bindResourceRequest(final InstanceIdentifierContext reqPath,
588 final ResourceBody body) {
589 final var inference = reqPath.inference();
590 final var path = reqPath.getInstanceIdentifier();
591 final var data = body.toNormalizedNode(path, inference, reqPath.getSchemaNode());
593 return new ResourceRequest(
594 server.getRestconfStrategy(inference.getEffectiveModelContext(), reqPath.getMountPoint()),
599 * Ordered list of edits that are applied to the target datastore by the server, as defined in
600 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
602 * @param identifier path to target
603 * @param body YANG Patch body
604 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
607 @Path("/data/{identifier:.+}")
608 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
610 MediaTypes.APPLICATION_YANG_DATA_JSON,
611 MediaTypes.APPLICATION_YANG_DATA_XML
613 public void yangPatchDataXML(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
614 @Suspended final AsyncResponse ar) {
615 try (var xmlBody = new XmlPatchBody(body)) {
616 yangPatchData(identifier, xmlBody, ar);
621 * Ordered list of edits that are applied to the datastore by the server, as defined in
622 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
624 * @param body YANG Patch body
625 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
629 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
631 MediaTypes.APPLICATION_YANG_DATA_JSON,
632 MediaTypes.APPLICATION_YANG_DATA_XML
634 public void yangPatchDataXML(final InputStream body, @Suspended final AsyncResponse ar) {
635 try (var xmlBody = new XmlPatchBody(body)) {
636 yangPatchData(xmlBody, ar);
641 * Ordered list of edits that are applied to the target datastore by the server, as defined in
642 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
644 * @param identifier path to target
645 * @param body YANG Patch body
646 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
649 @Path("/data/{identifier:.+}")
650 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
652 MediaTypes.APPLICATION_YANG_DATA_JSON,
653 MediaTypes.APPLICATION_YANG_DATA_XML
655 public void yangPatchDataJSON(@Encoded @PathParam("identifier") final String identifier,
656 final InputStream body, @Suspended final AsyncResponse ar) {
657 try (var jsonBody = new JsonPatchBody(body)) {
658 yangPatchData(identifier, jsonBody, ar);
663 * Ordered list of edits that are applied to the datastore by the server, as defined in
664 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
666 * @param body YANG Patch body
667 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
671 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
673 MediaTypes.APPLICATION_YANG_DATA_JSON,
674 MediaTypes.APPLICATION_YANG_DATA_XML
676 public void yangPatchDataJSON(final InputStream body, @Suspended final AsyncResponse ar) {
677 try (var jsonBody = new JsonPatchBody(body)) {
678 yangPatchData(jsonBody, ar);
682 private void yangPatchData(final @NonNull PatchBody body, final AsyncResponse ar) {
683 final var context = server.bindRequestRoot().getSchemaContext();
684 yangPatchData(context, parsePatchBody(context, YangInstanceIdentifier.of(), body), null, ar);
687 private void yangPatchData(final String identifier, final @NonNull PatchBody body,
688 final AsyncResponse ar) {
689 final var reqPath = server.bindRequestPath(identifier);
690 final var modelContext = reqPath.getSchemaContext();
691 yangPatchData(modelContext, parsePatchBody(modelContext, reqPath.getInstanceIdentifier(), body),
692 reqPath.getMountPoint(), ar);
696 void yangPatchData(final @NonNull EffectiveModelContext modelContext,
697 final @NonNull PatchContext patch, final @Nullable DOMMountPoint mountPoint, final AsyncResponse ar) {
698 server.getRestconfStrategy(modelContext, mountPoint).patchData(patch)
699 .addCallback(new JaxRsRestconfCallback<>(ar) {
701 Response transform(final PatchStatusContext result) {
702 return Response.status(getStatusCode(result)).entity(result).build();
707 private static Status getStatusCode(final PatchStatusContext result) {
710 } else if (result.globalErrors() == null || result.globalErrors().isEmpty()) {
711 return result.editCollection().stream()
712 .filter(patchStatus -> !patchStatus.isOk() && !patchStatus.getEditErrors().isEmpty())
714 .map(PatchStatusEntity::getEditErrors)
715 .flatMap(errors -> errors.stream().findFirst())
716 .map(error -> ErrorTags.statusOf(error.getErrorTag()))
717 .orElse(Status.INTERNAL_SERVER_ERROR);
719 final var error = result.globalErrors().iterator().next();
720 return ErrorTags.statusOf(error.getErrorTag());
724 private static @NonNull PatchContext parsePatchBody(final @NonNull EffectiveModelContext context,
725 final @NonNull YangInstanceIdentifier urlPath, final @NonNull PatchBody body) {
727 return body.toPatchContext(context, urlPath);
728 } catch (IOException e) {
729 LOG.debug("Error parsing YANG Patch input", e);
730 throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
731 ErrorTag.MALFORMED_MESSAGE, e);
736 * Invoke Action operation.
738 * @param payload {@link NormalizedNodePayload} - the body of the operation
739 * @param ar {@link AsyncResponse} which needs to be completed with a NormalizedNodePayload
741 private void invokeAction(final InstanceIdentifierContext reqPath, final OperationInputBody body,
742 final AsyncResponse ar) {
743 final var yangIIdContext = reqPath.getInstanceIdentifier();
744 final ContainerNode input;
746 input = body.toContainerNode(reqPath.inference());
747 } catch (IOException e) {
748 LOG.debug("Error reading input", e);
749 throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
750 ErrorTag.MALFORMED_MESSAGE, e);
753 final var mountPoint = reqPath.getMountPoint();
754 final var inference = reqPath.inference();
755 final var schemaPath = inference.toSchemaInferenceStack().toSchemaNodeIdentifier();
756 final var response = mountPoint != null ? invokeAction(input, schemaPath, yangIIdContext, mountPoint)
757 : invokeAction(input, schemaPath, yangIIdContext, actionService);
759 response.addCallback(new JaxRsRestconfCallback<>(ar) {
761 Response transform(final DOMActionResult result) {
762 final var output = result.getOutput().orElse(null);
763 return output == null || output.isEmpty() ? Response.status(Status.NO_CONTENT).build()
764 : Response.status(Status.OK).entity(new NormalizedNodePayload(inference, output)).build();
770 * Invoking Action via mount point.
772 * @param mountPoint mount point
773 * @param data input data
774 * @param schemaPath schema path of data
775 * @return {@link DOMActionResult}
777 private static RestconfFuture<DOMActionResult> invokeAction(final ContainerNode data,
778 final Absolute schemaPath, final YangInstanceIdentifier yangIId, final DOMMountPoint mountPoint) {
779 final var actionService = mountPoint.getService(DOMActionService.class);
780 return actionService.isPresent() ? invokeAction(data, schemaPath, yangIId, actionService.orElseThrow())
781 : RestconfFuture.failed(new RestconfDocumentedException("DOMActionService is missing."));
785 * Invoke Action via ActionServiceHandler.
787 * @param data input data
788 * @param yangIId invocation context
789 * @param schemaPath schema path of data
790 * @param actionService action service to invoke action
791 * @return {@link DOMActionResult}
793 private static RestconfFuture<DOMActionResult> invokeAction(final ContainerNode data, final Absolute schemaPath,
794 final YangInstanceIdentifier yangIId, final DOMActionService actionService) {
795 final var ret = new SettableRestconfFuture<DOMActionResult>();
797 Futures.addCallback(actionService.invokeAction(schemaPath,
798 new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, yangIId.getParent()), data),
799 new FutureCallback<DOMActionResult>() {
801 public void onSuccess(final DOMActionResult result) {
802 final var errors = result.getErrors();
803 LOG.debug("InvokeAction Error Message {}", errors);
804 if (errors.isEmpty()) {
807 ret.setFailure(new RestconfDocumentedException("InvokeAction Error Message ", null, errors));
812 public void onFailure(final Throwable cause) {
813 if (cause instanceof DOMActionException) {
814 ret.set(new SimpleDOMActionResult(List.of(RpcResultBuilder.newError(
815 ErrorType.RPC, ErrorTag.OPERATION_FAILED, cause.getMessage()))));
816 } else if (cause instanceof RestconfDocumentedException e) {
818 } else if (cause instanceof CancellationException) {
819 ret.setFailure(new RestconfDocumentedException("Action cancelled while executing",
820 ErrorType.RPC, ErrorTag.PARTIAL_OPERATION, cause));
822 ret.setFailure(new RestconfDocumentedException("Invocation failed", cause));
825 }, MoreExecutors.directExecutor());