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.time.Clock;
19 import java.time.LocalDateTime;
20 import java.time.format.DateTimeFormatter;
21 import java.util.List;
22 import java.util.function.Function;
23 import javax.inject.Singleton;
24 import javax.ws.rs.Consumes;
25 import javax.ws.rs.DELETE;
26 import javax.ws.rs.Encoded;
27 import javax.ws.rs.GET;
28 import javax.ws.rs.PATCH;
29 import javax.ws.rs.POST;
30 import javax.ws.rs.PUT;
31 import javax.ws.rs.Path;
32 import javax.ws.rs.PathParam;
33 import javax.ws.rs.Produces;
34 import javax.ws.rs.container.AsyncResponse;
35 import javax.ws.rs.container.Suspended;
36 import javax.ws.rs.core.Context;
37 import javax.ws.rs.core.MediaType;
38 import javax.ws.rs.core.Response;
39 import javax.ws.rs.core.Response.Status;
40 import javax.ws.rs.core.UriInfo;
41 import javax.ws.rs.ext.ParamConverter;
42 import javax.ws.rs.ext.ParamConverterProvider;
43 import org.eclipse.jdt.annotation.NonNull;
44 import org.eclipse.jdt.annotation.Nullable;
45 import org.opendaylight.restconf.api.ApiPath;
46 import org.opendaylight.restconf.api.MediaTypes;
47 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
48 import org.opendaylight.restconf.common.errors.RestconfError;
49 import org.opendaylight.restconf.common.errors.RestconfFuture;
50 import org.opendaylight.restconf.common.patch.PatchStatusContext;
51 import org.opendaylight.restconf.nb.rfc8040.ReadDataParams;
52 import org.opendaylight.restconf.nb.rfc8040.databind.JsonChildBody;
53 import org.opendaylight.restconf.nb.rfc8040.databind.JsonDataPostBody;
54 import org.opendaylight.restconf.nb.rfc8040.databind.JsonOperationInputBody;
55 import org.opendaylight.restconf.nb.rfc8040.databind.JsonPatchBody;
56 import org.opendaylight.restconf.nb.rfc8040.databind.JsonResourceBody;
57 import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
58 import org.opendaylight.restconf.nb.rfc8040.databind.XmlChildBody;
59 import org.opendaylight.restconf.nb.rfc8040.databind.XmlDataPostBody;
60 import org.opendaylight.restconf.nb.rfc8040.databind.XmlOperationInputBody;
61 import org.opendaylight.restconf.nb.rfc8040.databind.XmlPatchBody;
62 import org.opendaylight.restconf.nb.rfc8040.databind.XmlResourceBody;
63 import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
64 import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
65 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
66 import org.opendaylight.restconf.server.api.DataPostResult;
67 import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
68 import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
69 import org.opendaylight.restconf.server.api.DataPutResult;
70 import org.opendaylight.restconf.server.api.ModulesGetResult;
71 import org.opendaylight.restconf.server.api.OperationsGetResult;
72 import org.opendaylight.restconf.server.api.RestconfServer;
73 import org.opendaylight.restconf.server.spi.OperationOutput;
74 import org.opendaylight.yangtools.yang.common.Empty;
75 import org.opendaylight.yangtools.yang.common.Revision;
76 import org.opendaylight.yangtools.yang.common.YangConstants;
77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory;
81 * Baseline RESTCONF implementation with JAX-RS. Interfaces to a {@link RestconfServer}. Since we need {@link ApiPath}
82 * arguments, we also implement {@link ParamConverterProvider} and provide the appropriate converter. This has the nice
83 * side-effect of suppressing <a href="https://github.com/eclipse-ee4j/jersey/issues/3700">Jersey warnings</a>.
87 public final class JaxRsRestconf implements ParamConverterProvider {
88 private static final Logger LOG = LoggerFactory.getLogger(JaxRsRestconf.class);
89 private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss");
90 private static final ParamConverter<ApiPath> API_PATH_CONVERTER = new ParamConverter<>() {
92 public ApiPath fromString(final String value) {
93 final var str = nonnull(value);
95 return ApiPath.parseUrl(str);
96 } catch (ParseException e) {
97 throw new IllegalArgumentException(e.getMessage(), e);
102 public String toString(final ApiPath value) {
103 return nonnull(value).toString();
106 private static <T> @NonNull T nonnull(final @Nullable T value) {
108 throw new IllegalArgumentException("value must not be null");
114 private final RestconfServer server;
116 public JaxRsRestconf(final RestconfServer server) {
117 this.server = requireNonNull(server);
121 @SuppressWarnings("unchecked")
122 public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType,
123 final Annotation[] annotations) {
124 return ApiPath.class.equals(rawType) ? (ParamConverter<T>) API_PATH_CONVERTER : null;
128 * Delete the target data resource.
130 * @param identifier path to target
131 * @param ar {@link AsyncResponse} which needs to be completed
134 @Path("/data/{identifier:.+}")
135 @SuppressWarnings("checkstyle:abbreviationAsWordInName")
136 public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier,
137 @Suspended final AsyncResponse ar) {
138 server.dataDELETE(identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
140 Response transform(final Empty result) {
141 return Response.noContent().build();
147 * Get target data resource from data root.
149 * @param uriInfo URI info
150 * @param ar {@link AsyncResponse} which needs to be completed
155 MediaTypes.APPLICATION_YANG_DATA_JSON,
156 MediaTypes.APPLICATION_YANG_DATA_XML,
157 MediaType.APPLICATION_JSON,
158 MediaType.APPLICATION_XML,
161 public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
162 final var readParams = QueryParams.newReadDataParams(uriInfo);
163 completeDataGET(server.dataGET(readParams), readParams, 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 final var readParams = QueryParams.newReadDataParams(uriInfo);
185 completeDataGET(server.dataGET(identifier, readParams), readParams, ar);
188 private static void completeDataGET(final RestconfFuture<NormalizedNodePayload> future,
189 final ReadDataParams readParams, final AsyncResponse ar) {
190 future.addCallback(new JaxRsRestconfCallback<>(ar) {
192 Response transform(final NormalizedNodePayload result) {
193 return switch (readParams.content()) {
194 case ALL, CONFIG -> {
195 final var type = result.data().name().getNodeType();
196 yield Response.status(Status.OK)
198 // FIXME: is this ETag okay?
199 // FIXME: use tag() method instead
200 .header("ETag", '"' + type.getModule().getRevision().map(Revision::toString).orElse(null)
201 + "-" + type.getLocalName() + '"')
202 // FIXME: use lastModified() method instead
203 .header("Last-Modified", FORMATTER.format(LocalDateTime.now(Clock.systemUTC())))
206 case NONCONFIG -> Response.status(Status.OK).entity(result).build();
213 * Partially modify the target data store, as defined in
214 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
216 * @param body data node for put to config DS
217 * @param ar {@link AsyncResponse} which needs to be completed
222 MediaTypes.APPLICATION_YANG_DATA_XML,
223 MediaType.APPLICATION_XML,
226 public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
227 try (var xmlBody = new XmlResourceBody(body)) {
228 completeDataPATCH(server.dataPATCH(xmlBody), ar);
233 * Partially modify the target data resource, as defined in
234 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
236 * @param identifier path to target
237 * @param body data node for put to config DS
238 * @param ar {@link AsyncResponse} which needs to be completed
241 @Path("/data/{identifier:.+}")
243 MediaTypes.APPLICATION_YANG_DATA_XML,
244 MediaType.APPLICATION_XML,
247 public void dataXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
248 @Suspended final AsyncResponse ar) {
249 try (var xmlBody = new XmlResourceBody(body)) {
250 completeDataPATCH(server.dataPATCH(identifier, xmlBody), ar);
255 * Partially modify the target data store, as defined in
256 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
258 * @param body data node for put to config DS
259 * @param ar {@link AsyncResponse} which needs to be completed
264 MediaTypes.APPLICATION_YANG_DATA_JSON,
265 MediaType.APPLICATION_JSON,
267 public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
268 try (var jsonBody = new JsonResourceBody(body)) {
269 completeDataPATCH(server.dataPATCH(jsonBody), ar);
274 * Partially modify the target data resource, as defined in
275 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
277 * @param identifier path to target
278 * @param body data node for put to config DS
279 * @param ar {@link AsyncResponse} which needs to be completed
282 @Path("/data/{identifier:.+}")
284 MediaTypes.APPLICATION_YANG_DATA_JSON,
285 MediaType.APPLICATION_JSON,
287 public void dataJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
288 @Suspended final AsyncResponse ar) {
289 try (var jsonBody = new JsonResourceBody(body)) {
290 completeDataPATCH(server.dataPATCH(identifier, jsonBody), ar);
294 private static void completeDataPATCH(final RestconfFuture<Empty> future, final AsyncResponse ar) {
295 future.addCallback(new JaxRsRestconfCallback<>(ar) {
297 Response transform(final Empty result) {
298 return Response.ok().build();
304 * Ordered list of edits that are applied to the datastore by the server, as defined in
305 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
307 * @param body YANG Patch body
308 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
312 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
314 MediaTypes.APPLICATION_YANG_DATA_JSON,
315 MediaTypes.APPLICATION_YANG_DATA_XML
317 public void dataYangJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
318 try (var jsonBody = new JsonPatchBody(body)) {
319 completeDataYangPATCH(server.dataPATCH(jsonBody), ar);
324 * Ordered list of edits that are applied to the target datastore by the server, as defined in
325 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
327 * @param identifier path to target
328 * @param body YANG Patch body
329 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
332 @Path("/data/{identifier:.+}")
333 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
335 MediaTypes.APPLICATION_YANG_DATA_JSON,
336 MediaTypes.APPLICATION_YANG_DATA_XML
338 public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
339 @Suspended final AsyncResponse ar) {
340 try (var jsonBody = new JsonPatchBody(body)) {
341 completeDataYangPATCH(server.dataPATCH(identifier, jsonBody), ar);
346 * Ordered list of edits that are applied to the datastore by the server, as defined in
347 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
349 * @param body YANG Patch body
350 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
354 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
356 MediaTypes.APPLICATION_YANG_DATA_JSON,
357 MediaTypes.APPLICATION_YANG_DATA_XML
359 public void dataYangXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
360 try (var xmlBody = new XmlPatchBody(body)) {
361 completeDataYangPATCH(server.dataPATCH(xmlBody), ar);
366 * Ordered list of edits that are applied to the target datastore by the server, as defined in
367 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
369 * @param identifier path to target
370 * @param body YANG Patch body
371 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
374 @Path("/data/{identifier:.+}")
375 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
377 MediaTypes.APPLICATION_YANG_DATA_JSON,
378 MediaTypes.APPLICATION_YANG_DATA_XML
380 public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
381 @Suspended final AsyncResponse ar) {
382 try (var xmlBody = new XmlPatchBody(body)) {
383 completeDataYangPATCH(server.dataPATCH(identifier, xmlBody), ar);
387 private static void completeDataYangPATCH(final RestconfFuture<PatchStatusContext> future, final AsyncResponse ar) {
388 future.addCallback(new JaxRsRestconfCallback<>(ar) {
390 Response transform(final PatchStatusContext result) {
391 return Response.status(statusOf(result)).entity(result).build();
394 private static Status statusOf(final PatchStatusContext result) {
398 final var globalErrors = result.globalErrors();
399 if (globalErrors != null && !globalErrors.isEmpty()) {
400 return statusOfFirst(globalErrors);
402 for (var edit : result.editCollection()) {
404 final var editErrors = edit.getEditErrors();
405 if (editErrors != null && !editErrors.isEmpty()) {
406 return statusOfFirst(editErrors);
410 return Status.INTERNAL_SERVER_ERROR;
413 private static Status statusOfFirst(final List<RestconfError> error) {
414 return ErrorTags.statusOf(error.get(0).getErrorTag());
420 * Create a top-level data resource.
422 * @param body data node for put to config DS
423 * @param uriInfo URI info
424 * @param ar {@link AsyncResponse} which needs to be completed
429 MediaTypes.APPLICATION_YANG_DATA_JSON,
430 MediaType.APPLICATION_JSON,
432 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
433 @Suspended final AsyncResponse ar) {
434 try (var jsonBody = new JsonChildBody(body)) {
435 completeDataPOST(server.dataPOST(jsonBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
440 * Create a data resource in target.
442 * @param identifier path to target
443 * @param body data node for put to config DS
444 * @param uriInfo URI info
445 * @param ar {@link AsyncResponse} which needs to be completed
448 @Path("/data/{identifier:.+}")
450 MediaTypes.APPLICATION_YANG_DATA_JSON,
451 MediaType.APPLICATION_JSON,
453 public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
454 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
455 completeDataPOST(server.dataPOST(identifier, new JsonDataPostBody(body), QueryParams.normalize(uriInfo)),
460 * Create a top-level data resource.
462 * @param body data node for put to config DS
463 * @param uriInfo URI info
464 * @param ar {@link AsyncResponse} which needs to be completed
469 MediaTypes.APPLICATION_YANG_DATA_XML,
470 MediaType.APPLICATION_XML,
473 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
474 try (var xmlBody = new XmlChildBody(body)) {
475 completeDataPOST(server.dataPOST(xmlBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
480 * Create a data resource in target.
482 * @param identifier path to target
483 * @param body data node for put to config DS
484 * @param uriInfo URI info
485 * @param ar {@link AsyncResponse} which needs to be completed
488 @Path("/data/{identifier:.+}")
490 MediaTypes.APPLICATION_YANG_DATA_XML,
491 MediaType.APPLICATION_XML,
494 public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
495 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
496 completeDataPOST(server.dataPOST(identifier, new XmlDataPostBody(body), QueryParams.normalize(uriInfo)),
500 private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future, final UriInfo uriInfo,
501 final AsyncResponse ar) {
502 future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
504 Response transform(final DataPostResult result) {
505 if (result instanceof CreateResource createResource) {
506 return Response.created(uriInfo.getBaseUriBuilder()
508 .path(createResource.createdPath())
512 if (result instanceof InvokeOperation invokeOperation) {
513 final var output = invokeOperation.output();
514 return output == null ? Response.status(Status.NO_CONTENT).build()
515 : Response.status(Status.OK).entity(output).build();
517 LOG.error("Unhandled result {}", result);
518 return Response.serverError().build();
524 * Replace the data store.
526 * @param uriInfo request URI information
527 * @param body data node for put to config DS
528 * @param ar {@link AsyncResponse} which needs to be completed
533 MediaTypes.APPLICATION_YANG_DATA_JSON,
534 MediaType.APPLICATION_JSON,
536 public void dataJsonPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
537 try (var jsonBody = new JsonResourceBody(body)) {
538 completeDataPUT(server.dataPUT(jsonBody, QueryParams.normalize(uriInfo)), ar);
543 * Create or replace the target data resource.
545 * @param identifier path to target
546 * @param uriInfo request URI information
547 * @param body data node for put to config DS
548 * @param ar {@link AsyncResponse} which needs to be completed
551 @Path("/data/{identifier:.+}")
553 MediaTypes.APPLICATION_YANG_DATA_JSON,
554 MediaType.APPLICATION_JSON,
556 public void dataJsonPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
557 final InputStream body, @Suspended final AsyncResponse ar) {
558 try (var jsonBody = new JsonResourceBody(body)) {
559 completeDataPUT(server.dataPUT(identifier, jsonBody, QueryParams.normalize(uriInfo)), ar);
564 * Replace the data store.
566 * @param uriInfo request URI information
567 * @param body data node for put to config DS
568 * @param ar {@link AsyncResponse} which needs to be completed
573 MediaTypes.APPLICATION_YANG_DATA_XML,
574 MediaType.APPLICATION_XML,
577 public void dataXmlPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
578 try (var xmlBody = new XmlResourceBody(body)) {
579 completeDataPUT(server.dataPUT(xmlBody, QueryParams.normalize(uriInfo)), ar);
584 * Create or replace the target data resource.
586 * @param identifier path to target
587 * @param uriInfo request URI information
588 * @param body data node for put to config DS
589 * @param ar {@link AsyncResponse} which needs to be completed
592 @Path("/data/{identifier:.+}")
594 MediaTypes.APPLICATION_YANG_DATA_XML,
595 MediaType.APPLICATION_XML,
598 public void dataXmlPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
599 final InputStream body, @Suspended final AsyncResponse ar) {
600 try (var xmlBody = new XmlResourceBody(body)) {
601 completeDataPUT(server.dataPUT(identifier, xmlBody, QueryParams.normalize(uriInfo)), ar);
605 private static void completeDataPUT(final RestconfFuture<DataPutResult> future, final AsyncResponse ar) {
606 future.addCallback(new JaxRsRestconfCallback<>(ar) {
608 Response transform(final DataPutResult result) {
609 return switch (result) {
610 // Note: no Location header, as it matches the request path
611 case CREATED -> Response.status(Status.CREATED).build();
612 case REPLACED -> Response.noContent().build();
619 * List RPC and action operations in RFC7951 format.
621 * @param ar {@link AsyncResponse} which needs to be completed
625 @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
626 public void operationsJsonGET(@Suspended final AsyncResponse ar) {
627 completeOperationsJsonGet(server.operationsGET(), ar);
631 * Retrieve list of operations and actions supported by the server or device in JSON format.
633 * @param operation path parameter to identify device and/or operation
634 * @param ar {@link AsyncResponse} which needs to be completed
637 @Path("/operations/{operation:.+}")
638 @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
639 public void operationsJsonGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
640 completeOperationsGet(server.operationsGET(operation), ar, OperationsGetResult::toJSON);
643 private static void completeOperationsJsonGet(final RestconfFuture<OperationsGetResult> future,
644 final AsyncResponse ar) {
645 completeOperationsGet(future, ar, OperationsGetResult::toJSON);
649 * List RPC and action operations in RFC8040 XML format.
651 * @param ar {@link AsyncResponse} which needs to be completed
655 @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
656 public void operationsXmlGET(@Suspended final AsyncResponse ar) {
657 completeOperationsXmlGet(server.operationsGET(), ar);
661 * Retrieve list of operations and actions supported by the server or device in XML format.
663 * @param operation path parameter to identify device and/or operation
664 * @param ar {@link AsyncResponse} which needs to be completed
667 @Path("/operations/{operation:.+}")
668 @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
669 public void operationsXmlGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
670 completeOperationsXmlGet(server.operationsGET(operation), ar);
673 private static void completeOperationsXmlGet(final RestconfFuture<OperationsGetResult> future,
674 final AsyncResponse ar) {
675 completeOperationsGet(future, ar, OperationsGetResult::toXML);
678 private static void completeOperationsGet(final RestconfFuture<OperationsGetResult> future, final AsyncResponse ar,
679 final Function<OperationsGetResult, String> toString) {
680 future.addCallback(new JaxRsRestconfCallback<OperationsGetResult>(ar) {
682 Response transform(final OperationsGetResult result) {
683 return Response.ok().entity(toString.apply(result)).build();
689 * Invoke RPC operation.
691 * @param identifier module name and rpc identifier string for the desired operation
692 * @param body the body of the operation
693 * @param uriInfo URI info
694 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
697 // FIXME: identifier is just a *single* QName
698 @Path("/operations/{identifier:.+}")
700 MediaTypes.APPLICATION_YANG_DATA_XML,
701 MediaType.APPLICATION_XML,
705 MediaTypes.APPLICATION_YANG_DATA_JSON,
706 MediaTypes.APPLICATION_YANG_DATA_XML,
707 MediaType.APPLICATION_JSON,
708 MediaType.APPLICATION_XML,
711 public void operationsXmlPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
712 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
713 try (var xmlBody = new XmlOperationInputBody(body)) {
714 operationsPOST(identifier, uriInfo, ar, xmlBody);
719 * Invoke RPC operation.
721 * @param identifier module name and rpc identifier string for the desired operation
722 * @param body the body of the operation
723 * @param uriInfo URI info
724 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
727 // FIXME: identifier is just a *single* QName
728 @Path("/operations/{identifier:.+}")
730 MediaTypes.APPLICATION_YANG_DATA_JSON,
731 MediaType.APPLICATION_JSON,
734 MediaTypes.APPLICATION_YANG_DATA_JSON,
735 MediaTypes.APPLICATION_YANG_DATA_XML,
736 MediaType.APPLICATION_JSON,
737 MediaType.APPLICATION_XML,
740 public void operationsJsonPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
741 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
742 try (var jsonBody = new JsonOperationInputBody(body)) {
743 operationsPOST(identifier, uriInfo, ar, jsonBody);
747 private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
748 final OperationInputBody body) {
749 server.operationsPOST(uriInfo.getBaseUri(), identifier, body)
750 .addCallback(new JaxRsRestconfCallback<OperationOutput>(ar) {
752 Response transform(final OperationOutput result) {
753 final var body = result.output();
754 return body == null ? Response.noContent().build()
755 : Response.ok().entity(new NormalizedNodePayload(result.operation(), body)).build();
761 * Get revision of IETF YANG Library module.
763 * @param ar {@link AsyncResponse} which needs to be completed
766 @Path("/yang-library-version")
768 MediaTypes.APPLICATION_YANG_DATA_JSON,
769 MediaTypes.APPLICATION_YANG_DATA_XML,
770 MediaType.APPLICATION_JSON,
771 MediaType.APPLICATION_XML,
774 public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
775 server.yangLibraryVersionGET().addCallback(new JaxRsRestconfCallback<NormalizedNodePayload>(ar) {
777 Response transform(final NormalizedNodePayload result) {
778 return Response.ok().entity(result).build();
783 // FIXME: References to these resources are generated by our yang-library implementation. That means:
784 // - We really need to formalize the parameter structure so we get some help from JAX-RS during matching
786 // - optional yang-ext:mount prefix(es)
787 // - mandatory module name
788 // - optional module revision
789 // - We really should use /yang-library-module/{name}(/{revision})?
790 // - We seem to be lacking explicit support for submodules in there -- and those locations should then point
791 // to /yang-library-submodule/{moduleName}(/{moduleRevision})?/{name}(/{revision})? so as to look the
792 // submodule up efficiently and allow for the weird case where there are two submodules with the same name
793 // (that is currently not supported by the parser, but it will be in the future)
794 // - It does not make sense to support yang-ext:mount, unless we also intercept mount points and rewrite
795 // yang-library locations. We most likely want to do that to ensure users are not tempted to connect to
799 * Get schema of specific module.
801 * @param identifier path parameter
802 * @param ar {@link AsyncResponse} which needs to be completed
805 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
806 @Path("/modules/{identifier:.+}")
807 public void modulesYangGET(@PathParam("identifier") final String identifier, @Suspended final AsyncResponse ar) {
808 completeModulesGET(server.modulesYangGET(identifier), ar);
812 * Get schema of specific module.
814 * @param identifier path parameter
815 * @param ar {@link AsyncResponse} which needs to be completed
818 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
819 @Path("/modules/{identifier:.+}")
820 public void modulesYinGET(@PathParam("identifier") final String identifier, @Suspended final AsyncResponse ar) {
821 completeModulesGET(server.modulesYinGET(identifier), ar);
824 private static void completeModulesGET(final RestconfFuture<ModulesGetResult> future, final AsyncResponse ar) {
825 future.addCallback(new JaxRsRestconfCallback<>(ar) {
827 Response transform(final ModulesGetResult result) {
830 reader = result.source().openStream();
831 } catch (IOException e) {
832 throw new RestconfDocumentedException("Cannot open source", e);
834 return Response.ok(reader).build();