Refactor pretty printing
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / nb / jaxrs / JaxRsRestconf.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.jaxrs;
9
10 import static java.util.Objects.requireNonNull;
11
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.io.Reader;
15 import java.lang.annotation.Annotation;
16 import java.lang.reflect.Type;
17 import java.text.ParseException;
18 import java.util.Date;
19 import java.util.List;
20 import java.util.function.Function;
21 import javax.inject.Singleton;
22 import javax.ws.rs.Consumes;
23 import javax.ws.rs.DELETE;
24 import javax.ws.rs.Encoded;
25 import javax.ws.rs.GET;
26 import javax.ws.rs.PATCH;
27 import javax.ws.rs.POST;
28 import javax.ws.rs.PUT;
29 import javax.ws.rs.Path;
30 import javax.ws.rs.PathParam;
31 import javax.ws.rs.Produces;
32 import javax.ws.rs.QueryParam;
33 import javax.ws.rs.container.AsyncResponse;
34 import javax.ws.rs.container.Suspended;
35 import javax.ws.rs.core.CacheControl;
36 import javax.ws.rs.core.Context;
37 import javax.ws.rs.core.EntityTag;
38 import javax.ws.rs.core.MediaType;
39 import javax.ws.rs.core.Response;
40 import javax.ws.rs.core.Response.ResponseBuilder;
41 import javax.ws.rs.core.Response.Status;
42 import javax.ws.rs.core.UriInfo;
43 import javax.ws.rs.ext.ParamConverter;
44 import javax.ws.rs.ext.ParamConverterProvider;
45 import org.eclipse.jdt.annotation.NonNull;
46 import org.eclipse.jdt.annotation.Nullable;
47 import org.opendaylight.restconf.api.ApiPath;
48 import org.opendaylight.restconf.api.MediaTypes;
49 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
50 import org.opendaylight.restconf.common.errors.RestconfError;
51 import org.opendaylight.restconf.common.errors.RestconfFuture;
52 import org.opendaylight.restconf.common.patch.PatchStatusContext;
53 import org.opendaylight.restconf.nb.rfc8040.URLConstants;
54 import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
55 import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
56 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
57 import org.opendaylight.restconf.server.api.ConfigurationMetadata;
58 import org.opendaylight.restconf.server.api.DataGetResult;
59 import org.opendaylight.restconf.server.api.DataPatchResult;
60 import org.opendaylight.restconf.server.api.DataPostResult;
61 import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
62 import org.opendaylight.restconf.server.api.DataPutResult;
63 import org.opendaylight.restconf.server.api.DataYangPatchResult;
64 import org.opendaylight.restconf.server.api.InvokeResult;
65 import org.opendaylight.restconf.server.api.JsonChildBody;
66 import org.opendaylight.restconf.server.api.JsonDataPostBody;
67 import org.opendaylight.restconf.server.api.JsonOperationInputBody;
68 import org.opendaylight.restconf.server.api.JsonPatchBody;
69 import org.opendaylight.restconf.server.api.JsonResourceBody;
70 import org.opendaylight.restconf.server.api.ModulesGetResult;
71 import org.opendaylight.restconf.server.api.OperationInputBody;
72 import org.opendaylight.restconf.server.api.OperationsGetResult;
73 import org.opendaylight.restconf.server.api.RestconfServer;
74 import org.opendaylight.restconf.server.api.XmlChildBody;
75 import org.opendaylight.restconf.server.api.XmlDataPostBody;
76 import org.opendaylight.restconf.server.api.XmlOperationInputBody;
77 import org.opendaylight.restconf.server.api.XmlPatchBody;
78 import org.opendaylight.restconf.server.api.XmlResourceBody;
79 import org.opendaylight.yangtools.yang.common.Empty;
80 import org.opendaylight.yangtools.yang.common.YangConstants;
81 import org.slf4j.Logger;
82 import org.slf4j.LoggerFactory;
83
84 /**
85  * Baseline RESTCONF implementation with JAX-RS. Interfaces to a {@link RestconfServer}. Since we need {@link ApiPath}
86  * arguments, we also implement {@link ParamConverterProvider} and provide the appropriate converter. This has the nice
87  * side-effect of suppressing <a href="https://github.com/eclipse-ee4j/jersey/issues/3700">Jersey warnings</a>.
88  */
89 @Path("/")
90 @Singleton
91 public final class JaxRsRestconf implements ParamConverterProvider {
92     private static final Logger LOG = LoggerFactory.getLogger(JaxRsRestconf.class);
93     private static final CacheControl NO_CACHE = CacheControl.valueOf("no-cache");
94     private static final ParamConverter<ApiPath> API_PATH_CONVERTER = new ParamConverter<>() {
95         @Override
96         public ApiPath fromString(final String value) {
97             final var str = nonnull(value);
98             try {
99                 return ApiPath.parseUrl(str);
100             } catch (ParseException e) {
101                 throw new IllegalArgumentException(e.getMessage(), e);
102             }
103         }
104
105         @Override
106         public String toString(final ApiPath value) {
107             return nonnull(value).toString();
108         }
109
110         private static <T> @NonNull T nonnull(final @Nullable T value) {
111             if (value == null) {
112                 throw new IllegalArgumentException("value must not be null");
113             }
114             return value;
115         }
116     };
117
118     private final RestconfServer server;
119
120     public JaxRsRestconf(final RestconfServer server) {
121         this.server = requireNonNull(server);
122     }
123
124     @Override
125     @SuppressWarnings("unchecked")
126     public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType,
127             final Annotation[] annotations) {
128         return ApiPath.class.equals(rawType) ? (ParamConverter<T>) API_PATH_CONVERTER : null;
129     }
130
131     /**
132      * Delete the target data resource.
133      *
134      * @param identifier path to target
135      * @param ar {@link AsyncResponse} which needs to be completed
136      */
137     @DELETE
138     @Path("/data/{identifier:.+}")
139     @SuppressWarnings("checkstyle:abbreviationAsWordInName")
140     public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier,
141             @Suspended final AsyncResponse ar) {
142         server.dataDELETE(identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
143             @Override
144             Response transform(final Empty result) {
145                 return Response.noContent().build();
146             }
147         });
148     }
149
150     /**
151      * Get target data resource from data root.
152      *
153      * @param uriInfo URI info
154      * @param ar {@link AsyncResponse} which needs to be completed
155      */
156     @GET
157     @Path("/data")
158     @Produces({
159         MediaTypes.APPLICATION_YANG_DATA_JSON,
160         MediaTypes.APPLICATION_YANG_DATA_XML,
161         MediaType.APPLICATION_JSON,
162         MediaType.APPLICATION_XML,
163         MediaType.TEXT_XML
164     })
165     public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
166         completeDataGET(server.dataGET(QueryParams.newDataGetParams(uriInfo)), ar);
167     }
168
169     /**
170      * Get target data resource.
171      *
172      * @param identifier path to target
173      * @param uriInfo URI info
174      * @param ar {@link AsyncResponse} which needs to be completed
175      */
176     @GET
177     @Path("/data/{identifier:.+}")
178     @Produces({
179         MediaTypes.APPLICATION_YANG_DATA_JSON,
180         MediaTypes.APPLICATION_YANG_DATA_XML,
181         MediaType.APPLICATION_JSON,
182         MediaType.APPLICATION_XML,
183         MediaType.TEXT_XML
184     })
185     public void dataGET(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
186             @Suspended final AsyncResponse ar) {
187         completeDataGET(server.dataGET(identifier, QueryParams.newDataGetParams(uriInfo)), ar);
188     }
189
190     private static void completeDataGET(final RestconfFuture<DataGetResult> future, final AsyncResponse ar) {
191         future.addCallback(new JaxRsRestconfCallback<>(ar) {
192             @Override
193             Response transform(final DataGetResult result) {
194                 final var builder = Response.status(Status.OK)
195                     .entity(result.payload())
196                     .cacheControl(NO_CACHE);
197                 fillConfigurationMetadata(builder, result);
198                 return builder.build();
199             }
200         });
201     }
202
203     private static void fillConfigurationMetadata(final ResponseBuilder builder, final ConfigurationMetadata metadata) {
204         final var etag = metadata.entityTag();
205         if (etag != null) {
206             builder.tag(new EntityTag(etag.value(), etag.weak()));
207         }
208         final var lastModified = metadata.lastModified();
209         if (lastModified != null) {
210             builder.lastModified(Date.from(lastModified));
211         }
212     }
213
214     /**
215      * Partially modify the target data store, as defined in
216      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
217      *
218      * @param body data node for put to config DS
219      * @param ar {@link AsyncResponse} which needs to be completed
220      */
221     @PATCH
222     @Path("/data")
223     @Consumes({
224         MediaTypes.APPLICATION_YANG_DATA_XML,
225         MediaType.APPLICATION_XML,
226         MediaType.TEXT_XML
227     })
228     public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
229         try (var xmlBody = new XmlResourceBody(body)) {
230             completeDataPATCH(server.dataPATCH(xmlBody), ar);
231         }
232     }
233
234     /**
235      * Partially modify the target data resource, as defined in
236      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
237      *
238      * @param identifier path to target
239      * @param body data node for put to config DS
240      * @param ar {@link AsyncResponse} which needs to be completed
241      */
242     @PATCH
243     @Path("/data/{identifier:.+}")
244     @Consumes({
245         MediaTypes.APPLICATION_YANG_DATA_XML,
246         MediaType.APPLICATION_XML,
247         MediaType.TEXT_XML
248     })
249     public void dataXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
250             @Suspended final AsyncResponse ar) {
251         try (var xmlBody = new XmlResourceBody(body)) {
252             completeDataPATCH(server.dataPATCH(identifier, xmlBody), ar);
253         }
254     }
255
256     /**
257      * Partially modify the target data store, as defined in
258      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
259      *
260      * @param body data node for put to config DS
261      * @param ar {@link AsyncResponse} which needs to be completed
262      */
263     @PATCH
264     @Path("/data")
265     @Consumes({
266         MediaTypes.APPLICATION_YANG_DATA_JSON,
267         MediaType.APPLICATION_JSON,
268     })
269     public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
270         try (var jsonBody = new JsonResourceBody(body)) {
271             completeDataPATCH(server.dataPATCH(jsonBody), ar);
272         }
273     }
274
275     /**
276      * Partially modify the target data resource, as defined in
277      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
278      *
279      * @param identifier path to target
280      * @param body data node for put to config DS
281      * @param ar {@link AsyncResponse} which needs to be completed
282      */
283     @PATCH
284     @Path("/data/{identifier:.+}")
285     @Consumes({
286         MediaTypes.APPLICATION_YANG_DATA_JSON,
287         MediaType.APPLICATION_JSON,
288     })
289     public void dataJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
290             @Suspended final AsyncResponse ar) {
291         try (var jsonBody = new JsonResourceBody(body)) {
292             completeDataPATCH(server.dataPATCH(identifier, jsonBody), ar);
293         }
294     }
295
296     private static void completeDataPATCH(final RestconfFuture<DataPatchResult> future, final AsyncResponse ar) {
297         future.addCallback(new JaxRsRestconfCallback<>(ar) {
298             @Override
299             Response transform(final DataPatchResult result) {
300                 final var builder = Response.ok();
301                 fillConfigurationMetadata(builder, result);
302                 return builder.build();
303             }
304         });
305     }
306
307     /**
308      * Ordered list of edits that are applied to the datastore by the server, as defined in
309      * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
310      *
311      * @param body YANG Patch body
312      * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
313      */
314     @PATCH
315     @Path("/data")
316     @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
317     @Produces({
318         MediaTypes.APPLICATION_YANG_DATA_JSON,
319         MediaTypes.APPLICATION_YANG_DATA_XML
320     })
321     public void dataYangJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
322         try (var jsonBody = new JsonPatchBody(body)) {
323             completeDataYangPATCH(server.dataPATCH(jsonBody), ar);
324         }
325     }
326
327     /**
328      * Ordered list of edits that are applied to the target datastore by the server, as defined in
329      * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
330      *
331      * @param identifier path to target
332      * @param body YANG Patch body
333      * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
334      */
335     @PATCH
336     @Path("/data/{identifier:.+}")
337     @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
338     @Produces({
339         MediaTypes.APPLICATION_YANG_DATA_JSON,
340         MediaTypes.APPLICATION_YANG_DATA_XML
341     })
342     public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
343             @Suspended final AsyncResponse ar) {
344         try (var jsonBody = new JsonPatchBody(body)) {
345             completeDataYangPATCH(server.dataPATCH(identifier, jsonBody), ar);
346         }
347     }
348
349     /**
350      * Ordered list of edits that are applied to the datastore by the server, as defined in
351      * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
352      *
353      * @param body YANG Patch body
354      * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
355      */
356     @PATCH
357     @Path("/data")
358     @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
359     @Produces({
360         MediaTypes.APPLICATION_YANG_DATA_JSON,
361         MediaTypes.APPLICATION_YANG_DATA_XML
362     })
363     public void dataYangXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
364         try (var xmlBody = new XmlPatchBody(body)) {
365             completeDataYangPATCH(server.dataPATCH(xmlBody), ar);
366         }
367     }
368
369     /**
370      * Ordered list of edits that are applied to the target datastore by the server, as defined in
371      * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
372      *
373      * @param identifier path to target
374      * @param body YANG Patch body
375      * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
376      */
377     @PATCH
378     @Path("/data/{identifier:.+}")
379     @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
380     @Produces({
381         MediaTypes.APPLICATION_YANG_DATA_JSON,
382         MediaTypes.APPLICATION_YANG_DATA_XML
383     })
384     public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
385             @Suspended final AsyncResponse ar) {
386         try (var xmlBody = new XmlPatchBody(body)) {
387             completeDataYangPATCH(server.dataPATCH(identifier, xmlBody), ar);
388         }
389     }
390
391     private static void completeDataYangPATCH(final RestconfFuture<DataYangPatchResult> future,
392             final AsyncResponse ar) {
393         future.addCallback(new JaxRsRestconfCallback<>(ar) {
394             @Override
395             Response transform(final DataYangPatchResult result) {
396                 final var status = result.status();
397                 final var builder = Response.status(statusOf(status)).entity(status);
398                 fillConfigurationMetadata(builder, result);
399                 return builder.build();
400             }
401
402             private static Status statusOf(final PatchStatusContext result) {
403                 if (result.ok()) {
404                     return Status.OK;
405                 }
406                 final var globalErrors = result.globalErrors();
407                 if (globalErrors != null && !globalErrors.isEmpty()) {
408                     return statusOfFirst(globalErrors);
409                 }
410                 for (var edit : result.editCollection()) {
411                     if (!edit.isOk()) {
412                         final var editErrors = edit.getEditErrors();
413                         if (editErrors != null && !editErrors.isEmpty()) {
414                             return statusOfFirst(editErrors);
415                         }
416                     }
417                 }
418                 return Status.INTERNAL_SERVER_ERROR;
419             }
420
421             private static Status statusOfFirst(final List<RestconfError> error) {
422                 return ErrorTags.statusOf(error.get(0).getErrorTag());
423             }
424         });
425     }
426
427     /**
428      * Create a top-level data resource.
429      *
430      * @param body data node for put to config DS
431      * @param uriInfo URI info
432      * @param ar {@link AsyncResponse} which needs to be completed
433      */
434     @POST
435     @Path("/data")
436     @Consumes({
437         MediaTypes.APPLICATION_YANG_DATA_JSON,
438         MediaType.APPLICATION_JSON,
439     })
440     public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
441             @Suspended final AsyncResponse ar) {
442         try (var jsonBody = new JsonChildBody(body)) {
443             completeDataPOST(server.dataPOST(jsonBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
444         }
445     }
446
447     /**
448      * Create a data resource in target.
449      *
450      * @param identifier path to target
451      * @param body data node for put to config DS
452      * @param uriInfo URI info
453      * @param ar {@link AsyncResponse} which needs to be completed
454      */
455     @POST
456     @Path("/data/{identifier:.+}")
457     @Consumes({
458         MediaTypes.APPLICATION_YANG_DATA_JSON,
459         MediaType.APPLICATION_JSON,
460     })
461     public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
462             @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
463         completeDataPOST(server.dataPOST(identifier, new JsonDataPostBody(body), QueryParams.normalize(uriInfo)),
464             uriInfo, ar);
465     }
466
467     /**
468      * Create a top-level data resource.
469      *
470      * @param body data node for put to config DS
471      * @param uriInfo URI info
472      * @param ar {@link AsyncResponse} which needs to be completed
473      */
474     @POST
475     @Path("/data")
476     @Consumes({
477         MediaTypes.APPLICATION_YANG_DATA_XML,
478         MediaType.APPLICATION_XML,
479         MediaType.TEXT_XML
480     })
481     public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
482         try (var xmlBody = new XmlChildBody(body)) {
483             completeDataPOST(server.dataPOST(xmlBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
484         }
485     }
486
487     /**
488      * Create a data resource in target.
489      *
490      * @param identifier path to target
491      * @param body data node for put to config DS
492      * @param uriInfo URI info
493      * @param ar {@link AsyncResponse} which needs to be completed
494      */
495     @POST
496     @Path("/data/{identifier:.+}")
497     @Consumes({
498         MediaTypes.APPLICATION_YANG_DATA_XML,
499         MediaType.APPLICATION_XML,
500         MediaType.TEXT_XML
501     })
502     public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
503             @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
504         completeDataPOST(server.dataPOST(identifier, new XmlDataPostBody(body), QueryParams.normalize(uriInfo)),
505             uriInfo, ar);
506     }
507
508     private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future, final UriInfo uriInfo,
509             final AsyncResponse ar) {
510         future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
511             @Override
512             Response transform(final DataPostResult result) {
513                 if (result instanceof CreateResource createResource) {
514                     final var builder = Response.created(uriInfo.getBaseUriBuilder()
515                         .path("data")
516                         .path(createResource.createdPath())
517                         .build());
518                     fillConfigurationMetadata(builder, createResource);
519                     return builder.build();
520                 }
521                 if (result instanceof InvokeResult invokeOperation) {
522                     final var output = invokeOperation.output();
523                     return output == null ? Response.status(Status.NO_CONTENT).build()
524                         : Response.status(Status.OK).entity(output).build();
525                 }
526                 LOG.error("Unhandled result {}", result);
527                 return Response.serverError().build();
528             }
529         });
530     }
531
532     /**
533      * Replace the data store.
534      *
535      * @param uriInfo request URI information
536      * @param body data node for put to config DS
537      * @param ar {@link AsyncResponse} which needs to be completed
538      */
539     @PUT
540     @Path("/data")
541     @Consumes({
542         MediaTypes.APPLICATION_YANG_DATA_JSON,
543         MediaType.APPLICATION_JSON,
544     })
545     public void dataJsonPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
546         try (var jsonBody = new JsonResourceBody(body)) {
547             completeDataPUT(server.dataPUT(jsonBody, QueryParams.normalize(uriInfo)), ar);
548         }
549     }
550
551     /**
552      * Create or replace the target data resource.
553      *
554      * @param identifier path to target
555      * @param uriInfo request URI information
556      * @param body data node for put to config DS
557      * @param ar {@link AsyncResponse} which needs to be completed
558      */
559     @PUT
560     @Path("/data/{identifier:.+}")
561     @Consumes({
562         MediaTypes.APPLICATION_YANG_DATA_JSON,
563         MediaType.APPLICATION_JSON,
564     })
565     public void dataJsonPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
566             final InputStream body, @Suspended final AsyncResponse ar) {
567         try (var jsonBody = new JsonResourceBody(body)) {
568             completeDataPUT(server.dataPUT(identifier, jsonBody, QueryParams.normalize(uriInfo)), ar);
569         }
570     }
571
572     /**
573      * Replace the data store.
574      *
575      * @param uriInfo request URI information
576      * @param body data node for put to config DS
577      * @param ar {@link AsyncResponse} which needs to be completed
578      */
579     @PUT
580     @Path("/data")
581     @Consumes({
582         MediaTypes.APPLICATION_YANG_DATA_XML,
583         MediaType.APPLICATION_XML,
584         MediaType.TEXT_XML
585     })
586     public void dataXmlPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
587         try (var xmlBody = new XmlResourceBody(body)) {
588             completeDataPUT(server.dataPUT(xmlBody, QueryParams.normalize(uriInfo)), ar);
589         }
590     }
591
592     /**
593      * Create or replace the target data resource.
594      *
595      * @param identifier path to target
596      * @param uriInfo request URI information
597      * @param body data node for put to config DS
598      * @param ar {@link AsyncResponse} which needs to be completed
599      */
600     @PUT
601     @Path("/data/{identifier:.+}")
602     @Consumes({
603         MediaTypes.APPLICATION_YANG_DATA_XML,
604         MediaType.APPLICATION_XML,
605         MediaType.TEXT_XML
606     })
607     public void dataXmlPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
608             final InputStream body, @Suspended final AsyncResponse ar) {
609         try (var xmlBody = new XmlResourceBody(body)) {
610             completeDataPUT(server.dataPUT(identifier, xmlBody, QueryParams.normalize(uriInfo)), ar);
611         }
612     }
613
614     private static void completeDataPUT(final RestconfFuture<DataPutResult> future, final AsyncResponse ar) {
615         future.addCallback(new JaxRsRestconfCallback<>(ar) {
616             @Override
617             Response transform(final DataPutResult result) {
618                 // Note: no Location header, as it matches the request path
619                 final var builder = result.created() ? Response.status(Status.CREATED) : Response.noContent();
620                 fillConfigurationMetadata(builder, result);
621                 return builder.build();
622             }
623         });
624     }
625
626     /**
627      * List RPC and action operations in RFC7951 format.
628      *
629      * @param ar {@link AsyncResponse} which needs to be completed
630      */
631     @GET
632     @Path("/operations")
633     @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
634     public void operationsJsonGET(@Suspended final AsyncResponse ar) {
635         completeOperationsJsonGet(server.operationsGET(), ar);
636     }
637
638     /**
639      * Retrieve list of operations and actions supported by the server or device in JSON format.
640      *
641      * @param operation path parameter to identify device and/or operation
642      * @param ar {@link AsyncResponse} which needs to be completed
643      */
644     @GET
645     @Path("/operations/{operation:.+}")
646     @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
647     public void operationsJsonGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
648         completeOperationsGet(server.operationsGET(operation), ar, OperationsGetResult::toJSON);
649     }
650
651     private static void completeOperationsJsonGet(final RestconfFuture<OperationsGetResult> future,
652             final AsyncResponse ar) {
653         completeOperationsGet(future, ar, OperationsGetResult::toJSON);
654     }
655
656     /**
657      * List RPC and action operations in RFC8040 XML format.
658      *
659      * @param ar {@link AsyncResponse} which needs to be completed
660      */
661     @GET
662     @Path("/operations")
663     @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
664     public void operationsXmlGET(@Suspended final AsyncResponse ar) {
665         completeOperationsXmlGet(server.operationsGET(), ar);
666     }
667
668     /**
669      * Retrieve list of operations and actions supported by the server or device in XML format.
670      *
671      * @param operation path parameter to identify device and/or operation
672      * @param ar {@link AsyncResponse} which needs to be completed
673      */
674     @GET
675     @Path("/operations/{operation:.+}")
676     @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
677     public void operationsXmlGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
678         completeOperationsXmlGet(server.operationsGET(operation), ar);
679     }
680
681     private static void completeOperationsXmlGet(final RestconfFuture<OperationsGetResult> future,
682             final AsyncResponse ar) {
683         completeOperationsGet(future, ar, OperationsGetResult::toXML);
684     }
685
686     private static void completeOperationsGet(final RestconfFuture<OperationsGetResult> future, final AsyncResponse ar,
687             final Function<OperationsGetResult, String> toString) {
688         future.addCallback(new JaxRsRestconfCallback<OperationsGetResult>(ar) {
689             @Override
690             Response transform(final OperationsGetResult result) {
691                 return Response.ok().entity(toString.apply(result)).build();
692             }
693         });
694     }
695
696     /**
697      * Invoke RPC operation.
698      *
699      * @param identifier module name and rpc identifier string for the desired operation
700      * @param body the body of the operation
701      * @param uriInfo URI info
702      * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
703      */
704     @POST
705     // FIXME: identifier is just a *single* QName
706     @Path("/operations/{identifier:.+}")
707     @Consumes({
708         MediaTypes.APPLICATION_YANG_DATA_XML,
709         MediaType.APPLICATION_XML,
710         MediaType.TEXT_XML
711     })
712     @Produces({
713         MediaTypes.APPLICATION_YANG_DATA_JSON,
714         MediaTypes.APPLICATION_YANG_DATA_XML,
715         MediaType.APPLICATION_JSON,
716         MediaType.APPLICATION_XML,
717         MediaType.TEXT_XML
718     })
719     public void operationsXmlPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
720             @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
721         try (var xmlBody = new XmlOperationInputBody(body)) {
722             operationsPOST(identifier, uriInfo, ar, xmlBody);
723         }
724     }
725
726     /**
727      * Invoke RPC operation.
728      *
729      * @param identifier module name and rpc identifier string for the desired operation
730      * @param body the body of the operation
731      * @param uriInfo URI info
732      * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
733      */
734     @POST
735     // FIXME: identifier is just a *single* QName
736     @Path("/operations/{identifier:.+}")
737     @Consumes({
738         MediaTypes.APPLICATION_YANG_DATA_JSON,
739         MediaType.APPLICATION_JSON,
740     })
741     @Produces({
742         MediaTypes.APPLICATION_YANG_DATA_JSON,
743         MediaTypes.APPLICATION_YANG_DATA_XML,
744         MediaType.APPLICATION_JSON,
745         MediaType.APPLICATION_XML,
746         MediaType.TEXT_XML
747     })
748     public void operationsJsonPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
749             @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
750         try (var jsonBody = new JsonOperationInputBody(body)) {
751             operationsPOST(identifier, uriInfo, ar, jsonBody);
752         }
753     }
754
755     private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
756             final OperationInputBody body) {
757         server.operationsPOST(uriInfo.getBaseUri(), identifier, QueryParams.normalize(uriInfo), body)
758             .addCallback(new JaxRsRestconfCallback<>(ar) {
759                 @Override
760                 Response transform(final InvokeResult result) {
761                     final var body = result.output();
762                     return body == null ? Response.noContent().build()
763                         : Response.ok().entity(body).build();
764                 }
765             });
766     }
767
768     /**
769      * Get revision of IETF YANG Library module.
770      *
771      * @param ar {@link AsyncResponse} which needs to be completed
772      */
773     @GET
774     @Path("/yang-library-version")
775     @Produces({
776         MediaTypes.APPLICATION_YANG_DATA_JSON,
777         MediaTypes.APPLICATION_YANG_DATA_XML,
778         MediaType.APPLICATION_JSON,
779         MediaType.APPLICATION_XML,
780         MediaType.TEXT_XML
781     })
782     public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
783         server.yangLibraryVersionGET().addCallback(new JaxRsRestconfCallback<NormalizedNodePayload>(ar) {
784             @Override
785             Response transform(final NormalizedNodePayload result) {
786                 return Response.ok().entity(result).build();
787             }
788         });
789     }
790
791     // FIXME: References to these resources are generated by our yang-library implementation. That means:
792     //        - We really need to formalize the parameter structure so we get some help from JAX-RS during matching
793     //          of three things:
794     //          - optional yang-ext:mount prefix(es)
795     //          - mandatory module name
796     //          - optional module revision
797     //        - We really should use /yang-library-module/{name}(/{revision})?
798     //        - We seem to be lacking explicit support for submodules in there -- and those locations should then point
799     //          to /yang-library-submodule/{moduleName}(/{moduleRevision})?/{name}(/{revision})? so as to look the
800     //          submodule up efficiently and allow for the weird case where there are two submodules with the same name
801     //          (that is currently not supported by the parser, but it will be in the future)
802     //        - It does not make sense to support yang-ext:mount, unless we also intercept mount points and rewrite
803     //          yang-library locations. We most likely want to do that to ensure users are not tempted to connect to
804     //          wild destinations
805
806     /**
807      * Get schema of specific module.
808      *
809      * @param fileName source file name
810      * @param revision source revision
811      * @param ar {@link AsyncResponse} which needs to be completed
812      */
813     @GET
814     @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
815     @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
816     public void modulesYangGET(@PathParam("fileName") final String fileName,
817             @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
818         completeModulesGET(server.modulesYangGET(fileName, revision), ar);
819     }
820
821     /**
822      * Get schema of specific module.
823      *
824      * @param mountPath mount point path
825      * @param fileName source file name
826      * @param revision source revision
827      * @param ar {@link AsyncResponse} which needs to be completed
828      */
829     @GET
830     @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
831     @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
832     public void modulesYangGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
833             @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
834             @Suspended final AsyncResponse ar) {
835         completeModulesGET(server.modulesYangGET(mountPath, fileName, revision), ar);
836     }
837
838     /**
839      * Get schema of specific module.
840      *
841      * @param fileName source file name
842      * @param revision source revision
843      * @param ar {@link AsyncResponse} which needs to be completed
844      */
845     @GET
846     @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
847     @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
848     public void modulesYinGET(@PathParam("fileName") final String fileName,
849             @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
850         completeModulesGET(server.modulesYinGET(fileName, revision), ar);
851     }
852
853     /**
854      * Get schema of specific module.
855      *
856      * @param mountPath mount point path
857      * @param fileName source file name
858      * @param revision source revision
859      * @param ar {@link AsyncResponse} which needs to be completed
860      */
861     @GET
862     @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
863     @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
864     public void modulesYinGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
865             @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
866             @Suspended final AsyncResponse ar) {
867         completeModulesGET(server.modulesYinGET(mountPath, fileName, revision), ar);
868     }
869
870     private static void completeModulesGET(final RestconfFuture<ModulesGetResult> future, final AsyncResponse ar) {
871         future.addCallback(new JaxRsRestconfCallback<>(ar) {
872             @Override
873             Response transform(final ModulesGetResult result) {
874                 final Reader reader;
875                 try {
876                     reader = result.source().openStream();
877                 } catch (IOException e) {
878                     throw new RestconfDocumentedException("Cannot open source", e);
879                 }
880                 return Response.ok(reader).build();
881             }
882         });
883     }
884 }