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 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.DataPostResult.InvokeOperation;
63 import org.opendaylight.restconf.server.api.DataPutResult;
64 import org.opendaylight.restconf.server.api.DataYangPatchResult;
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.OperationsPostResult;
74 import org.opendaylight.restconf.server.api.RestconfServer;
75 import org.opendaylight.restconf.server.api.XmlChildBody;
76 import org.opendaylight.restconf.server.api.XmlDataPostBody;
77 import org.opendaylight.restconf.server.api.XmlOperationInputBody;
78 import org.opendaylight.restconf.server.api.XmlPatchBody;
79 import org.opendaylight.restconf.server.api.XmlResourceBody;
80 import org.opendaylight.yangtools.yang.common.Empty;
81 import org.opendaylight.yangtools.yang.common.YangConstants;
82 import org.slf4j.Logger;
83 import org.slf4j.LoggerFactory;
86 * Baseline RESTCONF implementation with JAX-RS. Interfaces to a {@link RestconfServer}. Since we need {@link ApiPath}
87 * arguments, we also implement {@link ParamConverterProvider} and provide the appropriate converter. This has the nice
88 * side-effect of suppressing <a href="https://github.com/eclipse-ee4j/jersey/issues/3700">Jersey warnings</a>.
92 public final class JaxRsRestconf implements ParamConverterProvider {
93 private static final Logger LOG = LoggerFactory.getLogger(JaxRsRestconf.class);
94 private static final CacheControl NO_CACHE = CacheControl.valueOf("no-cache");
95 private static final ParamConverter<ApiPath> API_PATH_CONVERTER = new ParamConverter<>() {
97 public ApiPath fromString(final String value) {
98 final var str = nonnull(value);
100 return ApiPath.parseUrl(str);
101 } catch (ParseException e) {
102 throw new IllegalArgumentException(e.getMessage(), e);
107 public String toString(final ApiPath value) {
108 return nonnull(value).toString();
111 private static <T> @NonNull T nonnull(final @Nullable T value) {
113 throw new IllegalArgumentException("value must not be null");
119 private final RestconfServer server;
121 public JaxRsRestconf(final RestconfServer server) {
122 this.server = requireNonNull(server);
126 @SuppressWarnings("unchecked")
127 public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType,
128 final Annotation[] annotations) {
129 return ApiPath.class.equals(rawType) ? (ParamConverter<T>) API_PATH_CONVERTER : null;
133 * Delete the target data resource.
135 * @param identifier path to target
136 * @param ar {@link AsyncResponse} which needs to be completed
139 @Path("/data/{identifier:.+}")
140 @SuppressWarnings("checkstyle:abbreviationAsWordInName")
141 public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier,
142 @Suspended final AsyncResponse ar) {
143 server.dataDELETE(identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
145 Response transform(final Empty result) {
146 return Response.noContent().build();
152 * Get target data resource from data root.
154 * @param uriInfo URI info
155 * @param ar {@link AsyncResponse} which needs to be completed
160 MediaTypes.APPLICATION_YANG_DATA_JSON,
161 MediaTypes.APPLICATION_YANG_DATA_XML,
162 MediaType.APPLICATION_JSON,
163 MediaType.APPLICATION_XML,
166 public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
167 completeDataGET(server.dataGET(QueryParams.newDataGetParams(uriInfo)), ar);
171 * Get target data resource.
173 * @param identifier path to target
174 * @param uriInfo URI info
175 * @param ar {@link AsyncResponse} which needs to be completed
178 @Path("/data/{identifier:.+}")
180 MediaTypes.APPLICATION_YANG_DATA_JSON,
181 MediaTypes.APPLICATION_YANG_DATA_XML,
182 MediaType.APPLICATION_JSON,
183 MediaType.APPLICATION_XML,
186 public void dataGET(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
187 @Suspended final AsyncResponse ar) {
188 completeDataGET(server.dataGET(identifier, QueryParams.newDataGetParams(uriInfo)), ar);
191 private static void completeDataGET(final RestconfFuture<DataGetResult> future, final AsyncResponse ar) {
192 future.addCallback(new JaxRsRestconfCallback<>(ar) {
194 Response transform(final DataGetResult result) {
195 final var builder = Response.status(Status.OK)
196 .entity(result.payload())
197 .cacheControl(NO_CACHE);
198 fillConfigurationMetadata(builder, result);
199 return builder.build();
204 private static void fillConfigurationMetadata(final ResponseBuilder builder, final ConfigurationMetadata metadata) {
205 final var etag = metadata.entityTag();
207 builder.tag(new EntityTag(etag.value(), etag.weak()));
209 final var lastModified = metadata.lastModified();
210 if (lastModified != null) {
211 builder.lastModified(Date.from(lastModified));
216 * Partially modify the target data store, as defined in
217 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
219 * @param body data node for put to config DS
220 * @param ar {@link AsyncResponse} which needs to be completed
225 MediaTypes.APPLICATION_YANG_DATA_XML,
226 MediaType.APPLICATION_XML,
229 public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
230 try (var xmlBody = new XmlResourceBody(body)) {
231 completeDataPATCH(server.dataPATCH(xmlBody), ar);
236 * Partially modify the target data resource, as defined in
237 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
239 * @param identifier path to target
240 * @param body data node for put to config DS
241 * @param ar {@link AsyncResponse} which needs to be completed
244 @Path("/data/{identifier:.+}")
246 MediaTypes.APPLICATION_YANG_DATA_XML,
247 MediaType.APPLICATION_XML,
250 public void dataXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
251 @Suspended final AsyncResponse ar) {
252 try (var xmlBody = new XmlResourceBody(body)) {
253 completeDataPATCH(server.dataPATCH(identifier, xmlBody), ar);
258 * Partially modify the target data store, as defined in
259 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
261 * @param body data node for put to config DS
262 * @param ar {@link AsyncResponse} which needs to be completed
267 MediaTypes.APPLICATION_YANG_DATA_JSON,
268 MediaType.APPLICATION_JSON,
270 public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
271 try (var jsonBody = new JsonResourceBody(body)) {
272 completeDataPATCH(server.dataPATCH(jsonBody), ar);
277 * Partially modify the target data resource, as defined in
278 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
280 * @param identifier path to target
281 * @param body data node for put to config DS
282 * @param ar {@link AsyncResponse} which needs to be completed
285 @Path("/data/{identifier:.+}")
287 MediaTypes.APPLICATION_YANG_DATA_JSON,
288 MediaType.APPLICATION_JSON,
290 public void dataJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
291 @Suspended final AsyncResponse ar) {
292 try (var jsonBody = new JsonResourceBody(body)) {
293 completeDataPATCH(server.dataPATCH(identifier, jsonBody), ar);
297 private static void completeDataPATCH(final RestconfFuture<DataPatchResult> future, final AsyncResponse ar) {
298 future.addCallback(new JaxRsRestconfCallback<>(ar) {
300 Response transform(final DataPatchResult result) {
301 final var builder = Response.ok();
302 fillConfigurationMetadata(builder, result);
303 return builder.build();
309 * Ordered list of edits that are applied to the datastore by the server, as defined in
310 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
312 * @param body YANG Patch body
313 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
317 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
319 MediaTypes.APPLICATION_YANG_DATA_JSON,
320 MediaTypes.APPLICATION_YANG_DATA_XML
322 public void dataYangJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
323 try (var jsonBody = new JsonPatchBody(body)) {
324 completeDataYangPATCH(server.dataPATCH(jsonBody), ar);
329 * Ordered list of edits that are applied to the target datastore by the server, as defined in
330 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
332 * @param identifier path to target
333 * @param body YANG Patch body
334 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
337 @Path("/data/{identifier:.+}")
338 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
340 MediaTypes.APPLICATION_YANG_DATA_JSON,
341 MediaTypes.APPLICATION_YANG_DATA_XML
343 public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
344 @Suspended final AsyncResponse ar) {
345 try (var jsonBody = new JsonPatchBody(body)) {
346 completeDataYangPATCH(server.dataPATCH(identifier, jsonBody), ar);
351 * Ordered list of edits that are applied to the datastore by the server, as defined in
352 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
354 * @param body YANG Patch body
355 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
359 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
361 MediaTypes.APPLICATION_YANG_DATA_JSON,
362 MediaTypes.APPLICATION_YANG_DATA_XML
364 public void dataYangXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
365 try (var xmlBody = new XmlPatchBody(body)) {
366 completeDataYangPATCH(server.dataPATCH(xmlBody), ar);
371 * Ordered list of edits that are applied to the target datastore by the server, as defined in
372 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
374 * @param identifier path to target
375 * @param body YANG Patch body
376 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
379 @Path("/data/{identifier:.+}")
380 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
382 MediaTypes.APPLICATION_YANG_DATA_JSON,
383 MediaTypes.APPLICATION_YANG_DATA_XML
385 public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
386 @Suspended final AsyncResponse ar) {
387 try (var xmlBody = new XmlPatchBody(body)) {
388 completeDataYangPATCH(server.dataPATCH(identifier, xmlBody), ar);
392 private static void completeDataYangPATCH(final RestconfFuture<DataYangPatchResult> future,
393 final AsyncResponse ar) {
394 future.addCallback(new JaxRsRestconfCallback<>(ar) {
396 Response transform(final DataYangPatchResult result) {
397 final var status = result.status();
398 final var builder = Response.status(statusOf(status)).entity(status);
399 fillConfigurationMetadata(builder, result);
400 return builder.build();
403 private static Status statusOf(final PatchStatusContext result) {
407 final var globalErrors = result.globalErrors();
408 if (globalErrors != null && !globalErrors.isEmpty()) {
409 return statusOfFirst(globalErrors);
411 for (var edit : result.editCollection()) {
413 final var editErrors = edit.getEditErrors();
414 if (editErrors != null && !editErrors.isEmpty()) {
415 return statusOfFirst(editErrors);
419 return Status.INTERNAL_SERVER_ERROR;
422 private static Status statusOfFirst(final List<RestconfError> error) {
423 return ErrorTags.statusOf(error.get(0).getErrorTag());
429 * Create a top-level data resource.
431 * @param body data node for put to config DS
432 * @param uriInfo URI info
433 * @param ar {@link AsyncResponse} which needs to be completed
438 MediaTypes.APPLICATION_YANG_DATA_JSON,
439 MediaType.APPLICATION_JSON,
441 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
442 @Suspended final AsyncResponse ar) {
443 try (var jsonBody = new JsonChildBody(body)) {
444 completeDataPOST(server.dataPOST(jsonBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
449 * Create a data resource in target.
451 * @param identifier path to target
452 * @param body data node for put to config DS
453 * @param uriInfo URI info
454 * @param ar {@link AsyncResponse} which needs to be completed
457 @Path("/data/{identifier:.+}")
459 MediaTypes.APPLICATION_YANG_DATA_JSON,
460 MediaType.APPLICATION_JSON,
462 public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
463 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
464 completeDataPOST(server.dataPOST(identifier, new JsonDataPostBody(body), QueryParams.normalize(uriInfo)),
469 * Create a top-level data resource.
471 * @param body data node for put to config DS
472 * @param uriInfo URI info
473 * @param ar {@link AsyncResponse} which needs to be completed
478 MediaTypes.APPLICATION_YANG_DATA_XML,
479 MediaType.APPLICATION_XML,
482 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
483 try (var xmlBody = new XmlChildBody(body)) {
484 completeDataPOST(server.dataPOST(xmlBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
489 * Create a data resource in target.
491 * @param identifier path to target
492 * @param body data node for put to config DS
493 * @param uriInfo URI info
494 * @param ar {@link AsyncResponse} which needs to be completed
497 @Path("/data/{identifier:.+}")
499 MediaTypes.APPLICATION_YANG_DATA_XML,
500 MediaType.APPLICATION_XML,
503 public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
504 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
505 completeDataPOST(server.dataPOST(identifier, new XmlDataPostBody(body), QueryParams.normalize(uriInfo)),
509 private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future, final UriInfo uriInfo,
510 final AsyncResponse ar) {
511 future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
513 Response transform(final DataPostResult result) {
514 if (result instanceof CreateResource createResource) {
515 final var builder = Response.created(uriInfo.getBaseUriBuilder()
517 .path(createResource.createdPath())
519 fillConfigurationMetadata(builder, createResource);
520 return builder.build();
522 if (result instanceof InvokeOperation invokeOperation) {
523 final var output = invokeOperation.output();
524 return output == null ? Response.status(Status.NO_CONTENT).build()
525 : Response.status(Status.OK).entity(output).build();
527 LOG.error("Unhandled result {}", result);
528 return Response.serverError().build();
534 * Replace the data store.
536 * @param uriInfo request URI information
537 * @param body data node for put to config DS
538 * @param ar {@link AsyncResponse} which needs to be completed
543 MediaTypes.APPLICATION_YANG_DATA_JSON,
544 MediaType.APPLICATION_JSON,
546 public void dataJsonPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
547 try (var jsonBody = new JsonResourceBody(body)) {
548 completeDataPUT(server.dataPUT(jsonBody, QueryParams.normalize(uriInfo)), ar);
553 * Create or replace the target data resource.
555 * @param identifier path to target
556 * @param uriInfo request URI information
557 * @param body data node for put to config DS
558 * @param ar {@link AsyncResponse} which needs to be completed
561 @Path("/data/{identifier:.+}")
563 MediaTypes.APPLICATION_YANG_DATA_JSON,
564 MediaType.APPLICATION_JSON,
566 public void dataJsonPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
567 final InputStream body, @Suspended final AsyncResponse ar) {
568 try (var jsonBody = new JsonResourceBody(body)) {
569 completeDataPUT(server.dataPUT(identifier, jsonBody, QueryParams.normalize(uriInfo)), ar);
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_XML,
584 MediaType.APPLICATION_XML,
587 public void dataXmlPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
588 try (var xmlBody = new XmlResourceBody(body)) {
589 completeDataPUT(server.dataPUT(xmlBody, QueryParams.normalize(uriInfo)), ar);
594 * Create or replace the target data resource.
596 * @param identifier path to target
597 * @param uriInfo request URI information
598 * @param body data node for put to config DS
599 * @param ar {@link AsyncResponse} which needs to be completed
602 @Path("/data/{identifier:.+}")
604 MediaTypes.APPLICATION_YANG_DATA_XML,
605 MediaType.APPLICATION_XML,
608 public void dataXmlPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
609 final InputStream body, @Suspended final AsyncResponse ar) {
610 try (var xmlBody = new XmlResourceBody(body)) {
611 completeDataPUT(server.dataPUT(identifier, xmlBody, QueryParams.normalize(uriInfo)), ar);
615 private static void completeDataPUT(final RestconfFuture<DataPutResult> future, final AsyncResponse ar) {
616 future.addCallback(new JaxRsRestconfCallback<>(ar) {
618 Response transform(final DataPutResult result) {
619 // Note: no Location header, as it matches the request path
620 final var builder = result.created() ? Response.status(Status.CREATED) : Response.noContent();
621 fillConfigurationMetadata(builder, result);
622 return builder.build();
628 * List RPC and action operations in RFC7951 format.
630 * @param ar {@link AsyncResponse} which needs to be completed
634 @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
635 public void operationsJsonGET(@Suspended final AsyncResponse ar) {
636 completeOperationsJsonGet(server.operationsGET(), ar);
640 * Retrieve list of operations and actions supported by the server or device in JSON format.
642 * @param operation path parameter to identify device and/or operation
643 * @param ar {@link AsyncResponse} which needs to be completed
646 @Path("/operations/{operation:.+}")
647 @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
648 public void operationsJsonGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
649 completeOperationsGet(server.operationsGET(operation), ar, OperationsGetResult::toJSON);
652 private static void completeOperationsJsonGet(final RestconfFuture<OperationsGetResult> future,
653 final AsyncResponse ar) {
654 completeOperationsGet(future, ar, OperationsGetResult::toJSON);
658 * List RPC and action operations in RFC8040 XML format.
660 * @param ar {@link AsyncResponse} which needs to be completed
664 @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
665 public void operationsXmlGET(@Suspended final AsyncResponse ar) {
666 completeOperationsXmlGet(server.operationsGET(), ar);
670 * Retrieve list of operations and actions supported by the server or device in XML format.
672 * @param operation path parameter to identify device and/or operation
673 * @param ar {@link AsyncResponse} which needs to be completed
676 @Path("/operations/{operation:.+}")
677 @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
678 public void operationsXmlGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
679 completeOperationsXmlGet(server.operationsGET(operation), ar);
682 private static void completeOperationsXmlGet(final RestconfFuture<OperationsGetResult> future,
683 final AsyncResponse ar) {
684 completeOperationsGet(future, ar, OperationsGetResult::toXML);
687 private static void completeOperationsGet(final RestconfFuture<OperationsGetResult> future, final AsyncResponse ar,
688 final Function<OperationsGetResult, String> toString) {
689 future.addCallback(new JaxRsRestconfCallback<OperationsGetResult>(ar) {
691 Response transform(final OperationsGetResult result) {
692 return Response.ok().entity(toString.apply(result)).build();
698 * Invoke RPC operation.
700 * @param identifier module name and rpc identifier string for the desired operation
701 * @param body the body of the operation
702 * @param uriInfo URI info
703 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
706 // FIXME: identifier is just a *single* QName
707 @Path("/operations/{identifier:.+}")
709 MediaTypes.APPLICATION_YANG_DATA_XML,
710 MediaType.APPLICATION_XML,
714 MediaTypes.APPLICATION_YANG_DATA_JSON,
715 MediaTypes.APPLICATION_YANG_DATA_XML,
716 MediaType.APPLICATION_JSON,
717 MediaType.APPLICATION_XML,
720 public void operationsXmlPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
721 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
722 try (var xmlBody = new XmlOperationInputBody(body)) {
723 operationsPOST(identifier, uriInfo, ar, xmlBody);
728 * Invoke RPC operation.
730 * @param identifier module name and rpc identifier string for the desired operation
731 * @param body the body of the operation
732 * @param uriInfo URI info
733 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
736 // FIXME: identifier is just a *single* QName
737 @Path("/operations/{identifier:.+}")
739 MediaTypes.APPLICATION_YANG_DATA_JSON,
740 MediaType.APPLICATION_JSON,
743 MediaTypes.APPLICATION_YANG_DATA_JSON,
744 MediaTypes.APPLICATION_YANG_DATA_XML,
745 MediaType.APPLICATION_JSON,
746 MediaType.APPLICATION_XML,
749 public void operationsJsonPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
750 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
751 try (var jsonBody = new JsonOperationInputBody(body)) {
752 operationsPOST(identifier, uriInfo, ar, jsonBody);
756 private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
757 final OperationInputBody body) {
758 server.operationsPOST(uriInfo.getBaseUri(), identifier, body)
759 .addCallback(new JaxRsRestconfCallback<OperationsPostResult>(ar) {
761 Response transform(final OperationsPostResult result) {
762 final var body = result.output();
763 return body == null ? Response.noContent().build()
764 : Response.ok().entity(new NormalizedNodePayload(result.path().inference(), body)).build();
770 * Get revision of IETF YANG Library module.
772 * @param ar {@link AsyncResponse} which needs to be completed
775 @Path("/yang-library-version")
777 MediaTypes.APPLICATION_YANG_DATA_JSON,
778 MediaTypes.APPLICATION_YANG_DATA_XML,
779 MediaType.APPLICATION_JSON,
780 MediaType.APPLICATION_XML,
783 public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
784 server.yangLibraryVersionGET().addCallback(new JaxRsRestconfCallback<NormalizedNodePayload>(ar) {
786 Response transform(final NormalizedNodePayload result) {
787 return Response.ok().entity(result).build();
792 // FIXME: References to these resources are generated by our yang-library implementation. That means:
793 // - We really need to formalize the parameter structure so we get some help from JAX-RS during matching
795 // - optional yang-ext:mount prefix(es)
796 // - mandatory module name
797 // - optional module revision
798 // - We really should use /yang-library-module/{name}(/{revision})?
799 // - We seem to be lacking explicit support for submodules in there -- and those locations should then point
800 // to /yang-library-submodule/{moduleName}(/{moduleRevision})?/{name}(/{revision})? so as to look the
801 // submodule up efficiently and allow for the weird case where there are two submodules with the same name
802 // (that is currently not supported by the parser, but it will be in the future)
803 // - It does not make sense to support yang-ext:mount, unless we also intercept mount points and rewrite
804 // yang-library locations. We most likely want to do that to ensure users are not tempted to connect to
808 * Get schema of specific module.
810 * @param fileName source file name
811 * @param revision source revision
812 * @param ar {@link AsyncResponse} which needs to be completed
815 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
816 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
817 public void modulesYangGET(@PathParam("fileName") final String fileName,
818 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
819 completeModulesGET(server.modulesYangGET(fileName, revision), ar);
823 * Get schema of specific module.
825 * @param mountPath mount point path
826 * @param fileName source file name
827 * @param revision source revision
828 * @param ar {@link AsyncResponse} which needs to be completed
831 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
832 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
833 public void modulesYangGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
834 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
835 @Suspended final AsyncResponse ar) {
836 completeModulesGET(server.modulesYangGET(mountPath, fileName, revision), ar);
840 * Get schema of specific module.
842 * @param fileName source file name
843 * @param revision source revision
844 * @param ar {@link AsyncResponse} which needs to be completed
847 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
848 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
849 public void modulesYinGET(@PathParam("fileName") final String fileName,
850 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
851 completeModulesGET(server.modulesYinGET(fileName, revision), ar);
855 * Get schema of specific module.
857 * @param mountPath mount point path
858 * @param fileName source file name
859 * @param revision source revision
860 * @param ar {@link AsyncResponse} which needs to be completed
863 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
864 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
865 public void modulesYinGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
866 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
867 @Suspended final AsyncResponse ar) {
868 completeModulesGET(server.modulesYinGET(mountPath, fileName, revision), ar);
871 private static void completeModulesGET(final RestconfFuture<ModulesGetResult> future, final AsyncResponse ar) {
872 future.addCallback(new JaxRsRestconfCallback<>(ar) {
874 Response transform(final ModulesGetResult result) {
877 reader = result.source().openStream();
878 } catch (IOException e) {
879 throw new RestconfDocumentedException("Cannot open source", e);
881 return Response.ok(reader).build();