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