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.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.MediaTypes;
48 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
49 import org.opendaylight.restconf.common.errors.RestconfError;
50 import org.opendaylight.restconf.common.errors.RestconfFuture;
51 import org.opendaylight.restconf.common.patch.PatchStatusContext;
52 import org.opendaylight.restconf.nb.rfc8040.URLConstants;
53 import org.opendaylight.restconf.nb.rfc8040.databind.JsonChildBody;
54 import org.opendaylight.restconf.nb.rfc8040.databind.JsonDataPostBody;
55 import org.opendaylight.restconf.nb.rfc8040.databind.JsonOperationInputBody;
56 import org.opendaylight.restconf.nb.rfc8040.databind.JsonPatchBody;
57 import org.opendaylight.restconf.nb.rfc8040.databind.JsonResourceBody;
58 import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
59 import org.opendaylight.restconf.nb.rfc8040.databind.XmlChildBody;
60 import org.opendaylight.restconf.nb.rfc8040.databind.XmlDataPostBody;
61 import org.opendaylight.restconf.nb.rfc8040.databind.XmlOperationInputBody;
62 import org.opendaylight.restconf.nb.rfc8040.databind.XmlPatchBody;
63 import org.opendaylight.restconf.nb.rfc8040.databind.XmlResourceBody;
64 import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
65 import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
66 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
67 import org.opendaylight.restconf.server.api.DataGetResult;
68 import org.opendaylight.restconf.server.api.DataPostResult;
69 import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
70 import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
71 import org.opendaylight.restconf.server.api.DataPutResult;
72 import org.opendaylight.restconf.server.api.ModulesGetResult;
73 import org.opendaylight.restconf.server.api.OperationsGetResult;
74 import org.opendaylight.restconf.server.api.OperationsPostResult;
75 import org.opendaylight.restconf.server.api.RestconfServer;
76 import org.opendaylight.yangtools.yang.common.Empty;
77 import org.opendaylight.yangtools.yang.common.YangConstants;
78 import org.slf4j.Logger;
79 import org.slf4j.LoggerFactory;
82 * Baseline RESTCONF implementation with JAX-RS. Interfaces to a {@link RestconfServer}. Since we need {@link ApiPath}
83 * arguments, we also implement {@link ParamConverterProvider} and provide the appropriate converter. This has the nice
84 * side-effect of suppressing <a href="https://github.com/eclipse-ee4j/jersey/issues/3700">Jersey warnings</a>.
88 public final class JaxRsRestconf implements ParamConverterProvider {
89 private static final Logger LOG = LoggerFactory.getLogger(JaxRsRestconf.class);
90 private static final CacheControl NO_CACHE = CacheControl.valueOf("no-cache");
91 private static final ParamConverter<ApiPath> API_PATH_CONVERTER = new ParamConverter<>() {
93 public ApiPath fromString(final String value) {
94 final var str = nonnull(value);
96 return ApiPath.parseUrl(str);
97 } catch (ParseException e) {
98 throw new IllegalArgumentException(e.getMessage(), e);
103 public String toString(final ApiPath value) {
104 return nonnull(value).toString();
107 private static <T> @NonNull T nonnull(final @Nullable T value) {
109 throw new IllegalArgumentException("value must not be null");
115 private final RestconfServer server;
117 public JaxRsRestconf(final RestconfServer server) {
118 this.server = requireNonNull(server);
122 @SuppressWarnings("unchecked")
123 public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType,
124 final Annotation[] annotations) {
125 return ApiPath.class.equals(rawType) ? (ParamConverter<T>) API_PATH_CONVERTER : null;
129 * Delete the target data resource.
131 * @param identifier path to target
132 * @param ar {@link AsyncResponse} which needs to be completed
135 @Path("/data/{identifier:.+}")
136 @SuppressWarnings("checkstyle:abbreviationAsWordInName")
137 public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier,
138 @Suspended final AsyncResponse ar) {
139 server.dataDELETE(identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
141 Response transform(final Empty result) {
142 return Response.noContent().build();
148 * Get target data resource from data root.
150 * @param uriInfo URI info
151 * @param ar {@link AsyncResponse} which needs to be completed
156 MediaTypes.APPLICATION_YANG_DATA_JSON,
157 MediaTypes.APPLICATION_YANG_DATA_XML,
158 MediaType.APPLICATION_JSON,
159 MediaType.APPLICATION_XML,
162 public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
163 completeDataGET(server.dataGET(QueryParams.newDataGetParams(uriInfo)), ar);
167 * Get target data resource.
169 * @param identifier path to target
170 * @param uriInfo URI info
171 * @param ar {@link AsyncResponse} which needs to be completed
174 @Path("/data/{identifier:.+}")
176 MediaTypes.APPLICATION_YANG_DATA_JSON,
177 MediaTypes.APPLICATION_YANG_DATA_XML,
178 MediaType.APPLICATION_JSON,
179 MediaType.APPLICATION_XML,
182 public void dataGET(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
183 @Suspended final AsyncResponse ar) {
184 completeDataGET(server.dataGET(identifier, QueryParams.newDataGetParams(uriInfo)), ar);
187 private static void completeDataGET(final RestconfFuture<DataGetResult> future, final AsyncResponse ar) {
188 future.addCallback(new JaxRsRestconfCallback<>(ar) {
190 Response transform(final DataGetResult result) {
191 final var builder = Response.status(Status.OK)
192 .entity(result.payload())
193 .cacheControl(NO_CACHE);
194 final var etag = result.entityTag();
196 builder.tag(new EntityTag(etag.value(), etag.weak()));
198 final var lastModified = result.lastModified();
199 if (lastModified != null) {
200 builder.lastModified(Date.from(lastModified));
202 return builder.build();
208 * Partially modify the target data store, as defined in
209 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
211 * @param body data node for put to config DS
212 * @param ar {@link AsyncResponse} which needs to be completed
217 MediaTypes.APPLICATION_YANG_DATA_XML,
218 MediaType.APPLICATION_XML,
221 public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
222 try (var xmlBody = new XmlResourceBody(body)) {
223 completeDataPATCH(server.dataPATCH(xmlBody), ar);
228 * Partially modify the target data resource, as defined in
229 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
231 * @param identifier path to target
232 * @param body data node for put to config DS
233 * @param ar {@link AsyncResponse} which needs to be completed
236 @Path("/data/{identifier:.+}")
238 MediaTypes.APPLICATION_YANG_DATA_XML,
239 MediaType.APPLICATION_XML,
242 public void dataXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
243 @Suspended final AsyncResponse ar) {
244 try (var xmlBody = new XmlResourceBody(body)) {
245 completeDataPATCH(server.dataPATCH(identifier, xmlBody), ar);
250 * Partially modify the target data store, as defined in
251 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
253 * @param body data node for put to config DS
254 * @param ar {@link AsyncResponse} which needs to be completed
259 MediaTypes.APPLICATION_YANG_DATA_JSON,
260 MediaType.APPLICATION_JSON,
262 public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
263 try (var jsonBody = new JsonResourceBody(body)) {
264 completeDataPATCH(server.dataPATCH(jsonBody), ar);
269 * Partially modify the target data resource, as defined in
270 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
272 * @param identifier path to target
273 * @param body data node for put to config DS
274 * @param ar {@link AsyncResponse} which needs to be completed
277 @Path("/data/{identifier:.+}")
279 MediaTypes.APPLICATION_YANG_DATA_JSON,
280 MediaType.APPLICATION_JSON,
282 public void dataJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
283 @Suspended final AsyncResponse ar) {
284 try (var jsonBody = new JsonResourceBody(body)) {
285 completeDataPATCH(server.dataPATCH(identifier, jsonBody), ar);
289 private static void completeDataPATCH(final RestconfFuture<Empty> future, final AsyncResponse ar) {
290 future.addCallback(new JaxRsRestconfCallback<>(ar) {
292 Response transform(final Empty result) {
293 return Response.ok().build();
299 * Ordered list of edits that are applied to the datastore by the server, as defined in
300 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
302 * @param body YANG Patch body
303 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
307 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
309 MediaTypes.APPLICATION_YANG_DATA_JSON,
310 MediaTypes.APPLICATION_YANG_DATA_XML
312 public void dataYangJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
313 try (var jsonBody = new JsonPatchBody(body)) {
314 completeDataYangPATCH(server.dataPATCH(jsonBody), ar);
319 * Ordered list of edits that are applied to the target datastore by the server, as defined in
320 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
322 * @param identifier path to target
323 * @param body YANG Patch body
324 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
327 @Path("/data/{identifier:.+}")
328 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
330 MediaTypes.APPLICATION_YANG_DATA_JSON,
331 MediaTypes.APPLICATION_YANG_DATA_XML
333 public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
334 @Suspended final AsyncResponse ar) {
335 try (var jsonBody = new JsonPatchBody(body)) {
336 completeDataYangPATCH(server.dataPATCH(identifier, jsonBody), ar);
341 * Ordered list of edits that are applied to the datastore by the server, as defined in
342 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
344 * @param body YANG Patch body
345 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
349 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
351 MediaTypes.APPLICATION_YANG_DATA_JSON,
352 MediaTypes.APPLICATION_YANG_DATA_XML
354 public void dataYangXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
355 try (var xmlBody = new XmlPatchBody(body)) {
356 completeDataYangPATCH(server.dataPATCH(xmlBody), ar);
361 * Ordered list of edits that are applied to the target datastore by the server, as defined in
362 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
364 * @param identifier path to target
365 * @param body YANG Patch body
366 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
369 @Path("/data/{identifier:.+}")
370 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
372 MediaTypes.APPLICATION_YANG_DATA_JSON,
373 MediaTypes.APPLICATION_YANG_DATA_XML
375 public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
376 @Suspended final AsyncResponse ar) {
377 try (var xmlBody = new XmlPatchBody(body)) {
378 completeDataYangPATCH(server.dataPATCH(identifier, xmlBody), ar);
382 private static void completeDataYangPATCH(final RestconfFuture<PatchStatusContext> future, final AsyncResponse ar) {
383 future.addCallback(new JaxRsRestconfCallback<>(ar) {
385 Response transform(final PatchStatusContext result) {
386 return Response.status(statusOf(result)).entity(result).build();
389 private static Status statusOf(final PatchStatusContext result) {
393 final var globalErrors = result.globalErrors();
394 if (globalErrors != null && !globalErrors.isEmpty()) {
395 return statusOfFirst(globalErrors);
397 for (var edit : result.editCollection()) {
399 final var editErrors = edit.getEditErrors();
400 if (editErrors != null && !editErrors.isEmpty()) {
401 return statusOfFirst(editErrors);
405 return Status.INTERNAL_SERVER_ERROR;
408 private static Status statusOfFirst(final List<RestconfError> error) {
409 return ErrorTags.statusOf(error.get(0).getErrorTag());
415 * Create a top-level data resource.
417 * @param body data node for put to config DS
418 * @param uriInfo URI info
419 * @param ar {@link AsyncResponse} which needs to be completed
424 MediaTypes.APPLICATION_YANG_DATA_JSON,
425 MediaType.APPLICATION_JSON,
427 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
428 @Suspended final AsyncResponse ar) {
429 try (var jsonBody = new JsonChildBody(body)) {
430 completeDataPOST(server.dataPOST(jsonBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
435 * Create a data resource in target.
437 * @param identifier path to target
438 * @param body data node for put to config DS
439 * @param uriInfo URI info
440 * @param ar {@link AsyncResponse} which needs to be completed
443 @Path("/data/{identifier:.+}")
445 MediaTypes.APPLICATION_YANG_DATA_JSON,
446 MediaType.APPLICATION_JSON,
448 public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
449 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
450 completeDataPOST(server.dataPOST(identifier, new JsonDataPostBody(body), QueryParams.normalize(uriInfo)),
455 * Create a top-level data resource.
457 * @param body data node for put to config DS
458 * @param uriInfo URI info
459 * @param ar {@link AsyncResponse} which needs to be completed
464 MediaTypes.APPLICATION_YANG_DATA_XML,
465 MediaType.APPLICATION_XML,
468 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
469 try (var xmlBody = new XmlChildBody(body)) {
470 completeDataPOST(server.dataPOST(xmlBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
475 * Create a data resource in target.
477 * @param identifier path to target
478 * @param body data node for put to config DS
479 * @param uriInfo URI info
480 * @param ar {@link AsyncResponse} which needs to be completed
483 @Path("/data/{identifier:.+}")
485 MediaTypes.APPLICATION_YANG_DATA_XML,
486 MediaType.APPLICATION_XML,
489 public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
490 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
491 completeDataPOST(server.dataPOST(identifier, new XmlDataPostBody(body), QueryParams.normalize(uriInfo)),
495 private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future, final UriInfo uriInfo,
496 final AsyncResponse ar) {
497 future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
499 Response transform(final DataPostResult result) {
500 if (result instanceof CreateResource createResource) {
501 return Response.created(uriInfo.getBaseUriBuilder()
503 .path(createResource.createdPath())
507 if (result instanceof InvokeOperation invokeOperation) {
508 final var output = invokeOperation.output();
509 return output == null ? Response.status(Status.NO_CONTENT).build()
510 : Response.status(Status.OK).entity(output).build();
512 LOG.error("Unhandled result {}", result);
513 return Response.serverError().build();
519 * Replace the data store.
521 * @param uriInfo request URI information
522 * @param body data node for put to config DS
523 * @param ar {@link AsyncResponse} which needs to be completed
528 MediaTypes.APPLICATION_YANG_DATA_JSON,
529 MediaType.APPLICATION_JSON,
531 public void dataJsonPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
532 try (var jsonBody = new JsonResourceBody(body)) {
533 completeDataPUT(server.dataPUT(jsonBody, QueryParams.normalize(uriInfo)), ar);
538 * Create or replace the target data resource.
540 * @param identifier path to target
541 * @param uriInfo request URI information
542 * @param body data node for put to config DS
543 * @param ar {@link AsyncResponse} which needs to be completed
546 @Path("/data/{identifier:.+}")
548 MediaTypes.APPLICATION_YANG_DATA_JSON,
549 MediaType.APPLICATION_JSON,
551 public void dataJsonPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
552 final InputStream body, @Suspended final AsyncResponse ar) {
553 try (var jsonBody = new JsonResourceBody(body)) {
554 completeDataPUT(server.dataPUT(identifier, jsonBody, QueryParams.normalize(uriInfo)), ar);
559 * Replace the data store.
561 * @param uriInfo request URI information
562 * @param body data node for put to config DS
563 * @param ar {@link AsyncResponse} which needs to be completed
568 MediaTypes.APPLICATION_YANG_DATA_XML,
569 MediaType.APPLICATION_XML,
572 public void dataXmlPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
573 try (var xmlBody = new XmlResourceBody(body)) {
574 completeDataPUT(server.dataPUT(xmlBody, QueryParams.normalize(uriInfo)), ar);
579 * Create or replace the target data resource.
581 * @param identifier path to target
582 * @param uriInfo request URI information
583 * @param body data node for put to config DS
584 * @param ar {@link AsyncResponse} which needs to be completed
587 @Path("/data/{identifier:.+}")
589 MediaTypes.APPLICATION_YANG_DATA_XML,
590 MediaType.APPLICATION_XML,
593 public void dataXmlPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
594 final InputStream body, @Suspended final AsyncResponse ar) {
595 try (var xmlBody = new XmlResourceBody(body)) {
596 completeDataPUT(server.dataPUT(identifier, xmlBody, QueryParams.normalize(uriInfo)), ar);
600 private static void completeDataPUT(final RestconfFuture<DataPutResult> future, final AsyncResponse ar) {
601 future.addCallback(new JaxRsRestconfCallback<>(ar) {
603 Response transform(final DataPutResult result) {
604 return switch (result) {
605 // Note: no Location header, as it matches the request path
606 case CREATED -> Response.status(Status.CREATED).build();
607 case REPLACED -> Response.noContent().build();
614 * List RPC and action operations in RFC7951 format.
616 * @param ar {@link AsyncResponse} which needs to be completed
620 @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
621 public void operationsJsonGET(@Suspended final AsyncResponse ar) {
622 completeOperationsJsonGet(server.operationsGET(), ar);
626 * Retrieve list of operations and actions supported by the server or device in JSON format.
628 * @param operation path parameter to identify device and/or operation
629 * @param ar {@link AsyncResponse} which needs to be completed
632 @Path("/operations/{operation:.+}")
633 @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
634 public void operationsJsonGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
635 completeOperationsGet(server.operationsGET(operation), ar, OperationsGetResult::toJSON);
638 private static void completeOperationsJsonGet(final RestconfFuture<OperationsGetResult> future,
639 final AsyncResponse ar) {
640 completeOperationsGet(future, ar, OperationsGetResult::toJSON);
644 * List RPC and action operations in RFC8040 XML format.
646 * @param ar {@link AsyncResponse} which needs to be completed
650 @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
651 public void operationsXmlGET(@Suspended final AsyncResponse ar) {
652 completeOperationsXmlGet(server.operationsGET(), ar);
656 * Retrieve list of operations and actions supported by the server or device in XML format.
658 * @param operation path parameter to identify device and/or operation
659 * @param ar {@link AsyncResponse} which needs to be completed
662 @Path("/operations/{operation:.+}")
663 @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
664 public void operationsXmlGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
665 completeOperationsXmlGet(server.operationsGET(operation), ar);
668 private static void completeOperationsXmlGet(final RestconfFuture<OperationsGetResult> future,
669 final AsyncResponse ar) {
670 completeOperationsGet(future, ar, OperationsGetResult::toXML);
673 private static void completeOperationsGet(final RestconfFuture<OperationsGetResult> future, final AsyncResponse ar,
674 final Function<OperationsGetResult, String> toString) {
675 future.addCallback(new JaxRsRestconfCallback<OperationsGetResult>(ar) {
677 Response transform(final OperationsGetResult result) {
678 return Response.ok().entity(toString.apply(result)).build();
684 * Invoke RPC operation.
686 * @param identifier module name and rpc identifier string for the desired operation
687 * @param body the body of the operation
688 * @param uriInfo URI info
689 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
692 // FIXME: identifier is just a *single* QName
693 @Path("/operations/{identifier:.+}")
695 MediaTypes.APPLICATION_YANG_DATA_XML,
696 MediaType.APPLICATION_XML,
700 MediaTypes.APPLICATION_YANG_DATA_JSON,
701 MediaTypes.APPLICATION_YANG_DATA_XML,
702 MediaType.APPLICATION_JSON,
703 MediaType.APPLICATION_XML,
706 public void operationsXmlPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
707 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
708 try (var xmlBody = new XmlOperationInputBody(body)) {
709 operationsPOST(identifier, uriInfo, ar, xmlBody);
714 * Invoke RPC operation.
716 * @param identifier module name and rpc identifier string for the desired operation
717 * @param body the body of the operation
718 * @param uriInfo URI info
719 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
722 // FIXME: identifier is just a *single* QName
723 @Path("/operations/{identifier:.+}")
725 MediaTypes.APPLICATION_YANG_DATA_JSON,
726 MediaType.APPLICATION_JSON,
729 MediaTypes.APPLICATION_YANG_DATA_JSON,
730 MediaTypes.APPLICATION_YANG_DATA_XML,
731 MediaType.APPLICATION_JSON,
732 MediaType.APPLICATION_XML,
735 public void operationsJsonPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
736 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
737 try (var jsonBody = new JsonOperationInputBody(body)) {
738 operationsPOST(identifier, uriInfo, ar, jsonBody);
742 private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
743 final OperationInputBody body) {
744 server.operationsPOST(uriInfo.getBaseUri(), identifier, body)
745 .addCallback(new JaxRsRestconfCallback<OperationsPostResult>(ar) {
747 Response transform(final OperationsPostResult result) {
748 final var body = result.output();
749 return body == null ? Response.noContent().build()
750 : Response.ok().entity(new NormalizedNodePayload(result.operation(), body)).build();
756 * Get revision of IETF YANG Library module.
758 * @param ar {@link AsyncResponse} which needs to be completed
761 @Path("/yang-library-version")
763 MediaTypes.APPLICATION_YANG_DATA_JSON,
764 MediaTypes.APPLICATION_YANG_DATA_XML,
765 MediaType.APPLICATION_JSON,
766 MediaType.APPLICATION_XML,
769 public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
770 server.yangLibraryVersionGET().addCallback(new JaxRsRestconfCallback<NormalizedNodePayload>(ar) {
772 Response transform(final NormalizedNodePayload result) {
773 return Response.ok().entity(result).build();
778 // FIXME: References to these resources are generated by our yang-library implementation. That means:
779 // - We really need to formalize the parameter structure so we get some help from JAX-RS during matching
781 // - optional yang-ext:mount prefix(es)
782 // - mandatory module name
783 // - optional module revision
784 // - We really should use /yang-library-module/{name}(/{revision})?
785 // - We seem to be lacking explicit support for submodules in there -- and those locations should then point
786 // to /yang-library-submodule/{moduleName}(/{moduleRevision})?/{name}(/{revision})? so as to look the
787 // submodule up efficiently and allow for the weird case where there are two submodules with the same name
788 // (that is currently not supported by the parser, but it will be in the future)
789 // - It does not make sense to support yang-ext:mount, unless we also intercept mount points and rewrite
790 // yang-library locations. We most likely want to do that to ensure users are not tempted to connect to
794 * Get schema of specific module.
796 * @param fileName source file name
797 * @param revision source revision
798 * @param ar {@link AsyncResponse} which needs to be completed
801 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
802 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
803 public void modulesYangGET(@PathParam("fileName") final String fileName,
804 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
805 completeModulesGET(server.modulesYangGET(fileName, revision), ar);
809 * Get schema of specific module.
811 * @param mountPath mount point path
812 * @param fileName source file name
813 * @param revision source revision
814 * @param ar {@link AsyncResponse} which needs to be completed
817 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
818 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
819 public void modulesYangGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
820 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
821 @Suspended final AsyncResponse ar) {
822 completeModulesGET(server.modulesYangGET(mountPath, fileName, revision), ar);
826 * Get schema of specific module.
828 * @param fileName source file name
829 * @param revision source revision
830 * @param ar {@link AsyncResponse} which needs to be completed
833 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
834 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
835 public void modulesYinGET(@PathParam("fileName") final String fileName,
836 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
837 completeModulesGET(server.modulesYinGET(fileName, revision), ar);
841 * Get schema of specific module.
843 * @param mountPath mount point path
844 * @param fileName source file name
845 * @param revision source revision
846 * @param ar {@link AsyncResponse} which needs to be completed
849 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
850 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
851 public void modulesYinGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
852 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
853 @Suspended final AsyncResponse ar) {
854 completeModulesGET(server.modulesYinGET(mountPath, fileName, revision), ar);
857 private static void completeModulesGET(final RestconfFuture<ModulesGetResult> future, final AsyncResponse ar) {
858 future.addCallback(new JaxRsRestconfCallback<>(ar) {
860 Response transform(final ModulesGetResult result) {
863 reader = result.source().openStream();
864 } catch (IOException e) {
865 throw new RestconfDocumentedException("Cannot open source", e);
867 return Response.ok(reader).build();