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.JsonChildBody;
55 import org.opendaylight.restconf.nb.rfc8040.databind.JsonDataPostBody;
56 import org.opendaylight.restconf.nb.rfc8040.databind.JsonOperationInputBody;
57 import org.opendaylight.restconf.nb.rfc8040.databind.JsonPatchBody;
58 import org.opendaylight.restconf.nb.rfc8040.databind.JsonResourceBody;
59 import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
60 import org.opendaylight.restconf.nb.rfc8040.databind.XmlChildBody;
61 import org.opendaylight.restconf.nb.rfc8040.databind.XmlDataPostBody;
62 import org.opendaylight.restconf.nb.rfc8040.databind.XmlOperationInputBody;
63 import org.opendaylight.restconf.nb.rfc8040.databind.XmlPatchBody;
64 import org.opendaylight.restconf.nb.rfc8040.databind.XmlResourceBody;
65 import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
66 import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
67 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
68 import org.opendaylight.restconf.server.api.ConfigurationMetadata;
69 import org.opendaylight.restconf.server.api.DataGetResult;
70 import org.opendaylight.restconf.server.api.DataPatchResult;
71 import org.opendaylight.restconf.server.api.DataPostResult;
72 import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
73 import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
74 import org.opendaylight.restconf.server.api.DataPutResult;
75 import org.opendaylight.restconf.server.api.ModulesGetResult;
76 import org.opendaylight.restconf.server.api.OperationsGetResult;
77 import org.opendaylight.restconf.server.api.OperationsPostResult;
78 import org.opendaylight.restconf.server.api.RestconfServer;
79 import org.opendaylight.yangtools.yang.common.Empty;
80 import org.opendaylight.yangtools.yang.common.YangConstants;
81 import org.slf4j.Logger;
82 import org.slf4j.LoggerFactory;
85 * Baseline RESTCONF implementation with JAX-RS. Interfaces to a {@link RestconfServer}. Since we need {@link ApiPath}
86 * arguments, we also implement {@link ParamConverterProvider} and provide the appropriate converter. This has the nice
87 * side-effect of suppressing <a href="https://github.com/eclipse-ee4j/jersey/issues/3700">Jersey warnings</a>.
91 public final class JaxRsRestconf implements ParamConverterProvider {
92 private static final Logger LOG = LoggerFactory.getLogger(JaxRsRestconf.class);
93 private static final CacheControl NO_CACHE = CacheControl.valueOf("no-cache");
94 private static final ParamConverter<ApiPath> API_PATH_CONVERTER = new ParamConverter<>() {
96 public ApiPath fromString(final String value) {
97 final var str = nonnull(value);
99 return ApiPath.parseUrl(str);
100 } catch (ParseException e) {
101 throw new IllegalArgumentException(e.getMessage(), e);
106 public String toString(final ApiPath value) {
107 return nonnull(value).toString();
110 private static <T> @NonNull T nonnull(final @Nullable T value) {
112 throw new IllegalArgumentException("value must not be null");
118 private final RestconfServer server;
120 public JaxRsRestconf(final RestconfServer server) {
121 this.server = requireNonNull(server);
125 @SuppressWarnings("unchecked")
126 public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType,
127 final Annotation[] annotations) {
128 return ApiPath.class.equals(rawType) ? (ParamConverter<T>) API_PATH_CONVERTER : null;
132 * Delete the target data resource.
134 * @param identifier path to target
135 * @param ar {@link AsyncResponse} which needs to be completed
138 @Path("/data/{identifier:.+}")
139 @SuppressWarnings("checkstyle:abbreviationAsWordInName")
140 public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier,
141 @Suspended final AsyncResponse ar) {
142 server.dataDELETE(identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
144 Response transform(final Empty result) {
145 return Response.noContent().build();
151 * Get target data resource from data root.
153 * @param uriInfo URI info
154 * @param ar {@link AsyncResponse} which needs to be completed
159 MediaTypes.APPLICATION_YANG_DATA_JSON,
160 MediaTypes.APPLICATION_YANG_DATA_XML,
161 MediaType.APPLICATION_JSON,
162 MediaType.APPLICATION_XML,
165 public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
166 completeDataGET(server.dataGET(QueryParams.newDataGetParams(uriInfo)), ar);
170 * Get target data resource.
172 * @param identifier path to target
173 * @param uriInfo URI info
174 * @param ar {@link AsyncResponse} which needs to be completed
177 @Path("/data/{identifier:.+}")
179 MediaTypes.APPLICATION_YANG_DATA_JSON,
180 MediaTypes.APPLICATION_YANG_DATA_XML,
181 MediaType.APPLICATION_JSON,
182 MediaType.APPLICATION_XML,
185 public void dataGET(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
186 @Suspended final AsyncResponse ar) {
187 completeDataGET(server.dataGET(identifier, QueryParams.newDataGetParams(uriInfo)), ar);
190 private static void completeDataGET(final RestconfFuture<DataGetResult> future, final AsyncResponse ar) {
191 future.addCallback(new JaxRsRestconfCallback<>(ar) {
193 Response transform(final DataGetResult result) {
194 final var builder = Response.status(Status.OK)
195 .entity(result.payload())
196 .cacheControl(NO_CACHE);
197 fillConfigurationMetadata(builder, result);
198 return builder.build();
203 private static void fillConfigurationMetadata(final ResponseBuilder builder, final ConfigurationMetadata metadata) {
204 final var etag = metadata.entityTag();
206 builder.tag(new EntityTag(etag.value(), etag.weak()));
208 final var lastModified = metadata.lastModified();
209 if (lastModified != null) {
210 builder.lastModified(Date.from(lastModified));
215 * Partially modify the target data store, as defined in
216 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
218 * @param body data node for put to config DS
219 * @param ar {@link AsyncResponse} which needs to be completed
224 MediaTypes.APPLICATION_YANG_DATA_XML,
225 MediaType.APPLICATION_XML,
228 public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
229 try (var xmlBody = new XmlResourceBody(body)) {
230 completeDataPATCH(server.dataPATCH(xmlBody), ar);
235 * Partially modify the target data resource, as defined in
236 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
238 * @param identifier path to target
239 * @param body data node for put to config DS
240 * @param ar {@link AsyncResponse} which needs to be completed
243 @Path("/data/{identifier:.+}")
245 MediaTypes.APPLICATION_YANG_DATA_XML,
246 MediaType.APPLICATION_XML,
249 public void dataXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
250 @Suspended final AsyncResponse ar) {
251 try (var xmlBody = new XmlResourceBody(body)) {
252 completeDataPATCH(server.dataPATCH(identifier, xmlBody), ar);
257 * Partially modify the target data store, as defined in
258 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
260 * @param body data node for put to config DS
261 * @param ar {@link AsyncResponse} which needs to be completed
266 MediaTypes.APPLICATION_YANG_DATA_JSON,
267 MediaType.APPLICATION_JSON,
269 public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
270 try (var jsonBody = new JsonResourceBody(body)) {
271 completeDataPATCH(server.dataPATCH(jsonBody), ar);
276 * Partially modify the target data resource, as defined in
277 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
279 * @param identifier path to target
280 * @param body data node for put to config DS
281 * @param ar {@link AsyncResponse} which needs to be completed
284 @Path("/data/{identifier:.+}")
286 MediaTypes.APPLICATION_YANG_DATA_JSON,
287 MediaType.APPLICATION_JSON,
289 public void dataJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
290 @Suspended final AsyncResponse ar) {
291 try (var jsonBody = new JsonResourceBody(body)) {
292 completeDataPATCH(server.dataPATCH(identifier, jsonBody), ar);
296 private static void completeDataPATCH(final RestconfFuture<DataPatchResult> future, final AsyncResponse ar) {
297 future.addCallback(new JaxRsRestconfCallback<>(ar) {
299 Response transform(final DataPatchResult result) {
300 final var builder = Response.ok();
301 fillConfigurationMetadata(builder, result);
302 return builder.build();
308 * Ordered list of edits that are applied to the datastore by the server, as defined in
309 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
311 * @param body YANG Patch body
312 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
316 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
318 MediaTypes.APPLICATION_YANG_DATA_JSON,
319 MediaTypes.APPLICATION_YANG_DATA_XML
321 public void dataYangJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
322 try (var jsonBody = new JsonPatchBody(body)) {
323 completeDataYangPATCH(server.dataPATCH(jsonBody), ar);
328 * Ordered list of edits that are applied to the target datastore by the server, as defined in
329 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
331 * @param identifier path to target
332 * @param body YANG Patch body
333 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
336 @Path("/data/{identifier:.+}")
337 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
339 MediaTypes.APPLICATION_YANG_DATA_JSON,
340 MediaTypes.APPLICATION_YANG_DATA_XML
342 public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
343 @Suspended final AsyncResponse ar) {
344 try (var jsonBody = new JsonPatchBody(body)) {
345 completeDataYangPATCH(server.dataPATCH(identifier, jsonBody), ar);
350 * Ordered list of edits that are applied to the datastore by the server, as defined in
351 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
353 * @param body YANG Patch body
354 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
358 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
360 MediaTypes.APPLICATION_YANG_DATA_JSON,
361 MediaTypes.APPLICATION_YANG_DATA_XML
363 public void dataYangXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
364 try (var xmlBody = new XmlPatchBody(body)) {
365 completeDataYangPATCH(server.dataPATCH(xmlBody), ar);
370 * Ordered list of edits that are applied to the target datastore by the server, as defined in
371 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
373 * @param identifier path to target
374 * @param body YANG Patch body
375 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
378 @Path("/data/{identifier:.+}")
379 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
381 MediaTypes.APPLICATION_YANG_DATA_JSON,
382 MediaTypes.APPLICATION_YANG_DATA_XML
384 public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
385 @Suspended final AsyncResponse ar) {
386 try (var xmlBody = new XmlPatchBody(body)) {
387 completeDataYangPATCH(server.dataPATCH(identifier, xmlBody), ar);
391 private static void completeDataYangPATCH(final RestconfFuture<PatchStatusContext> future, final AsyncResponse ar) {
392 future.addCallback(new JaxRsRestconfCallback<>(ar) {
394 Response transform(final PatchStatusContext result) {
395 return Response.status(statusOf(result)).entity(result).build();
398 private static Status statusOf(final PatchStatusContext result) {
402 final var globalErrors = result.globalErrors();
403 if (globalErrors != null && !globalErrors.isEmpty()) {
404 return statusOfFirst(globalErrors);
406 for (var edit : result.editCollection()) {
408 final var editErrors = edit.getEditErrors();
409 if (editErrors != null && !editErrors.isEmpty()) {
410 return statusOfFirst(editErrors);
414 return Status.INTERNAL_SERVER_ERROR;
417 private static Status statusOfFirst(final List<RestconfError> error) {
418 return ErrorTags.statusOf(error.get(0).getErrorTag());
424 * Create a top-level data resource.
426 * @param body data node for put to config DS
427 * @param uriInfo URI info
428 * @param ar {@link AsyncResponse} which needs to be completed
433 MediaTypes.APPLICATION_YANG_DATA_JSON,
434 MediaType.APPLICATION_JSON,
436 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
437 @Suspended final AsyncResponse ar) {
438 try (var jsonBody = new JsonChildBody(body)) {
439 completeDataPOST(server.dataPOST(jsonBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
444 * Create a data resource in target.
446 * @param identifier path to target
447 * @param body data node for put to config DS
448 * @param uriInfo URI info
449 * @param ar {@link AsyncResponse} which needs to be completed
452 @Path("/data/{identifier:.+}")
454 MediaTypes.APPLICATION_YANG_DATA_JSON,
455 MediaType.APPLICATION_JSON,
457 public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
458 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
459 completeDataPOST(server.dataPOST(identifier, new JsonDataPostBody(body), QueryParams.normalize(uriInfo)),
464 * Create a top-level data resource.
466 * @param body data node for put to config DS
467 * @param uriInfo URI info
468 * @param ar {@link AsyncResponse} which needs to be completed
473 MediaTypes.APPLICATION_YANG_DATA_XML,
474 MediaType.APPLICATION_XML,
477 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
478 try (var xmlBody = new XmlChildBody(body)) {
479 completeDataPOST(server.dataPOST(xmlBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
484 * Create a data resource in target.
486 * @param identifier path to target
487 * @param body data node for put to config DS
488 * @param uriInfo URI info
489 * @param ar {@link AsyncResponse} which needs to be completed
492 @Path("/data/{identifier:.+}")
494 MediaTypes.APPLICATION_YANG_DATA_XML,
495 MediaType.APPLICATION_XML,
498 public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
499 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
500 completeDataPOST(server.dataPOST(identifier, new XmlDataPostBody(body), QueryParams.normalize(uriInfo)),
504 private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future, final UriInfo uriInfo,
505 final AsyncResponse ar) {
506 future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
508 Response transform(final DataPostResult result) {
509 if (result instanceof CreateResource createResource) {
510 final var builder = Response.created(uriInfo.getBaseUriBuilder()
512 .path(createResource.createdPath())
514 fillConfigurationMetadata(builder, createResource);
515 return builder.build();
517 if (result instanceof InvokeOperation invokeOperation) {
518 final var output = invokeOperation.output();
519 return output == null ? Response.status(Status.NO_CONTENT).build()
520 : Response.status(Status.OK).entity(output).build();
522 LOG.error("Unhandled result {}", result);
523 return Response.serverError().build();
529 * Replace the data store.
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
538 MediaTypes.APPLICATION_YANG_DATA_JSON,
539 MediaType.APPLICATION_JSON,
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);
548 * Create or replace the target data resource.
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
556 @Path("/data/{identifier:.+}")
558 MediaTypes.APPLICATION_YANG_DATA_JSON,
559 MediaType.APPLICATION_JSON,
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);
569 * Replace the data store.
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
578 MediaTypes.APPLICATION_YANG_DATA_XML,
579 MediaType.APPLICATION_XML,
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);
589 * Create or replace the target data resource.
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
597 @Path("/data/{identifier:.+}")
599 MediaTypes.APPLICATION_YANG_DATA_XML,
600 MediaType.APPLICATION_XML,
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);
610 private static void completeDataPUT(final RestconfFuture<DataPutResult> future, final AsyncResponse ar) {
611 future.addCallback(new JaxRsRestconfCallback<>(ar) {
613 Response transform(final DataPutResult result) {
614 // Note: no Location header, as it matches the request path
615 final var builder = result.created() ? Response.status(Status.CREATED) : Response.noContent();
616 fillConfigurationMetadata(builder, result);
617 return builder.build();
623 * List RPC and action operations in RFC7951 format.
625 * @param ar {@link AsyncResponse} which needs to be completed
629 @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
630 public void operationsJsonGET(@Suspended final AsyncResponse ar) {
631 completeOperationsJsonGet(server.operationsGET(), ar);
635 * Retrieve list of operations and actions supported by the server or device in JSON format.
637 * @param operation path parameter to identify device and/or operation
638 * @param ar {@link AsyncResponse} which needs to be completed
641 @Path("/operations/{operation:.+}")
642 @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
643 public void operationsJsonGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
644 completeOperationsGet(server.operationsGET(operation), ar, OperationsGetResult::toJSON);
647 private static void completeOperationsJsonGet(final RestconfFuture<OperationsGetResult> future,
648 final AsyncResponse ar) {
649 completeOperationsGet(future, ar, OperationsGetResult::toJSON);
653 * List RPC and action operations in RFC8040 XML format.
655 * @param ar {@link AsyncResponse} which needs to be completed
659 @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
660 public void operationsXmlGET(@Suspended final AsyncResponse ar) {
661 completeOperationsXmlGet(server.operationsGET(), ar);
665 * Retrieve list of operations and actions supported by the server or device in XML format.
667 * @param operation path parameter to identify device and/or operation
668 * @param ar {@link AsyncResponse} which needs to be completed
671 @Path("/operations/{operation:.+}")
672 @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
673 public void operationsXmlGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
674 completeOperationsXmlGet(server.operationsGET(operation), ar);
677 private static void completeOperationsXmlGet(final RestconfFuture<OperationsGetResult> future,
678 final AsyncResponse ar) {
679 completeOperationsGet(future, ar, OperationsGetResult::toXML);
682 private static void completeOperationsGet(final RestconfFuture<OperationsGetResult> future, final AsyncResponse ar,
683 final Function<OperationsGetResult, String> toString) {
684 future.addCallback(new JaxRsRestconfCallback<OperationsGetResult>(ar) {
686 Response transform(final OperationsGetResult result) {
687 return Response.ok().entity(toString.apply(result)).build();
693 * Invoke RPC operation.
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
701 // FIXME: identifier is just a *single* QName
702 @Path("/operations/{identifier:.+}")
704 MediaTypes.APPLICATION_YANG_DATA_XML,
705 MediaType.APPLICATION_XML,
709 MediaTypes.APPLICATION_YANG_DATA_JSON,
710 MediaTypes.APPLICATION_YANG_DATA_XML,
711 MediaType.APPLICATION_JSON,
712 MediaType.APPLICATION_XML,
715 public void operationsXmlPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
716 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
717 try (var xmlBody = new XmlOperationInputBody(body)) {
718 operationsPOST(identifier, uriInfo, ar, xmlBody);
723 * Invoke RPC operation.
725 * @param identifier module name and rpc identifier string for the desired operation
726 * @param body the body of the operation
727 * @param uriInfo URI info
728 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
731 // FIXME: identifier is just a *single* QName
732 @Path("/operations/{identifier:.+}")
734 MediaTypes.APPLICATION_YANG_DATA_JSON,
735 MediaType.APPLICATION_JSON,
738 MediaTypes.APPLICATION_YANG_DATA_JSON,
739 MediaTypes.APPLICATION_YANG_DATA_XML,
740 MediaType.APPLICATION_JSON,
741 MediaType.APPLICATION_XML,
744 public void operationsJsonPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
745 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
746 try (var jsonBody = new JsonOperationInputBody(body)) {
747 operationsPOST(identifier, uriInfo, ar, jsonBody);
751 private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
752 final OperationInputBody body) {
753 server.operationsPOST(uriInfo.getBaseUri(), identifier, body)
754 .addCallback(new JaxRsRestconfCallback<OperationsPostResult>(ar) {
756 Response transform(final OperationsPostResult result) {
757 final var body = result.output();
758 return body == null ? Response.noContent().build()
759 : Response.ok().entity(new NormalizedNodePayload(result.operation(), body)).build();
765 * Get revision of IETF YANG Library module.
767 * @param ar {@link AsyncResponse} which needs to be completed
770 @Path("/yang-library-version")
772 MediaTypes.APPLICATION_YANG_DATA_JSON,
773 MediaTypes.APPLICATION_YANG_DATA_XML,
774 MediaType.APPLICATION_JSON,
775 MediaType.APPLICATION_XML,
778 public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
779 server.yangLibraryVersionGET().addCallback(new JaxRsRestconfCallback<NormalizedNodePayload>(ar) {
781 Response transform(final NormalizedNodePayload result) {
782 return Response.ok().entity(result).build();
787 // FIXME: References to these resources are generated by our yang-library implementation. That means:
788 // - We really need to formalize the parameter structure so we get some help from JAX-RS during matching
790 // - optional yang-ext:mount prefix(es)
791 // - mandatory module name
792 // - optional module revision
793 // - We really should use /yang-library-module/{name}(/{revision})?
794 // - We seem to be lacking explicit support for submodules in there -- and those locations should then point
795 // to /yang-library-submodule/{moduleName}(/{moduleRevision})?/{name}(/{revision})? so as to look the
796 // submodule up efficiently and allow for the weird case where there are two submodules with the same name
797 // (that is currently not supported by the parser, but it will be in the future)
798 // - It does not make sense to support yang-ext:mount, unless we also intercept mount points and rewrite
799 // yang-library locations. We most likely want to do that to ensure users are not tempted to connect to
803 * Get schema of specific module.
805 * @param fileName source file name
806 * @param revision source revision
807 * @param ar {@link AsyncResponse} which needs to be completed
810 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
811 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
812 public void modulesYangGET(@PathParam("fileName") final String fileName,
813 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
814 completeModulesGET(server.modulesYangGET(fileName, revision), ar);
818 * Get schema of specific module.
820 * @param mountPath mount point path
821 * @param fileName source file name
822 * @param revision source revision
823 * @param ar {@link AsyncResponse} which needs to be completed
826 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
827 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
828 public void modulesYangGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
829 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
830 @Suspended final AsyncResponse ar) {
831 completeModulesGET(server.modulesYangGET(mountPath, fileName, revision), ar);
835 * Get schema of specific module.
837 * @param fileName source file name
838 * @param revision source revision
839 * @param ar {@link AsyncResponse} which needs to be completed
842 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
843 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
844 public void modulesYinGET(@PathParam("fileName") final String fileName,
845 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
846 completeModulesGET(server.modulesYinGET(fileName, revision), ar);
850 * Get schema of specific module.
852 * @param mountPath mount point path
853 * @param fileName source file name
854 * @param revision source revision
855 * @param ar {@link AsyncResponse} which needs to be completed
858 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
859 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
860 public void modulesYinGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
861 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
862 @Suspended final AsyncResponse ar) {
863 completeModulesGET(server.modulesYinGET(mountPath, fileName, revision), ar);
866 private static void completeModulesGET(final RestconfFuture<ModulesGetResult> future, final AsyncResponse ar) {
867 future.addCallback(new JaxRsRestconfCallback<>(ar) {
869 Response transform(final ModulesGetResult result) {
872 reader = result.source().openStream();
873 } catch (IOException e) {
874 throw new RestconfDocumentedException("Cannot open source", e);
876 return Response.ok(reader).build();