8d2c466f2ab4204a47a9d3611468febe4287e709
[netconf.git] / restconf / restconf-nb / 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
12 import com.google.common.annotations.VisibleForTesting;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.net.URI;
16 import java.util.List;
17 import javax.ws.rs.Consumes;
18 import javax.ws.rs.DELETE;
19 import javax.ws.rs.Encoded;
20 import javax.ws.rs.PATCH;
21 import javax.ws.rs.POST;
22 import javax.ws.rs.PUT;
23 import javax.ws.rs.Path;
24 import javax.ws.rs.PathParam;
25 import javax.ws.rs.Produces;
26 import javax.ws.rs.container.AsyncResponse;
27 import javax.ws.rs.container.Suspended;
28 import javax.ws.rs.core.Context;
29 import javax.ws.rs.core.MediaType;
30 import javax.ws.rs.core.Response;
31 import javax.ws.rs.core.Response.Status;
32 import javax.ws.rs.core.UriInfo;
33 import org.eclipse.jdt.annotation.NonNull;
34 import org.eclipse.jdt.annotation.Nullable;
35 import org.opendaylight.mdsal.dom.api.DOMActionResult;
36 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
37 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
38 import org.opendaylight.restconf.common.patch.PatchContext;
39 import org.opendaylight.restconf.common.patch.PatchStatusContext;
40 import org.opendaylight.restconf.common.patch.PatchStatusEntity;
41 import org.opendaylight.restconf.nb.rfc8040.MediaTypes;
42 import org.opendaylight.restconf.nb.rfc8040.databind.ChildBody;
43 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
44 import org.opendaylight.restconf.nb.rfc8040.databind.JsonChildBody;
45 import org.opendaylight.restconf.nb.rfc8040.databind.JsonOperationInputBody;
46 import org.opendaylight.restconf.nb.rfc8040.databind.JsonPatchBody;
47 import org.opendaylight.restconf.nb.rfc8040.databind.JsonResourceBody;
48 import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
49 import org.opendaylight.restconf.nb.rfc8040.databind.PatchBody;
50 import org.opendaylight.restconf.nb.rfc8040.databind.ResourceBody;
51 import org.opendaylight.restconf.nb.rfc8040.databind.XmlChildBody;
52 import org.opendaylight.restconf.nb.rfc8040.databind.XmlOperationInputBody;
53 import org.opendaylight.restconf.nb.rfc8040.databind.XmlPatchBody;
54 import org.opendaylight.restconf.nb.rfc8040.databind.XmlResourceBody;
55 import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
56 import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
57 import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
58 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
59 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy.CreateOrReplaceResult;
60 import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
61 import org.opendaylight.yangtools.yang.common.Empty;
62 import org.opendaylight.yangtools.yang.common.ErrorTag;
63 import org.opendaylight.yangtools.yang.common.ErrorType;
64 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
65 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
66 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
67 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
68 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
69 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
70 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
71 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74
75 /**
76  * The "{+restconf}/data" subtree represents the datastore resource type, which is a collection of configuration data
77  * and state data nodes.
78  */
79 @Path("/")
80 public final class RestconfDataServiceImpl {
81     private static final Logger LOG = LoggerFactory.getLogger(RestconfDataServiceImpl.class);
82
83     private final DatabindProvider databindProvider;
84     private final MdsalRestconfServer server;
85
86     public RestconfDataServiceImpl(final DatabindProvider databindProvider, final MdsalRestconfServer server) {
87         this.databindProvider = requireNonNull(databindProvider);
88         this.server = requireNonNull(server);
89     }
90
91     /**
92      * Replace the data store.
93      *
94      * @param uriInfo request URI information
95      * @param body data node for put to config DS
96      * @param ar {@link AsyncResponse} which needs to be completed
97      */
98     @PUT
99     @Path("/data")
100     @Consumes({
101         MediaTypes.APPLICATION_YANG_DATA_JSON,
102         MediaType.APPLICATION_JSON,
103     })
104     public void putDataJSON(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
105         try (var jsonBody = new JsonResourceBody(body)) {
106             putData(null, uriInfo, jsonBody, ar);
107         }
108     }
109
110     /**
111      * Create or replace the target data resource.
112      *
113      * @param identifier path to target
114      * @param uriInfo request URI information
115      * @param body data node for put to config DS
116      * @param ar {@link AsyncResponse} which needs to be completed
117      */
118     @PUT
119     @Path("/data/{identifier:.+}")
120     @Consumes({
121         MediaTypes.APPLICATION_YANG_DATA_JSON,
122         MediaType.APPLICATION_JSON,
123     })
124     public void putDataJSON(@Encoded @PathParam("identifier") final String identifier,
125             @Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
126         try (var jsonBody = new JsonResourceBody(body)) {
127             putData(identifier, uriInfo, jsonBody, ar);
128         }
129     }
130
131     /**
132      * Replace the data store.
133      *
134      * @param uriInfo request URI information
135      * @param body data node for put to config DS
136      * @param ar {@link AsyncResponse} which needs to be completed
137      */
138     @PUT
139     @Path("/data")
140     @Consumes({
141         MediaTypes.APPLICATION_YANG_DATA_XML,
142         MediaType.APPLICATION_XML,
143         MediaType.TEXT_XML
144     })
145     public void putDataXML(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
146         try (var xmlBody = new XmlResourceBody(body)) {
147             putData(null, uriInfo, xmlBody, ar);
148         }
149     }
150
151     /**
152      * Create or replace the target data resource.
153      *
154      * @param identifier path to target
155      * @param uriInfo request URI information
156      * @param body data node for put to config DS
157      * @param ar {@link AsyncResponse} which needs to be completed
158      */
159     @PUT
160     @Path("/data/{identifier:.+}")
161     @Consumes({
162         MediaTypes.APPLICATION_YANG_DATA_XML,
163         MediaType.APPLICATION_XML,
164         MediaType.TEXT_XML
165     })
166     public void putDataXML(@Encoded @PathParam("identifier") final String identifier,
167             @Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
168         try (var xmlBody = new XmlResourceBody(body)) {
169             putData(identifier, uriInfo, xmlBody, ar);
170         }
171     }
172
173     private void putData(final @Nullable String identifier, final UriInfo uriInfo, final ResourceBody body,
174             final AsyncResponse ar) {
175         final var reqPath = server.bindRequestPath(identifier);
176         final var insert = QueryParams.parseInsert(reqPath.getSchemaContext(), uriInfo);
177         final var req = server.bindResourceRequest(reqPath, body);
178
179         req.strategy().putData(req.path(), req.data(), insert).addCallback(new JaxRsRestconfCallback<>(ar) {
180             @Override
181             Response transform(final CreateOrReplaceResult result) {
182                 return switch (result) {
183                     // Note: no Location header, as it matches the request path
184                     case CREATED -> Response.status(Status.CREATED).build();
185                     case REPLACED -> Response.noContent().build();
186                 };
187             }
188         });
189     }
190
191     /**
192      * Create a top-level data resource.
193      *
194      * @param body data node for put to config DS
195      * @param uriInfo URI info
196      * @param ar {@link AsyncResponse} which needs to be completed
197      */
198     @POST
199     @Path("/data")
200     @Consumes({
201         MediaTypes.APPLICATION_YANG_DATA_JSON,
202         MediaType.APPLICATION_JSON,
203     })
204     public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
205             @Suspended final AsyncResponse ar) {
206         try (var jsonBody = new JsonChildBody(body)) {
207             postData(jsonBody, uriInfo, ar);
208         }
209     }
210
211     /**
212      * Create a data resource in target.
213      *
214      * @param identifier path to target
215      * @param body data node for put to config DS
216      * @param uriInfo URI info
217      * @param ar {@link AsyncResponse} which needs to be completed
218      */
219     @POST
220     @Path("/data/{identifier:.+}")
221     @Consumes({
222         MediaTypes.APPLICATION_YANG_DATA_JSON,
223         MediaType.APPLICATION_JSON,
224     })
225     public void postDataJSON(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
226             @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
227         final var reqPath = server.bindRequestPath(identifier);
228         if (reqPath.getSchemaNode() instanceof ActionDefinition) {
229             try (var jsonBody = new JsonOperationInputBody(body)) {
230                 invokeAction(reqPath, jsonBody, ar);
231             }
232         } else {
233             try (var jsonBody = new JsonChildBody(body)) {
234                 postData(reqPath.inference(), reqPath.getInstanceIdentifier(), jsonBody, uriInfo,
235                     reqPath.getMountPoint(), ar);
236             }
237         }
238     }
239
240     /**
241      * Create a top-level data resource.
242      *
243      * @param body data node for put to config DS
244      * @param uriInfo URI info
245      * @param ar {@link AsyncResponse} which needs to be completed
246      */
247     @POST
248     @Path("/data")
249     @Consumes({
250         MediaTypes.APPLICATION_YANG_DATA_XML,
251         MediaType.APPLICATION_XML,
252         MediaType.TEXT_XML
253     })
254     public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
255         try (var xmlBody = new XmlChildBody(body)) {
256             postData(xmlBody, uriInfo, ar);
257         }
258     }
259
260     /**
261      * Create a data resource in target.
262      *
263      * @param identifier path to target
264      * @param body data node for put to config DS
265      * @param uriInfo URI info
266      * @param ar {@link AsyncResponse} which needs to be completed
267      */
268     @POST
269     @Path("/data/{identifier:.+}")
270     @Consumes({
271         MediaTypes.APPLICATION_YANG_DATA_XML,
272         MediaType.APPLICATION_XML,
273         MediaType.TEXT_XML
274     })
275     public void postDataXML(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
276             @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
277         final var reqPath = server.bindRequestPath(identifier);
278         if (reqPath.getSchemaNode() instanceof ActionDefinition) {
279             try (var xmlBody = new XmlOperationInputBody(body)) {
280                 invokeAction(reqPath, xmlBody, ar);
281             }
282         } else {
283             try (var xmlBody = new XmlChildBody(body)) {
284                 postData(reqPath.inference(), reqPath.getInstanceIdentifier(), xmlBody, uriInfo,
285                     reqPath.getMountPoint(), ar);
286             }
287         }
288     }
289
290     private void postData(final ChildBody body, final UriInfo uriInfo, final AsyncResponse ar) {
291         postData(Inference.ofDataTreePath(databindProvider.currentContext().modelContext()),
292             YangInstanceIdentifier.of(), body, uriInfo, null, ar);
293     }
294
295     private void postData(final Inference inference, final YangInstanceIdentifier parentPath, final ChildBody body,
296             final UriInfo uriInfo, final @Nullable DOMMountPoint mountPoint, final AsyncResponse ar) {
297         final var modelContext = inference.getEffectiveModelContext();
298         final var insert = QueryParams.parseInsert(modelContext, uriInfo);
299         final var strategy = server.getRestconfStrategy(modelContext, mountPoint);
300         final var payload = body.toPayload(parentPath, inference);
301         final var data = payload.body();
302         final var path = concat(parentPath, payload.prefix());
303
304         strategy.postData(path, data, insert).addCallback(new JaxRsRestconfCallback<>(ar) {
305             @Override
306             Response transform(final Empty result) {
307                 return Response.created(resolveLocation(uriInfo, path, modelContext, data)).build();
308             }
309         });
310     }
311
312     private static YangInstanceIdentifier concat(final YangInstanceIdentifier parent, final List<PathArgument> args) {
313         var ret = parent;
314         for (var arg : args) {
315             ret = ret.node(arg);
316         }
317         return ret;
318     }
319
320     /**
321      * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
322      *
323      * @param uriInfo       uri info
324      * @param initialPath   data path
325      * @param schemaContext reference to {@link SchemaContext}
326      * @return {@link URI}
327      */
328     private static URI resolveLocation(final UriInfo uriInfo, final YangInstanceIdentifier initialPath,
329                                        final EffectiveModelContext schemaContext, final NormalizedNode data) {
330         YangInstanceIdentifier path = initialPath;
331         if (data instanceof MapNode mapData) {
332             final var children = mapData.body();
333             if (!children.isEmpty()) {
334                 path = path.node(children.iterator().next().name());
335             }
336         }
337
338         return uriInfo.getBaseUriBuilder().path("data").path(IdentifierCodec.serialize(path, schemaContext)).build();
339     }
340
341     /**
342      * Delete the target data resource.
343      *
344      * @param identifier path to target
345      * @param ar {@link AsyncResponse} which needs to be completed
346      */
347     @DELETE
348     @Path("/data/{identifier:.+}")
349     public void deleteData(@Encoded @PathParam("identifier") final String identifier,
350             @Suspended final AsyncResponse ar) {
351         final var reqPath = server.bindRequestPath(identifier);
352         final var strategy = server.getRestconfStrategy(reqPath.getSchemaContext(), reqPath.getMountPoint());
353
354         strategy.delete(reqPath.getInstanceIdentifier()).addCallback(new JaxRsRestconfCallback<>(ar) {
355             @Override
356             Response transform(final Empty result) {
357                 return Response.noContent().build();
358             }
359         });
360     }
361
362     /**
363      * Partially modify the target data store, as defined in
364      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
365      *
366      * @param body data node for put to config DS
367      * @param ar {@link AsyncResponse} which needs to be completed
368      */
369     @PATCH
370     @Path("/data")
371     @Consumes({
372         MediaTypes.APPLICATION_YANG_DATA_XML,
373         MediaType.APPLICATION_XML,
374         MediaType.TEXT_XML
375     })
376     public void plainPatchDataXML(final InputStream body, @Suspended final AsyncResponse ar) {
377         try (var xmlBody = new XmlResourceBody(body)) {
378             plainPatchData(xmlBody, ar);
379         }
380     }
381
382     /**
383      * Partially modify the target data resource, as defined in
384      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
385      *
386      * @param identifier path to target
387      * @param body data node for put to config DS
388      * @param ar {@link AsyncResponse} which needs to be completed
389      */
390     @PATCH
391     @Path("/data/{identifier:.+}")
392     @Consumes({
393         MediaTypes.APPLICATION_YANG_DATA_XML,
394         MediaType.APPLICATION_XML,
395         MediaType.TEXT_XML
396     })
397     public void plainPatchDataXML(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
398             @Suspended final AsyncResponse ar) {
399         try (var xmlBody = new XmlResourceBody(body)) {
400             plainPatchData(identifier, xmlBody, ar);
401         }
402     }
403
404     /**
405      * Partially modify the target data store, as defined in
406      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
407      *
408      * @param body data node for put to config DS
409      * @param ar {@link AsyncResponse} which needs to be completed
410      */
411     @PATCH
412     @Path("/data")
413     @Consumes({
414         MediaTypes.APPLICATION_YANG_DATA_JSON,
415         MediaType.APPLICATION_JSON,
416     })
417     public void plainPatchDataJSON(final InputStream body, @Suspended final AsyncResponse ar) {
418         try (var jsonBody = new JsonResourceBody(body)) {
419             plainPatchData(jsonBody, ar);
420         }
421     }
422
423     /**
424      * Partially modify the target data resource, as defined in
425      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
426      *
427      * @param identifier path to target
428      * @param body data node for put to config DS
429      * @param ar {@link AsyncResponse} which needs to be completed
430      */
431     @PATCH
432     @Path("/data/{identifier:.+}")
433     @Consumes({
434         MediaTypes.APPLICATION_YANG_DATA_JSON,
435         MediaType.APPLICATION_JSON,
436     })
437     public void plainPatchDataJSON(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
438             @Suspended final AsyncResponse ar) {
439         try (var jsonBody = new JsonResourceBody(body)) {
440             plainPatchData(identifier, jsonBody, ar);
441         }
442     }
443
444     /**
445      * Partially modify the target data resource, as defined in
446      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
447      *
448      * @param body data node for put to config DS
449      * @param ar {@link AsyncResponse} which needs to be completed
450      */
451     private void plainPatchData(final ResourceBody body, final AsyncResponse ar) {
452         plainPatchData(server.bindRequestRoot(), body, ar);
453     }
454
455     /**
456      * Partially modify the target data resource, as defined in
457      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
458      *
459      * @param identifier path to target
460      * @param body data node for put to config DS
461      * @param ar {@link AsyncResponse} which needs to be completed
462      */
463     private void plainPatchData(final String identifier, final ResourceBody body, final AsyncResponse ar) {
464         plainPatchData(server.bindRequestPath(identifier), body, ar);
465     }
466
467     /**
468      * Partially modify the target data resource, as defined in
469      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
470      *
471      * @param reqPath path to target
472      * @param body data node for put to config DS
473      * @param ar {@link AsyncResponse} which needs to be completed
474      */
475     private void plainPatchData(final InstanceIdentifierContext reqPath, final ResourceBody body,
476             final AsyncResponse ar) {
477         final var req = server.bindResourceRequest(reqPath, body);
478         req.strategy().merge(req.path(), req.data()).addCallback(new JaxRsRestconfCallback<>(ar) {
479             @Override
480             Response transform(final Empty result) {
481                 return Response.ok().build();
482             }
483         });
484     }
485
486     /**
487      * Ordered list of edits that are applied to the target datastore by the server, as defined in
488      * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
489      *
490      * @param identifier path to target
491      * @param body YANG Patch body
492      * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
493      */
494     @PATCH
495     @Path("/data/{identifier:.+}")
496     @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
497     @Produces({
498         MediaTypes.APPLICATION_YANG_DATA_JSON,
499         MediaTypes.APPLICATION_YANG_DATA_XML
500     })
501     public void yangPatchDataXML(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
502             @Suspended final AsyncResponse ar) {
503         try (var xmlBody = new XmlPatchBody(body)) {
504             yangPatchData(identifier, xmlBody, ar);
505         }
506     }
507
508     /**
509      * Ordered list of edits that are applied to the datastore by the server, as defined in
510      * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
511      *
512      * @param body YANG Patch body
513      * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
514      */
515     @PATCH
516     @Path("/data")
517     @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
518     @Produces({
519         MediaTypes.APPLICATION_YANG_DATA_JSON,
520         MediaTypes.APPLICATION_YANG_DATA_XML
521     })
522     public void yangPatchDataXML(final InputStream body, @Suspended final AsyncResponse ar) {
523         try (var xmlBody = new XmlPatchBody(body)) {
524             yangPatchData(xmlBody, ar);
525         }
526     }
527
528     /**
529      * Ordered list of edits that are applied to the target datastore by the server, as defined in
530      * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
531      *
532      * @param identifier path to target
533      * @param body YANG Patch body
534      * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
535      */
536     @PATCH
537     @Path("/data/{identifier:.+}")
538     @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
539     @Produces({
540         MediaTypes.APPLICATION_YANG_DATA_JSON,
541         MediaTypes.APPLICATION_YANG_DATA_XML
542     })
543     public void yangPatchDataJSON(@Encoded @PathParam("identifier") final String identifier,
544             final InputStream body, @Suspended final AsyncResponse ar) {
545         try (var jsonBody = new JsonPatchBody(body)) {
546             yangPatchData(identifier, jsonBody, ar);
547         }
548     }
549
550     /**
551      * Ordered list of edits that are applied to the datastore by the server, as defined in
552      * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
553      *
554      * @param body YANG Patch body
555      * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
556      */
557     @PATCH
558     @Path("/data")
559     @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
560     @Produces({
561         MediaTypes.APPLICATION_YANG_DATA_JSON,
562         MediaTypes.APPLICATION_YANG_DATA_XML
563     })
564     public void yangPatchDataJSON(final InputStream body, @Suspended final AsyncResponse ar) {
565         try (var jsonBody = new JsonPatchBody(body)) {
566             yangPatchData(jsonBody, ar);
567         }
568     }
569
570     private void yangPatchData(final @NonNull PatchBody body, final AsyncResponse ar) {
571         final var context = server.bindRequestRoot().getSchemaContext();
572         yangPatchData(context, parsePatchBody(context, YangInstanceIdentifier.of(), body), null, ar);
573     }
574
575     private void yangPatchData(final String identifier, final @NonNull PatchBody body,
576             final AsyncResponse ar) {
577         final var reqPath = server.bindRequestPath(identifier);
578         final var modelContext = reqPath.getSchemaContext();
579         yangPatchData(modelContext, parsePatchBody(modelContext, reqPath.getInstanceIdentifier(), body),
580             reqPath.getMountPoint(), ar);
581     }
582
583     @VisibleForTesting
584     void yangPatchData(final @NonNull EffectiveModelContext modelContext,
585             final @NonNull PatchContext patch, final @Nullable DOMMountPoint mountPoint, final AsyncResponse ar) {
586         server.getRestconfStrategy(modelContext, mountPoint).patchData(patch)
587             .addCallback(new JaxRsRestconfCallback<>(ar) {
588                 @Override
589                 Response transform(final PatchStatusContext result) {
590                     return Response.status(getStatusCode(result)).entity(result).build();
591                 }
592             });
593     }
594
595     private static Status getStatusCode(final PatchStatusContext result) {
596         if (result.ok()) {
597             return Status.OK;
598         } else if (result.globalErrors() == null || result.globalErrors().isEmpty()) {
599             return result.editCollection().stream()
600                 .filter(patchStatus -> !patchStatus.isOk() && !patchStatus.getEditErrors().isEmpty())
601                 .findFirst()
602                 .map(PatchStatusEntity::getEditErrors)
603                 .flatMap(errors -> errors.stream().findFirst())
604                 .map(error -> ErrorTags.statusOf(error.getErrorTag()))
605                 .orElse(Status.INTERNAL_SERVER_ERROR);
606         } else {
607             final var error = result.globalErrors().iterator().next();
608             return ErrorTags.statusOf(error.getErrorTag());
609         }
610     }
611
612     private static @NonNull PatchContext parsePatchBody(final @NonNull EffectiveModelContext context,
613             final @NonNull YangInstanceIdentifier urlPath, final @NonNull PatchBody body) {
614         try {
615             return body.toPatchContext(context, urlPath);
616         } catch (IOException e) {
617             LOG.debug("Error parsing YANG Patch input", e);
618             throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
619                     ErrorTag.MALFORMED_MESSAGE, e);
620         }
621     }
622
623     /**
624      * Invoke Action operation.
625      *
626      * @param payload {@link NormalizedNodePayload} - the body of the operation
627      * @param ar {@link AsyncResponse} which needs to be completed with a NormalizedNodePayload
628      */
629     private void invokeAction(final InstanceIdentifierContext reqPath, final OperationInputBody body,
630             final AsyncResponse ar) {
631         server.dataInvokePOST(reqPath, body).addCallback(new JaxRsRestconfCallback<>(ar) {
632             @Override
633             Response transform(final DOMActionResult result) {
634                 final var output = result.getOutput().orElse(null);
635                 return output == null || output.isEmpty() ? Response.status(Status.NO_CONTENT).build()
636                     : Response.status(Status.OK).entity(new NormalizedNodePayload(reqPath.inference(), output)).build();
637             }
638         });
639     }
640 }