2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.restconf.nb.jaxrs;
10 import static java.util.Objects.requireNonNull;
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.BadRequestException;
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.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.NonNullByDefault;
46 import org.eclipse.jdt.annotation.Nullable;
47 import org.opendaylight.restconf.api.ApiPath;
48 import org.opendaylight.restconf.api.FormattableBody;
49 import org.opendaylight.restconf.api.HttpStatusCode;
50 import org.opendaylight.restconf.api.MediaTypes;
51 import org.opendaylight.restconf.api.QueryParameters;
52 import org.opendaylight.restconf.api.query.PrettyPrintParam;
53 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
54 import org.opendaylight.restconf.common.errors.RestconfFuture;
55 import org.opendaylight.restconf.nb.rfc8040.ErrorTagMapping;
56 import org.opendaylight.restconf.nb.rfc8040.URLConstants;
57 import org.opendaylight.restconf.server.api.ConfigurationMetadata;
58 import org.opendaylight.restconf.server.api.CreateResourceResult;
59 import org.opendaylight.restconf.server.api.DataGetResult;
60 import org.opendaylight.restconf.server.api.DataPatchResult;
61 import org.opendaylight.restconf.server.api.DataPostResult;
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.PatchStatusContext;
73 import org.opendaylight.restconf.server.api.RestconfServer;
74 import org.opendaylight.restconf.server.api.ServerError;
75 import org.opendaylight.restconf.server.api.ServerRequest;
76 import org.opendaylight.restconf.server.api.XmlChildBody;
77 import org.opendaylight.restconf.server.api.XmlDataPostBody;
78 import org.opendaylight.restconf.server.api.XmlOperationInputBody;
79 import org.opendaylight.restconf.server.api.XmlPatchBody;
80 import org.opendaylight.restconf.server.api.XmlResourceBody;
81 import org.opendaylight.restconf.server.spi.YangPatchStatusBody;
82 import org.opendaylight.yangtools.yang.common.Empty;
83 import org.opendaylight.yangtools.yang.common.YangConstants;
84 import org.slf4j.Logger;
85 import org.slf4j.LoggerFactory;
88 * Baseline RESTCONF implementation with JAX-RS. Interfaces to a {@link RestconfServer}. Since we need {@link ApiPath}
89 * arguments, we also implement {@link ParamConverterProvider} and provide the appropriate converter. This has the nice
90 * side-effect of suppressing <a href="https://github.com/eclipse-ee4j/jersey/issues/3700">Jersey warnings</a>.
94 public final class JaxRsRestconf implements ParamConverterProvider {
95 private static final Logger LOG = LoggerFactory.getLogger(JaxRsRestconf.class);
96 private static final CacheControl NO_CACHE = CacheControl.valueOf("no-cache");
97 private static final ParamConverter<ApiPath> API_PATH_CONVERTER = new ParamConverter<>() {
99 public ApiPath fromString(final String value) {
100 final var str = nonnull(value);
102 return ApiPath.parseUrl(str);
103 } catch (ParseException e) {
104 throw new IllegalArgumentException(e.getMessage(), e);
109 public String toString(final ApiPath value) {
110 return nonnull(value).toString();
113 private static <T> @NonNull T nonnull(final @Nullable T value) {
115 throw new IllegalArgumentException("value must not be null");
121 private final @NonNull RestconfServer server;
122 private final @NonNull ServerRequest emptyRequest;
123 private final @NonNull PrettyPrintParam prettyPrint;
124 private final @NonNull ErrorTagMapping errorTagMapping;
126 public JaxRsRestconf(final RestconfServer server, final ErrorTagMapping errorTagMapping,
127 final PrettyPrintParam prettyPrint) {
128 this.server = requireNonNull(server);
129 this.errorTagMapping = requireNonNull(errorTagMapping);
130 this.prettyPrint = requireNonNull(prettyPrint);
131 emptyRequest = ServerRequest.of(QueryParameters.of(), prettyPrint);
133 LOG.info("RESTCONF data-missing condition is reported as HTTP status {}", switch (errorTagMapping) {
134 case ERRATA_5565 -> "404 (Errata 5565)";
135 case RFC8040 -> "409 (RFC8040)";
139 private @NonNull ServerRequest requestOf(final UriInfo uriInfo) {
140 final QueryParameters params;
142 params = QueryParameters.ofMultiValue(uriInfo.getQueryParameters());
143 } catch (IllegalArgumentException e) {
144 throw new BadRequestException(e.getMessage(), e);
146 return params.isEmpty() ? emptyRequest : ServerRequest.of(params, prettyPrint);
150 @SuppressWarnings("unchecked")
151 public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType,
152 final Annotation[] annotations) {
153 return ApiPath.class.equals(rawType) ? (ParamConverter<T>) API_PATH_CONVERTER : null;
157 * Delete the target data resource.
159 * @param identifier path to target
160 * @param ar {@link AsyncResponse} which needs to be completed
163 @Path("/data/{identifier:.+}")
164 @SuppressWarnings("checkstyle:abbreviationAsWordInName")
165 public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier,
166 @Suspended final AsyncResponse ar) {
167 server.dataDELETE(emptyRequest, identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
169 Response transform(final Empty result) {
170 return Response.noContent().build();
176 * Get target data resource from data root.
178 * @param uriInfo URI info
179 * @param ar {@link AsyncResponse} which needs to be completed
184 MediaTypes.APPLICATION_YANG_DATA_JSON,
185 MediaTypes.APPLICATION_YANG_DATA_XML,
186 MediaType.APPLICATION_JSON,
187 MediaType.APPLICATION_XML,
190 public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
191 final var request = requestOf(uriInfo);
192 completeDataGET(server.dataGET(request), request.prettyPrint(), ar);
196 * Get target data resource.
198 * @param identifier path to target
199 * @param uriInfo URI info
200 * @param ar {@link AsyncResponse} which needs to be completed
203 @Path("/data/{identifier:.+}")
205 MediaTypes.APPLICATION_YANG_DATA_JSON,
206 MediaTypes.APPLICATION_YANG_DATA_XML,
207 MediaType.APPLICATION_JSON,
208 MediaType.APPLICATION_XML,
211 public void dataGET(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
212 @Suspended final AsyncResponse ar) {
213 final var request = requestOf(uriInfo);
214 completeDataGET(server.dataGET(request, identifier), request.prettyPrint(), ar);
218 private static void completeDataGET(final RestconfFuture<DataGetResult> future, final PrettyPrintParam prettyPrint,
219 final AsyncResponse ar) {
220 future.addCallback(new JaxRsRestconfCallback<>(ar) {
222 Response transform(final DataGetResult result) {
223 final var builder = Response.ok()
224 .entity(new JaxRsFormattableBody(result.body(), prettyPrint))
225 .cacheControl(NO_CACHE);
226 fillConfigurationMetadata(builder, result);
227 return builder.build();
232 private static void fillConfigurationMetadata(final ResponseBuilder builder, final ConfigurationMetadata metadata) {
233 final var etag = metadata.entityTag();
235 builder.tag(new EntityTag(etag.value(), etag.weak()));
237 final var lastModified = metadata.lastModified();
238 if (lastModified != null) {
239 builder.lastModified(Date.from(lastModified));
244 * Partially modify the target data store, as defined in
245 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
247 * @param body data node for put to config DS
248 * @param ar {@link AsyncResponse} which needs to be completed
253 MediaTypes.APPLICATION_YANG_DATA_XML,
254 MediaType.APPLICATION_XML,
257 public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
258 try (var xmlBody = new XmlResourceBody(body)) {
259 completeDataPATCH(server.dataPATCH(emptyRequest, xmlBody), ar);
264 * Partially modify the target data resource, as defined in
265 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
267 * @param identifier path to target
268 * @param body data node for put to config DS
269 * @param ar {@link AsyncResponse} which needs to be completed
272 @Path("/data/{identifier:.+}")
274 MediaTypes.APPLICATION_YANG_DATA_XML,
275 MediaType.APPLICATION_XML,
278 public void dataXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
279 @Suspended final AsyncResponse ar) {
280 try (var xmlBody = new XmlResourceBody(body)) {
281 completeDataPATCH(server.dataPATCH(emptyRequest, identifier, xmlBody), ar);
286 * Partially modify the target data store, as defined in
287 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
289 * @param body data node for put to config DS
290 * @param ar {@link AsyncResponse} which needs to be completed
295 MediaTypes.APPLICATION_YANG_DATA_JSON,
296 MediaType.APPLICATION_JSON,
298 public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
299 try (var jsonBody = new JsonResourceBody(body)) {
300 completeDataPATCH(server.dataPATCH(emptyRequest, jsonBody), ar);
305 * Partially modify the target data resource, as defined in
306 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
308 * @param identifier path to target
309 * @param body data node for put to config DS
310 * @param ar {@link AsyncResponse} which needs to be completed
313 @Path("/data/{identifier:.+}")
315 MediaTypes.APPLICATION_YANG_DATA_JSON,
316 MediaType.APPLICATION_JSON,
318 public void dataJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
319 @Suspended final AsyncResponse ar) {
320 try (var jsonBody = new JsonResourceBody(body)) {
321 completeDataPATCH(server.dataPATCH(emptyRequest, identifier, jsonBody), ar);
325 private static void completeDataPATCH(final RestconfFuture<DataPatchResult> future, final AsyncResponse ar) {
326 future.addCallback(new JaxRsRestconfCallback<>(ar) {
328 Response transform(final DataPatchResult result) {
329 final var builder = Response.ok();
330 fillConfigurationMetadata(builder, result);
331 return builder.build();
337 * Ordered list of edits that are applied to the datastore by the server, as defined in
338 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
340 * @param body YANG Patch body
341 * @param uriInfo URI info
342 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
346 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
348 MediaTypes.APPLICATION_YANG_DATA_JSON,
349 MediaTypes.APPLICATION_YANG_DATA_XML
351 public void dataYangJsonPATCH(final InputStream body, @Context final UriInfo uriInfo,
352 @Suspended final AsyncResponse ar) {
353 try (var jsonBody = new JsonPatchBody(body)) {
354 completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), jsonBody), ar);
359 * Ordered list of edits that are applied to the target datastore by the server, as defined in
360 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
362 * @param identifier path to target
363 * @param body YANG Patch body
364 * @param uriInfo URI info
365 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
368 @Path("/data/{identifier:.+}")
369 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
371 MediaTypes.APPLICATION_YANG_DATA_JSON,
372 MediaTypes.APPLICATION_YANG_DATA_XML
374 public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
375 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
376 try (var jsonBody = new JsonPatchBody(body)) {
377 completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), identifier, jsonBody), ar);
382 * Ordered list of edits that are applied to the datastore by the server, as defined in
383 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
385 * @param body YANG Patch body
386 * @param uriInfo URI info
387 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
391 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
393 MediaTypes.APPLICATION_YANG_DATA_JSON,
394 MediaTypes.APPLICATION_YANG_DATA_XML
396 public void dataYangXmlPATCH(final InputStream body, @Context final UriInfo uriInfo,
397 @Suspended final AsyncResponse ar) {
398 try (var xmlBody = new XmlPatchBody(body)) {
399 completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), xmlBody), ar);
404 * Ordered list of edits that are applied to the target datastore by the server, as defined in
405 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
407 * @param identifier path to target
408 * @param uriInfo URI info
409 * @param body YANG Patch body
410 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
413 @Path("/data/{identifier:.+}")
414 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
416 MediaTypes.APPLICATION_YANG_DATA_JSON,
417 MediaTypes.APPLICATION_YANG_DATA_XML
419 public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
420 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
421 try (var xmlBody = new XmlPatchBody(body)) {
422 completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), identifier, xmlBody), ar);
426 private void completeDataYangPATCH(final RestconfFuture<DataYangPatchResult> future,
427 final AsyncResponse ar) {
428 future.addCallback(new JaxRsRestconfCallback<>(ar) {
430 Response transform(final DataYangPatchResult result) {
431 final var patchStatus = result.status();
432 final var statusCode = statusOf(patchStatus);
433 final var builder = Response.status(statusCode.code(), statusCode.phrase())
434 .entity(new YangPatchStatusBody(patchStatus));
435 fillConfigurationMetadata(builder, result);
436 return builder.build();
439 private HttpStatusCode statusOf(final PatchStatusContext result) {
441 return HttpStatusCode.OK;
443 final var globalErrors = result.globalErrors();
444 if (globalErrors != null && !globalErrors.isEmpty()) {
445 return statusOfFirst(globalErrors);
447 for (var edit : result.editCollection()) {
449 final var editErrors = edit.getEditErrors();
450 if (editErrors != null && !editErrors.isEmpty()) {
451 return statusOfFirst(editErrors);
455 return HttpStatusCode.INTERNAL_SERVER_ERROR;
458 private @NonNull HttpStatusCode statusOfFirst(final List<ServerError> error) {
459 return errorTagMapping.statusOf(error.get(0).tag());
465 * Create a top-level data resource.
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
474 MediaTypes.APPLICATION_YANG_DATA_JSON,
475 MediaType.APPLICATION_JSON,
477 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
478 @Suspended final AsyncResponse ar) {
479 try (var jsonBody = new JsonChildBody(body)) {
480 final var request = requestOf(uriInfo);
481 completeDataPOST(server.dataPOST(request, jsonBody), request.prettyPrint(), uriInfo, ar);
486 * Create a data resource in target.
488 * @param identifier path to target
489 * @param body data node for put to config DS
490 * @param uriInfo URI info
491 * @param ar {@link AsyncResponse} which needs to be completed
494 @Path("/data/{identifier:.+}")
496 MediaTypes.APPLICATION_YANG_DATA_JSON,
497 MediaType.APPLICATION_JSON,
499 public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
500 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
501 final var request = requestOf(uriInfo);
502 completeDataPOST(server.dataPOST(request, identifier, new JsonDataPostBody(body)), request.prettyPrint(),
507 * Create a top-level data resource.
509 * @param body data node for put to config DS
510 * @param uriInfo URI info
511 * @param ar {@link AsyncResponse} which needs to be completed
516 MediaTypes.APPLICATION_YANG_DATA_XML,
517 MediaType.APPLICATION_XML,
520 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
521 try (var xmlBody = new XmlChildBody(body)) {
522 final var request = requestOf(uriInfo);
523 completeDataPOST(server.dataPOST(request, xmlBody), request.prettyPrint(), uriInfo, ar);
528 * Create a data resource in target.
530 * @param identifier path to target
531 * @param body data node for put to config DS
532 * @param uriInfo URI info
533 * @param ar {@link AsyncResponse} which needs to be completed
536 @Path("/data/{identifier:.+}")
538 MediaTypes.APPLICATION_YANG_DATA_XML,
539 MediaType.APPLICATION_XML,
542 public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
543 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
544 final var request = requestOf(uriInfo);
545 completeDataPOST(server.dataPOST(request, identifier, new XmlDataPostBody(body)), request.prettyPrint(),
549 private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future,
550 final PrettyPrintParam prettyPrint, final UriInfo uriInfo, final AsyncResponse ar) {
551 future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
553 Response transform(final DataPostResult result) {
554 if (result instanceof CreateResourceResult createResource) {
555 final var builder = Response.created(uriInfo.getBaseUriBuilder()
557 .path(createResource.createdPath().toString())
559 fillConfigurationMetadata(builder, createResource);
560 return builder.build();
562 if (result instanceof InvokeResult invokeOperation) {
563 final var output = invokeOperation.output();
564 return output == null ? Response.noContent().build()
565 : Response.ok().entity(new JaxRsFormattableBody(output, prettyPrint)).build();
567 LOG.error("Unhandled result {}", result);
568 return Response.serverError().build();
574 * Replace the data store.
576 * @param uriInfo request URI information
577 * @param body data node for put to config DS
578 * @param ar {@link AsyncResponse} which needs to be completed
583 MediaTypes.APPLICATION_YANG_DATA_JSON,
584 MediaType.APPLICATION_JSON,
586 public void dataJsonPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
587 try (var jsonBody = new JsonResourceBody(body)) {
588 completeDataPUT(server.dataPUT(requestOf(uriInfo), jsonBody), ar);
593 * Create or replace the target data resource.
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
601 @Path("/data/{identifier:.+}")
603 MediaTypes.APPLICATION_YANG_DATA_JSON,
604 MediaType.APPLICATION_JSON,
606 public void dataJsonPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
607 final InputStream body, @Suspended final AsyncResponse ar) {
608 try (var jsonBody = new JsonResourceBody(body)) {
609 completeDataPUT(server.dataPUT(requestOf(uriInfo), identifier, jsonBody), ar);
614 * Replace the data store.
616 * @param uriInfo request URI information
617 * @param body data node for put to config DS
618 * @param ar {@link AsyncResponse} which needs to be completed
623 MediaTypes.APPLICATION_YANG_DATA_XML,
624 MediaType.APPLICATION_XML,
627 public void dataXmlPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
628 try (var xmlBody = new XmlResourceBody(body)) {
629 completeDataPUT(server.dataPUT(requestOf(uriInfo), xmlBody), ar);
634 * Create or replace the target data resource.
636 * @param identifier path to target
637 * @param uriInfo request URI information
638 * @param body data node for put to config DS
639 * @param ar {@link AsyncResponse} which needs to be completed
642 @Path("/data/{identifier:.+}")
644 MediaTypes.APPLICATION_YANG_DATA_XML,
645 MediaType.APPLICATION_XML,
648 public void dataXmlPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
649 final InputStream body, @Suspended final AsyncResponse ar) {
650 try (var xmlBody = new XmlResourceBody(body)) {
651 completeDataPUT(server.dataPUT(requestOf(uriInfo), identifier, xmlBody), ar);
655 private static void completeDataPUT(final RestconfFuture<DataPutResult> future, final AsyncResponse ar) {
656 future.addCallback(new JaxRsRestconfCallback<>(ar) {
658 Response transform(final DataPutResult result) {
659 // Note: no Location header, as it matches the request path
660 final var builder = result.created() ? Response.created(null) : Response.noContent();
661 fillConfigurationMetadata(builder, result);
662 return builder.build();
668 * List RPC and action operations.
670 * @param ar {@link AsyncResponse} which needs to be completed
675 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
676 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
678 public void operationsGET(@Suspended final AsyncResponse ar) {
679 server.operationsGET(emptyRequest).addCallback(new FormattableBodyCallback(ar, prettyPrint));
683 * Retrieve list of operations and actions supported by the server or device.
685 * @param operation path parameter to identify device and/or operation
686 * @param ar {@link AsyncResponse} which needs to be completed
689 @Path("/operations/{operation:.+}")
691 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
692 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
694 public void operationsGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
695 server.operationsGET(emptyRequest, operation)
696 .addCallback(new FormattableBodyCallback(ar, prettyPrint));
700 * Invoke RPC operation.
702 * @param identifier module name and rpc identifier string for the desired operation
703 * @param body the body of the operation
704 * @param uriInfo URI info
705 * @param ar {@link AsyncResponse} which needs to be completed with a {@link FormattableBody} output
708 // FIXME: identifier is just a *single* QName
709 @Path("/operations/{identifier:.+}")
711 MediaTypes.APPLICATION_YANG_DATA_XML,
712 MediaType.APPLICATION_XML,
716 MediaTypes.APPLICATION_YANG_DATA_JSON,
717 MediaTypes.APPLICATION_YANG_DATA_XML,
718 MediaType.APPLICATION_JSON,
719 MediaType.APPLICATION_XML,
722 public void operationsXmlPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
723 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
724 try (var xmlBody = new XmlOperationInputBody(body)) {
725 operationsPOST(identifier, uriInfo, ar, xmlBody);
730 * Invoke RPC operation.
732 * @param identifier module name and rpc identifier string for the desired operation
733 * @param body the body of the operation
734 * @param uriInfo URI info
735 * @param ar {@link AsyncResponse} which needs to be completed with a {@link FormattableBody} output
738 // FIXME: identifier is just a *single* QName
739 @Path("/operations/{identifier:.+}")
741 MediaTypes.APPLICATION_YANG_DATA_JSON,
742 MediaType.APPLICATION_JSON,
745 MediaTypes.APPLICATION_YANG_DATA_JSON,
746 MediaTypes.APPLICATION_YANG_DATA_XML,
747 MediaType.APPLICATION_JSON,
748 MediaType.APPLICATION_XML,
751 public void operationsJsonPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
752 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
753 try (var jsonBody = new JsonOperationInputBody(body)) {
754 operationsPOST(identifier, uriInfo, ar, jsonBody);
758 private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
759 final OperationInputBody body) {
760 final var request = requestOf(uriInfo);
761 server.operationsPOST(request, uriInfo.getBaseUri(), identifier, body)
762 .addCallback(new JaxRsRestconfCallback<>(ar) {
764 Response transform(final InvokeResult result) {
765 final var body = result.output();
766 return body == null ? Response.noContent().build()
767 : Response.ok().entity(new JaxRsFormattableBody(body, request.prettyPrint())).build();
773 * Get revision of IETF YANG Library module.
775 * @param ar {@link AsyncResponse} which needs to be completed
778 @Path("/yang-library-version")
780 MediaTypes.APPLICATION_YANG_DATA_JSON,
781 MediaTypes.APPLICATION_YANG_DATA_XML,
782 MediaType.APPLICATION_JSON,
783 MediaType.APPLICATION_XML,
786 public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
787 server.yangLibraryVersionGET(emptyRequest).addCallback(new FormattableBodyCallback(ar, prettyPrint));
790 // FIXME: References to these resources are generated by our yang-library implementation. That means:
791 // - We really need to formalize the parameter structure so we get some help from JAX-RS during matching
793 // - optional yang-ext:mount prefix(es)
794 // - mandatory module name
795 // - optional module revision
796 // - We really should use /yang-library-module/{name}(/{revision})?
797 // - We seem to be lacking explicit support for submodules in there -- and those locations should then point
798 // to /yang-library-submodule/{moduleName}(/{moduleRevision})?/{name}(/{revision})? so as to look the
799 // submodule up efficiently and allow for the weird case where there are two submodules with the same name
800 // (that is currently not supported by the parser, but it will be in the future)
801 // - It does not make sense to support yang-ext:mount, unless we also intercept mount points and rewrite
802 // yang-library locations. We most likely want to do that to ensure users are not tempted to connect to
806 * Get schema of specific module.
808 * @param fileName source file name
809 * @param revision source revision
810 * @param ar {@link AsyncResponse} which needs to be completed
813 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
814 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
815 public void modulesYangGET(@PathParam("fileName") final String fileName,
816 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
817 completeModulesGET(server.modulesYangGET(emptyRequest, fileName, revision), ar);
821 * Get schema of specific module.
823 * @param mountPath mount point path
824 * @param fileName source file name
825 * @param revision source revision
826 * @param ar {@link AsyncResponse} which needs to be completed
829 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
830 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
831 public void modulesYangGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
832 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
833 @Suspended final AsyncResponse ar) {
834 completeModulesGET(server.modulesYangGET(emptyRequest, mountPath, fileName, revision), ar);
838 * Get schema of specific module.
840 * @param fileName source file name
841 * @param revision source revision
842 * @param ar {@link AsyncResponse} which needs to be completed
845 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
846 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
847 public void modulesYinGET(@PathParam("fileName") final String fileName,
848 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
849 completeModulesGET(server.modulesYinGET(emptyRequest, fileName, revision), ar);
853 * Get schema of specific module.
855 * @param mountPath mount point path
856 * @param fileName source file name
857 * @param revision source revision
858 * @param ar {@link AsyncResponse} which needs to be completed
861 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
862 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
863 public void modulesYinGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
864 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
865 @Suspended final AsyncResponse ar) {
866 completeModulesGET(server.modulesYinGET(emptyRequest, mountPath, fileName, revision), ar);
869 private static void completeModulesGET(final RestconfFuture<ModulesGetResult> future, final AsyncResponse ar) {
870 future.addCallback(new JaxRsRestconfCallback<>(ar) {
872 Response transform(final ModulesGetResult result) {
875 reader = result.source().openStream();
876 } catch (IOException e) {
877 throw new RestconfDocumentedException("Cannot open source", e);
879 return Response.ok(reader).build();