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 java.io.IOException;
14 import java.io.InputStream;
16 import java.time.Clock;
17 import java.time.LocalDateTime;
18 import java.time.format.DateTimeFormatter;
19 import java.util.List;
20 import javax.ws.rs.Consumes;
21 import javax.ws.rs.DELETE;
22 import javax.ws.rs.Encoded;
23 import javax.ws.rs.GET;
24 import javax.ws.rs.PATCH;
25 import javax.ws.rs.POST;
26 import javax.ws.rs.PUT;
27 import javax.ws.rs.Path;
28 import javax.ws.rs.PathParam;
29 import javax.ws.rs.Produces;
30 import javax.ws.rs.container.AsyncResponse;
31 import javax.ws.rs.container.Suspended;
32 import javax.ws.rs.core.Context;
33 import javax.ws.rs.core.MediaType;
34 import javax.ws.rs.core.Response;
35 import javax.ws.rs.core.Response.Status;
36 import javax.ws.rs.core.UriInfo;
37 import org.eclipse.jdt.annotation.NonNull;
38 import org.eclipse.jdt.annotation.Nullable;
39 import org.opendaylight.mdsal.dom.api.DOMActionResult;
40 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
41 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
42 import org.opendaylight.restconf.common.patch.PatchContext;
43 import org.opendaylight.restconf.common.patch.PatchStatusContext;
44 import org.opendaylight.restconf.common.patch.PatchStatusEntity;
45 import org.opendaylight.restconf.nb.rfc8040.MediaTypes;
46 import org.opendaylight.restconf.nb.rfc8040.ReadDataParams;
47 import org.opendaylight.restconf.nb.rfc8040.databind.ChildBody;
48 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
49 import org.opendaylight.restconf.nb.rfc8040.databind.JsonChildBody;
50 import org.opendaylight.restconf.nb.rfc8040.databind.JsonOperationInputBody;
51 import org.opendaylight.restconf.nb.rfc8040.databind.JsonPatchBody;
52 import org.opendaylight.restconf.nb.rfc8040.databind.JsonResourceBody;
53 import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
54 import org.opendaylight.restconf.nb.rfc8040.databind.PatchBody;
55 import org.opendaylight.restconf.nb.rfc8040.databind.ResourceBody;
56 import org.opendaylight.restconf.nb.rfc8040.databind.XmlChildBody;
57 import org.opendaylight.restconf.nb.rfc8040.databind.XmlOperationInputBody;
58 import org.opendaylight.restconf.nb.rfc8040.databind.XmlPatchBody;
59 import org.opendaylight.restconf.nb.rfc8040.databind.XmlResourceBody;
60 import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
61 import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
62 import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
63 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
64 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy.CreateOrReplaceResult;
65 import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
66 import org.opendaylight.yangtools.yang.common.Empty;
67 import org.opendaylight.yangtools.yang.common.ErrorTag;
68 import org.opendaylight.yangtools.yang.common.ErrorType;
69 import org.opendaylight.yangtools.yang.common.QName;
70 import org.opendaylight.yangtools.yang.common.Revision;
71 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
72 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
73 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
74 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
75 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
76 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
77 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
78 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
79 import org.slf4j.Logger;
80 import org.slf4j.LoggerFactory;
83 * The "{+restconf}/data" subtree represents the datastore resource type, which is a collection of configuration data
84 * and state data nodes.
87 public final class RestconfDataServiceImpl {
88 private static final Logger LOG = LoggerFactory.getLogger(RestconfDataServiceImpl.class);
89 private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss");
91 private final DatabindProvider databindProvider;
92 private final MdsalRestconfServer server;
94 public RestconfDataServiceImpl(final DatabindProvider databindProvider, final MdsalRestconfServer server) {
95 this.databindProvider = requireNonNull(databindProvider);
96 this.server = requireNonNull(server);
100 * Get target data resource from data root.
102 * @param uriInfo URI info
103 * @return {@link NormalizedNodePayload}
108 MediaTypes.APPLICATION_YANG_DATA_JSON,
109 MediaTypes.APPLICATION_YANG_DATA_XML,
110 MediaType.APPLICATION_JSON,
111 MediaType.APPLICATION_XML,
114 public Response readData(@Context final UriInfo uriInfo) {
115 final var readParams = QueryParams.newReadDataParams(uriInfo);
116 return readData(server.bindRequestRoot(), readParams);
120 * Get target data resource.
122 * @param identifier path to target
123 * @param uriInfo URI info
124 * @return {@link NormalizedNodePayload}
127 @Path("/data/{identifier:.+}")
129 MediaTypes.APPLICATION_YANG_DATA_JSON,
130 MediaTypes.APPLICATION_YANG_DATA_XML,
131 MediaType.APPLICATION_JSON,
132 MediaType.APPLICATION_XML,
135 public Response readData(@Encoded @PathParam("identifier") final String identifier,
136 @Context final UriInfo uriInfo) {
137 final var readParams = QueryParams.newReadDataParams(uriInfo);
138 return readData(server.bindRequestPath(identifier), readParams);
141 private Response readData(final InstanceIdentifierContext reqPath, final ReadDataParams readParams) {
142 final var queryParams = QueryParams.newQueryParameters(readParams, reqPath);
143 final var fieldPaths = queryParams.fieldPaths();
144 final var strategy = server.getRestconfStrategy(reqPath.getSchemaContext(), reqPath.getMountPoint());
145 final NormalizedNode node;
146 if (fieldPaths != null && !fieldPaths.isEmpty()) {
147 node = strategy.readData(readParams.content(), reqPath.getInstanceIdentifier(),
148 readParams.withDefaults(), fieldPaths);
150 node = strategy.readData(readParams.content(), reqPath.getInstanceIdentifier(),
151 readParams.withDefaults());
154 throw new RestconfDocumentedException(
155 "Request could not be completed because the relevant data model content does not exist",
156 ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
159 return switch (readParams.content()) {
160 case ALL, CONFIG -> {
161 final QName type = node.name().getNodeType();
162 yield Response.status(Status.OK)
163 .entity(new NormalizedNodePayload(reqPath.inference(), node, queryParams))
164 .header("ETag", '"' + type.getModule().getRevision().map(Revision::toString).orElse(null) + "-"
165 + type.getLocalName() + '"')
166 .header("Last-Modified", FORMATTER.format(LocalDateTime.now(Clock.systemUTC())))
169 case NONCONFIG -> Response.status(Status.OK)
170 .entity(new NormalizedNodePayload(reqPath.inference(), node, queryParams))
176 * Replace the data store.
178 * @param uriInfo request URI information
179 * @param body data node for put to config DS
180 * @param ar {@link AsyncResponse} which needs to be completed
185 MediaTypes.APPLICATION_YANG_DATA_JSON,
186 MediaType.APPLICATION_JSON,
188 public void putDataJSON(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
189 try (var jsonBody = new JsonResourceBody(body)) {
190 putData(null, uriInfo, jsonBody, ar);
195 * Create or replace the target data resource.
197 * @param identifier path to target
198 * @param uriInfo request URI information
199 * @param body data node for put to config DS
200 * @param ar {@link AsyncResponse} which needs to be completed
203 @Path("/data/{identifier:.+}")
205 MediaTypes.APPLICATION_YANG_DATA_JSON,
206 MediaType.APPLICATION_JSON,
208 public void putDataJSON(@Encoded @PathParam("identifier") final String identifier,
209 @Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
210 try (var jsonBody = new JsonResourceBody(body)) {
211 putData(identifier, uriInfo, jsonBody, ar);
216 * Replace the data store.
218 * @param uriInfo request URI information
219 * @param body data node for put to config DS
220 * @param ar {@link AsyncResponse} which needs to be completed
225 MediaTypes.APPLICATION_YANG_DATA_XML,
226 MediaType.APPLICATION_XML,
229 public void putDataXML(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
230 try (var xmlBody = new XmlResourceBody(body)) {
231 putData(null, uriInfo, xmlBody, ar);
236 * Create or replace the target data resource.
238 * @param identifier path to target
239 * @param uriInfo request URI information
240 * @param body data node for put to config DS
241 * @param ar {@link AsyncResponse} which needs to be completed
244 @Path("/data/{identifier:.+}")
246 MediaTypes.APPLICATION_YANG_DATA_XML,
247 MediaType.APPLICATION_XML,
250 public void putDataXML(@Encoded @PathParam("identifier") final String identifier,
251 @Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
252 try (var xmlBody = new XmlResourceBody(body)) {
253 putData(identifier, uriInfo, xmlBody, ar);
257 private void putData(final @Nullable String identifier, final UriInfo uriInfo, final ResourceBody body,
258 final AsyncResponse ar) {
259 final var reqPath = server.bindRequestPath(identifier);
260 final var insert = QueryParams.parseInsert(reqPath.getSchemaContext(), uriInfo);
261 final var req = bindResourceRequest(reqPath, body);
263 req.strategy().putData(req.path(), req.data(), insert).addCallback(new JaxRsRestconfCallback<>(ar) {
265 Response transform(final CreateOrReplaceResult result) {
266 return switch (result) {
267 // Note: no Location header, as it matches the request path
268 case CREATED -> Response.status(Status.CREATED).build();
269 case REPLACED -> Response.noContent().build();
276 * Create a top-level data resource.
278 * @param body data node for put to config DS
279 * @param uriInfo URI info
280 * @param ar {@link AsyncResponse} which needs to be completed
285 MediaTypes.APPLICATION_YANG_DATA_JSON,
286 MediaType.APPLICATION_JSON,
288 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
289 @Suspended final AsyncResponse ar) {
290 try (var jsonBody = new JsonChildBody(body)) {
291 postData(jsonBody, uriInfo, ar);
296 * Create a data resource in target.
298 * @param identifier path to target
299 * @param body data node for put to config DS
300 * @param uriInfo URI info
301 * @param ar {@link AsyncResponse} which needs to be completed
304 @Path("/data/{identifier:.+}")
306 MediaTypes.APPLICATION_YANG_DATA_JSON,
307 MediaType.APPLICATION_JSON,
309 public void postDataJSON(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
310 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
311 final var reqPath = server.bindRequestPath(identifier);
312 if (reqPath.getSchemaNode() instanceof ActionDefinition) {
313 try (var jsonBody = new JsonOperationInputBody(body)) {
314 invokeAction(reqPath, jsonBody, ar);
317 try (var jsonBody = new JsonChildBody(body)) {
318 postData(reqPath.inference(), reqPath.getInstanceIdentifier(), jsonBody, uriInfo,
319 reqPath.getMountPoint(), ar);
325 * Create a top-level data resource.
327 * @param body data node for put to config DS
328 * @param uriInfo URI info
329 * @param ar {@link AsyncResponse} which needs to be completed
334 MediaTypes.APPLICATION_YANG_DATA_XML,
335 MediaType.APPLICATION_XML,
338 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
339 try (var xmlBody = new XmlChildBody(body)) {
340 postData(xmlBody, uriInfo, ar);
345 * Create a data resource in target.
347 * @param identifier path to target
348 * @param body data node for put to config DS
349 * @param uriInfo URI info
350 * @param ar {@link AsyncResponse} which needs to be completed
353 @Path("/data/{identifier:.+}")
355 MediaTypes.APPLICATION_YANG_DATA_XML,
356 MediaType.APPLICATION_XML,
359 public void postDataXML(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
360 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
361 final var reqPath = server.bindRequestPath(identifier);
362 if (reqPath.getSchemaNode() instanceof ActionDefinition) {
363 try (var xmlBody = new XmlOperationInputBody(body)) {
364 invokeAction(reqPath, xmlBody, ar);
367 try (var xmlBody = new XmlChildBody(body)) {
368 postData(reqPath.inference(), reqPath.getInstanceIdentifier(), xmlBody, uriInfo,
369 reqPath.getMountPoint(), ar);
374 private void postData(final ChildBody body, final UriInfo uriInfo, final AsyncResponse ar) {
375 postData(Inference.ofDataTreePath(databindProvider.currentContext().modelContext()),
376 YangInstanceIdentifier.of(), body, uriInfo, null, ar);
379 private void postData(final Inference inference, final YangInstanceIdentifier parentPath, final ChildBody body,
380 final UriInfo uriInfo, final @Nullable DOMMountPoint mountPoint, final AsyncResponse ar) {
381 final var modelContext = inference.getEffectiveModelContext();
382 final var insert = QueryParams.parseInsert(modelContext, uriInfo);
383 final var strategy = server.getRestconfStrategy(modelContext, mountPoint);
384 final var payload = body.toPayload(parentPath, inference);
385 final var data = payload.body();
386 final var path = concat(parentPath, payload.prefix());
388 strategy.postData(path, data, insert).addCallback(new JaxRsRestconfCallback<>(ar) {
390 Response transform(final Empty result) {
391 return Response.created(resolveLocation(uriInfo, path, modelContext, data)).build();
396 private static YangInstanceIdentifier concat(final YangInstanceIdentifier parent, final List<PathArgument> args) {
398 for (var arg : args) {
405 * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
407 * @param uriInfo uri info
408 * @param initialPath data path
409 * @param schemaContext reference to {@link SchemaContext}
410 * @return {@link URI}
412 private static URI resolveLocation(final UriInfo uriInfo, final YangInstanceIdentifier initialPath,
413 final EffectiveModelContext schemaContext, final NormalizedNode data) {
414 YangInstanceIdentifier path = initialPath;
415 if (data instanceof MapNode mapData) {
416 final var children = mapData.body();
417 if (!children.isEmpty()) {
418 path = path.node(children.iterator().next().name());
422 return uriInfo.getBaseUriBuilder().path("data").path(IdentifierCodec.serialize(path, schemaContext)).build();
426 * Delete the target data resource.
428 * @param identifier path to target
429 * @param ar {@link AsyncResponse} which needs to be completed
432 @Path("/data/{identifier:.+}")
433 public void deleteData(@Encoded @PathParam("identifier") final String identifier,
434 @Suspended final AsyncResponse ar) {
435 final var reqPath = server.bindRequestPath(identifier);
436 final var strategy = server.getRestconfStrategy(reqPath.getSchemaContext(), reqPath.getMountPoint());
438 strategy.delete(reqPath.getInstanceIdentifier()).addCallback(new JaxRsRestconfCallback<>(ar) {
440 Response transform(final Empty result) {
441 return Response.noContent().build();
447 * Partially modify the target data store, as defined in
448 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
450 * @param body data node for put to config DS
451 * @param ar {@link AsyncResponse} which needs to be completed
456 MediaTypes.APPLICATION_YANG_DATA_XML,
457 MediaType.APPLICATION_XML,
460 public void plainPatchDataXML(final InputStream body, @Suspended final AsyncResponse ar) {
461 try (var xmlBody = new XmlResourceBody(body)) {
462 plainPatchData(xmlBody, ar);
467 * Partially modify the target data resource, as defined in
468 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
470 * @param identifier path to target
471 * @param body data node for put to config DS
472 * @param ar {@link AsyncResponse} which needs to be completed
475 @Path("/data/{identifier:.+}")
477 MediaTypes.APPLICATION_YANG_DATA_XML,
478 MediaType.APPLICATION_XML,
481 public void plainPatchDataXML(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
482 @Suspended final AsyncResponse ar) {
483 try (var xmlBody = new XmlResourceBody(body)) {
484 plainPatchData(identifier, xmlBody, ar);
489 * Partially modify the target data store, as defined in
490 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
492 * @param body data node for put to config DS
493 * @param ar {@link AsyncResponse} which needs to be completed
498 MediaTypes.APPLICATION_YANG_DATA_JSON,
499 MediaType.APPLICATION_JSON,
501 public void plainPatchDataJSON(final InputStream body, @Suspended final AsyncResponse ar) {
502 try (var jsonBody = new JsonResourceBody(body)) {
503 plainPatchData(jsonBody, ar);
508 * Partially modify the target data resource, as defined in
509 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
511 * @param identifier path to target
512 * @param body data node for put to config DS
513 * @param ar {@link AsyncResponse} which needs to be completed
516 @Path("/data/{identifier:.+}")
518 MediaTypes.APPLICATION_YANG_DATA_JSON,
519 MediaType.APPLICATION_JSON,
521 public void plainPatchDataJSON(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
522 @Suspended final AsyncResponse ar) {
523 try (var jsonBody = new JsonResourceBody(body)) {
524 plainPatchData(identifier, jsonBody, ar);
529 * Partially modify the target data resource, as defined in
530 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
532 * @param body data node for put to config DS
533 * @param ar {@link AsyncResponse} which needs to be completed
535 private void plainPatchData(final ResourceBody body, final AsyncResponse ar) {
536 plainPatchData(server.bindRequestRoot(), body, ar);
540 * Partially modify the target data resource, as defined in
541 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
543 * @param identifier path to target
544 * @param body data node for put to config DS
545 * @param ar {@link AsyncResponse} which needs to be completed
547 private void plainPatchData(final String identifier, final ResourceBody body, final AsyncResponse ar) {
548 plainPatchData(server.bindRequestPath(identifier), body, ar);
552 * Partially modify the target data resource, as defined in
553 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
555 * @param reqPath path to target
556 * @param body data node for put to config DS
557 * @param ar {@link AsyncResponse} which needs to be completed
559 private void plainPatchData(final InstanceIdentifierContext reqPath, final ResourceBody body,
560 final AsyncResponse ar) {
561 final var req = bindResourceRequest(reqPath, body);
562 req.strategy().merge(req.path(), req.data()).addCallback(new JaxRsRestconfCallback<>(ar) {
564 Response transform(final Empty result) {
565 return Response.ok().build();
570 private @NonNull ResourceRequest bindResourceRequest(final InstanceIdentifierContext reqPath,
571 final ResourceBody body) {
572 final var inference = reqPath.inference();
573 final var path = reqPath.getInstanceIdentifier();
574 final var data = body.toNormalizedNode(path, inference, reqPath.getSchemaNode());
576 return new ResourceRequest(
577 server.getRestconfStrategy(inference.getEffectiveModelContext(), reqPath.getMountPoint()),
582 * Ordered list of edits that are applied to the target datastore by the server, as defined in
583 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
585 * @param identifier path to target
586 * @param body YANG Patch body
587 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
590 @Path("/data/{identifier:.+}")
591 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
593 MediaTypes.APPLICATION_YANG_DATA_JSON,
594 MediaTypes.APPLICATION_YANG_DATA_XML
596 public void yangPatchDataXML(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
597 @Suspended final AsyncResponse ar) {
598 try (var xmlBody = new XmlPatchBody(body)) {
599 yangPatchData(identifier, xmlBody, ar);
604 * Ordered list of edits that are applied to the datastore by the server, as defined in
605 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
607 * @param body YANG Patch body
608 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
612 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
614 MediaTypes.APPLICATION_YANG_DATA_JSON,
615 MediaTypes.APPLICATION_YANG_DATA_XML
617 public void yangPatchDataXML(final InputStream body, @Suspended final AsyncResponse ar) {
618 try (var xmlBody = new XmlPatchBody(body)) {
619 yangPatchData(xmlBody, ar);
624 * Ordered list of edits that are applied to the target datastore by the server, as defined in
625 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
627 * @param identifier path to target
628 * @param body YANG Patch body
629 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
632 @Path("/data/{identifier:.+}")
633 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
635 MediaTypes.APPLICATION_YANG_DATA_JSON,
636 MediaTypes.APPLICATION_YANG_DATA_XML
638 public void yangPatchDataJSON(@Encoded @PathParam("identifier") final String identifier,
639 final InputStream body, @Suspended final AsyncResponse ar) {
640 try (var jsonBody = new JsonPatchBody(body)) {
641 yangPatchData(identifier, jsonBody, ar);
646 * Ordered list of edits that are applied to the datastore by the server, as defined in
647 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
649 * @param body YANG Patch body
650 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
654 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
656 MediaTypes.APPLICATION_YANG_DATA_JSON,
657 MediaTypes.APPLICATION_YANG_DATA_XML
659 public void yangPatchDataJSON(final InputStream body, @Suspended final AsyncResponse ar) {
660 try (var jsonBody = new JsonPatchBody(body)) {
661 yangPatchData(jsonBody, ar);
665 private void yangPatchData(final @NonNull PatchBody body, final AsyncResponse ar) {
666 final var context = server.bindRequestRoot().getSchemaContext();
667 yangPatchData(context, parsePatchBody(context, YangInstanceIdentifier.of(), body), null, ar);
670 private void yangPatchData(final String identifier, final @NonNull PatchBody body,
671 final AsyncResponse ar) {
672 final var reqPath = server.bindRequestPath(identifier);
673 final var modelContext = reqPath.getSchemaContext();
674 yangPatchData(modelContext, parsePatchBody(modelContext, reqPath.getInstanceIdentifier(), body),
675 reqPath.getMountPoint(), ar);
679 void yangPatchData(final @NonNull EffectiveModelContext modelContext,
680 final @NonNull PatchContext patch, final @Nullable DOMMountPoint mountPoint, final AsyncResponse ar) {
681 server.getRestconfStrategy(modelContext, mountPoint).patchData(patch)
682 .addCallback(new JaxRsRestconfCallback<>(ar) {
684 Response transform(final PatchStatusContext result) {
685 return Response.status(getStatusCode(result)).entity(result).build();
690 private static Status getStatusCode(final PatchStatusContext result) {
693 } else if (result.globalErrors() == null || result.globalErrors().isEmpty()) {
694 return result.editCollection().stream()
695 .filter(patchStatus -> !patchStatus.isOk() && !patchStatus.getEditErrors().isEmpty())
697 .map(PatchStatusEntity::getEditErrors)
698 .flatMap(errors -> errors.stream().findFirst())
699 .map(error -> ErrorTags.statusOf(error.getErrorTag()))
700 .orElse(Status.INTERNAL_SERVER_ERROR);
702 final var error = result.globalErrors().iterator().next();
703 return ErrorTags.statusOf(error.getErrorTag());
707 private static @NonNull PatchContext parsePatchBody(final @NonNull EffectiveModelContext context,
708 final @NonNull YangInstanceIdentifier urlPath, final @NonNull PatchBody body) {
710 return body.toPatchContext(context, urlPath);
711 } catch (IOException e) {
712 LOG.debug("Error parsing YANG Patch input", e);
713 throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
714 ErrorTag.MALFORMED_MESSAGE, e);
719 * Invoke Action operation.
721 * @param payload {@link NormalizedNodePayload} - the body of the operation
722 * @param ar {@link AsyncResponse} which needs to be completed with a NormalizedNodePayload
724 private void invokeAction(final InstanceIdentifierContext reqPath, final OperationInputBody body,
725 final AsyncResponse ar) {
726 server.dataInvokePOST(reqPath, body).addCallback(new JaxRsRestconfCallback<>(ar) {
728 Response transform(final DOMActionResult result) {
729 final var output = result.getOutput().orElse(null);
730 return output == null || output.isEmpty() ? Response.status(Status.NO_CONTENT).build()
731 : Response.status(Status.OK).entity(new NormalizedNodePayload(reqPath.inference(), output)).build();