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.QueryParam;
35 import javax.ws.rs.container.AsyncResponse;
36 import javax.ws.rs.container.Suspended;
37 import javax.ws.rs.core.Context;
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.ReadDataParams;
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.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.Revision;
78 import org.opendaylight.yangtools.yang.common.YangConstants;
79 import org.slf4j.Logger;
80 import org.slf4j.LoggerFactory;
83 * Baseline RESTCONF implementation with JAX-RS. Interfaces to a {@link RestconfServer}. Since we need {@link ApiPath}
84 * arguments, we also implement {@link ParamConverterProvider} and provide the appropriate converter. This has the nice
85 * side-effect of suppressing <a href="https://github.com/eclipse-ee4j/jersey/issues/3700">Jersey warnings</a>.
89 public final class JaxRsRestconf implements ParamConverterProvider {
90 private static final Logger LOG = LoggerFactory.getLogger(JaxRsRestconf.class);
91 private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss");
92 private static final ParamConverter<ApiPath> API_PATH_CONVERTER = new ParamConverter<>() {
94 public ApiPath fromString(final String value) {
95 final var str = nonnull(value);
97 return ApiPath.parseUrl(str);
98 } catch (ParseException e) {
99 throw new IllegalArgumentException(e.getMessage(), e);
104 public String toString(final ApiPath value) {
105 return nonnull(value).toString();
108 private static <T> @NonNull T nonnull(final @Nullable T value) {
110 throw new IllegalArgumentException("value must not be null");
116 private final RestconfServer server;
118 public JaxRsRestconf(final RestconfServer server) {
119 this.server = requireNonNull(server);
123 @SuppressWarnings("unchecked")
124 public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType,
125 final Annotation[] annotations) {
126 return ApiPath.class.equals(rawType) ? (ParamConverter<T>) API_PATH_CONVERTER : null;
130 * Delete the target data resource.
132 * @param identifier path to target
133 * @param ar {@link AsyncResponse} which needs to be completed
136 @Path("/data/{identifier:.+}")
137 @SuppressWarnings("checkstyle:abbreviationAsWordInName")
138 public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier,
139 @Suspended final AsyncResponse ar) {
140 server.dataDELETE(identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
142 Response transform(final Empty result) {
143 return Response.noContent().build();
149 * Get target data resource from data root.
151 * @param uriInfo URI info
152 * @param ar {@link AsyncResponse} which needs to be completed
157 MediaTypes.APPLICATION_YANG_DATA_JSON,
158 MediaTypes.APPLICATION_YANG_DATA_XML,
159 MediaType.APPLICATION_JSON,
160 MediaType.APPLICATION_XML,
163 public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
164 final var readParams = QueryParams.newReadDataParams(uriInfo);
165 completeDataGET(server.dataGET(readParams), readParams, ar);
169 * Get target data resource.
171 * @param identifier path to target
172 * @param uriInfo URI info
173 * @param ar {@link AsyncResponse} which needs to be completed
176 @Path("/data/{identifier:.+}")
178 MediaTypes.APPLICATION_YANG_DATA_JSON,
179 MediaTypes.APPLICATION_YANG_DATA_XML,
180 MediaType.APPLICATION_JSON,
181 MediaType.APPLICATION_XML,
184 public void dataGET(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
185 @Suspended final AsyncResponse ar) {
186 final var readParams = QueryParams.newReadDataParams(uriInfo);
187 completeDataGET(server.dataGET(identifier, readParams), readParams, ar);
190 private static void completeDataGET(final RestconfFuture<NormalizedNodePayload> future,
191 final ReadDataParams readParams, final AsyncResponse ar) {
192 future.addCallback(new JaxRsRestconfCallback<>(ar) {
194 Response transform(final NormalizedNodePayload result) {
195 return switch (readParams.content()) {
196 case ALL, CONFIG -> {
197 final var type = result.data().name().getNodeType();
198 yield Response.status(Status.OK)
200 // FIXME: is this ETag okay?
201 // FIXME: use tag() method instead
202 .header("ETag", '"' + type.getModule().getRevision().map(Revision::toString).orElse(null)
203 + "-" + type.getLocalName() + '"')
204 // FIXME: use lastModified() method instead
205 .header("Last-Modified", FORMATTER.format(LocalDateTime.now(Clock.systemUTC())))
208 case NONCONFIG -> Response.status(Status.OK).entity(result).build();
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<Empty> future, final AsyncResponse ar) {
297 future.addCallback(new JaxRsRestconfCallback<>(ar) {
299 Response transform(final Empty result) {
300 return Response.ok().build();
306 * Ordered list of edits that are applied to the datastore by the server, as defined in
307 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
309 * @param body YANG Patch body
310 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
314 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
316 MediaTypes.APPLICATION_YANG_DATA_JSON,
317 MediaTypes.APPLICATION_YANG_DATA_XML
319 public void dataYangJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
320 try (var jsonBody = new JsonPatchBody(body)) {
321 completeDataYangPATCH(server.dataPATCH(jsonBody), ar);
326 * Ordered list of edits that are applied to the target datastore by the server, as defined in
327 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
329 * @param identifier path to target
330 * @param body YANG Patch body
331 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
334 @Path("/data/{identifier:.+}")
335 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
337 MediaTypes.APPLICATION_YANG_DATA_JSON,
338 MediaTypes.APPLICATION_YANG_DATA_XML
340 public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
341 @Suspended final AsyncResponse ar) {
342 try (var jsonBody = new JsonPatchBody(body)) {
343 completeDataYangPATCH(server.dataPATCH(identifier, jsonBody), ar);
348 * Ordered list of edits that are applied to the datastore by the server, as defined in
349 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
351 * @param body YANG Patch body
352 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
356 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
358 MediaTypes.APPLICATION_YANG_DATA_JSON,
359 MediaTypes.APPLICATION_YANG_DATA_XML
361 public void dataYangXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
362 try (var xmlBody = new XmlPatchBody(body)) {
363 completeDataYangPATCH(server.dataPATCH(xmlBody), ar);
368 * Ordered list of edits that are applied to the target datastore by the server, as defined in
369 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
371 * @param identifier path to target
372 * @param body YANG Patch body
373 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
376 @Path("/data/{identifier:.+}")
377 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
379 MediaTypes.APPLICATION_YANG_DATA_JSON,
380 MediaTypes.APPLICATION_YANG_DATA_XML
382 public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
383 @Suspended final AsyncResponse ar) {
384 try (var xmlBody = new XmlPatchBody(body)) {
385 completeDataYangPATCH(server.dataPATCH(identifier, xmlBody), ar);
389 private static void completeDataYangPATCH(final RestconfFuture<PatchStatusContext> future, final AsyncResponse ar) {
390 future.addCallback(new JaxRsRestconfCallback<>(ar) {
392 Response transform(final PatchStatusContext result) {
393 return Response.status(statusOf(result)).entity(result).build();
396 private static Status statusOf(final PatchStatusContext result) {
400 final var globalErrors = result.globalErrors();
401 if (globalErrors != null && !globalErrors.isEmpty()) {
402 return statusOfFirst(globalErrors);
404 for (var edit : result.editCollection()) {
406 final var editErrors = edit.getEditErrors();
407 if (editErrors != null && !editErrors.isEmpty()) {
408 return statusOfFirst(editErrors);
412 return Status.INTERNAL_SERVER_ERROR;
415 private static Status statusOfFirst(final List<RestconfError> error) {
416 return ErrorTags.statusOf(error.get(0).getErrorTag());
422 * Create a top-level data resource.
424 * @param body data node for put to config DS
425 * @param uriInfo URI info
426 * @param ar {@link AsyncResponse} which needs to be completed
431 MediaTypes.APPLICATION_YANG_DATA_JSON,
432 MediaType.APPLICATION_JSON,
434 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
435 @Suspended final AsyncResponse ar) {
436 try (var jsonBody = new JsonChildBody(body)) {
437 completeDataPOST(server.dataPOST(jsonBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
442 * Create a data resource in target.
444 * @param identifier path to target
445 * @param body data node for put to config DS
446 * @param uriInfo URI info
447 * @param ar {@link AsyncResponse} which needs to be completed
450 @Path("/data/{identifier:.+}")
452 MediaTypes.APPLICATION_YANG_DATA_JSON,
453 MediaType.APPLICATION_JSON,
455 public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
456 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
457 completeDataPOST(server.dataPOST(identifier, new JsonDataPostBody(body), QueryParams.normalize(uriInfo)),
462 * Create a top-level data resource.
464 * @param body data node for put to config DS
465 * @param uriInfo URI info
466 * @param ar {@link AsyncResponse} which needs to be completed
471 MediaTypes.APPLICATION_YANG_DATA_XML,
472 MediaType.APPLICATION_XML,
475 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
476 try (var xmlBody = new XmlChildBody(body)) {
477 completeDataPOST(server.dataPOST(xmlBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
482 * Create a data resource in target.
484 * @param identifier path to target
485 * @param body data node for put to config DS
486 * @param uriInfo URI info
487 * @param ar {@link AsyncResponse} which needs to be completed
490 @Path("/data/{identifier:.+}")
492 MediaTypes.APPLICATION_YANG_DATA_XML,
493 MediaType.APPLICATION_XML,
496 public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
497 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
498 completeDataPOST(server.dataPOST(identifier, new XmlDataPostBody(body), QueryParams.normalize(uriInfo)),
502 private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future, final UriInfo uriInfo,
503 final AsyncResponse ar) {
504 future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
506 Response transform(final DataPostResult result) {
507 if (result instanceof CreateResource createResource) {
508 return Response.created(uriInfo.getBaseUriBuilder()
510 .path(createResource.createdPath())
514 if (result instanceof InvokeOperation invokeOperation) {
515 final var output = invokeOperation.output();
516 return output == null ? Response.status(Status.NO_CONTENT).build()
517 : Response.status(Status.OK).entity(output).build();
519 LOG.error("Unhandled result {}", result);
520 return Response.serverError().build();
526 * Replace the data store.
528 * @param uriInfo request URI information
529 * @param body data node for put to config DS
530 * @param ar {@link AsyncResponse} which needs to be completed
535 MediaTypes.APPLICATION_YANG_DATA_JSON,
536 MediaType.APPLICATION_JSON,
538 public void dataJsonPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
539 try (var jsonBody = new JsonResourceBody(body)) {
540 completeDataPUT(server.dataPUT(jsonBody, QueryParams.normalize(uriInfo)), ar);
545 * Create or replace the target data resource.
547 * @param identifier path to target
548 * @param uriInfo request URI information
549 * @param body data node for put to config DS
550 * @param ar {@link AsyncResponse} which needs to be completed
553 @Path("/data/{identifier:.+}")
555 MediaTypes.APPLICATION_YANG_DATA_JSON,
556 MediaType.APPLICATION_JSON,
558 public void dataJsonPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
559 final InputStream body, @Suspended final AsyncResponse ar) {
560 try (var jsonBody = new JsonResourceBody(body)) {
561 completeDataPUT(server.dataPUT(identifier, jsonBody, QueryParams.normalize(uriInfo)), ar);
566 * Replace the data store.
568 * @param uriInfo request URI information
569 * @param body data node for put to config DS
570 * @param ar {@link AsyncResponse} which needs to be completed
575 MediaTypes.APPLICATION_YANG_DATA_XML,
576 MediaType.APPLICATION_XML,
579 public void dataXmlPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
580 try (var xmlBody = new XmlResourceBody(body)) {
581 completeDataPUT(server.dataPUT(xmlBody, QueryParams.normalize(uriInfo)), ar);
586 * Create or replace the target data resource.
588 * @param identifier path to target
589 * @param uriInfo request URI information
590 * @param body data node for put to config DS
591 * @param ar {@link AsyncResponse} which needs to be completed
594 @Path("/data/{identifier:.+}")
596 MediaTypes.APPLICATION_YANG_DATA_XML,
597 MediaType.APPLICATION_XML,
600 public void dataXmlPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
601 final InputStream body, @Suspended final AsyncResponse ar) {
602 try (var xmlBody = new XmlResourceBody(body)) {
603 completeDataPUT(server.dataPUT(identifier, xmlBody, QueryParams.normalize(uriInfo)), ar);
607 private static void completeDataPUT(final RestconfFuture<DataPutResult> future, final AsyncResponse ar) {
608 future.addCallback(new JaxRsRestconfCallback<>(ar) {
610 Response transform(final DataPutResult result) {
611 return switch (result) {
612 // Note: no Location header, as it matches the request path
613 case CREATED -> Response.status(Status.CREATED).build();
614 case REPLACED -> Response.noContent().build();
621 * List RPC and action operations in RFC7951 format.
623 * @param ar {@link AsyncResponse} which needs to be completed
627 @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
628 public void operationsJsonGET(@Suspended final AsyncResponse ar) {
629 completeOperationsJsonGet(server.operationsGET(), ar);
633 * Retrieve list of operations and actions supported by the server or device in JSON format.
635 * @param operation path parameter to identify device and/or operation
636 * @param ar {@link AsyncResponse} which needs to be completed
639 @Path("/operations/{operation:.+}")
640 @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
641 public void operationsJsonGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
642 completeOperationsGet(server.operationsGET(operation), ar, OperationsGetResult::toJSON);
645 private static void completeOperationsJsonGet(final RestconfFuture<OperationsGetResult> future,
646 final AsyncResponse ar) {
647 completeOperationsGet(future, ar, OperationsGetResult::toJSON);
651 * List RPC and action operations in RFC8040 XML format.
653 * @param ar {@link AsyncResponse} which needs to be completed
657 @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
658 public void operationsXmlGET(@Suspended final AsyncResponse ar) {
659 completeOperationsXmlGet(server.operationsGET(), ar);
663 * Retrieve list of operations and actions supported by the server or device in XML format.
665 * @param operation path parameter to identify device and/or operation
666 * @param ar {@link AsyncResponse} which needs to be completed
669 @Path("/operations/{operation:.+}")
670 @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
671 public void operationsXmlGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
672 completeOperationsXmlGet(server.operationsGET(operation), ar);
675 private static void completeOperationsXmlGet(final RestconfFuture<OperationsGetResult> future,
676 final AsyncResponse ar) {
677 completeOperationsGet(future, ar, OperationsGetResult::toXML);
680 private static void completeOperationsGet(final RestconfFuture<OperationsGetResult> future, final AsyncResponse ar,
681 final Function<OperationsGetResult, String> toString) {
682 future.addCallback(new JaxRsRestconfCallback<OperationsGetResult>(ar) {
684 Response transform(final OperationsGetResult result) {
685 return Response.ok().entity(toString.apply(result)).build();
691 * Invoke RPC operation.
693 * @param identifier module name and rpc identifier string for the desired operation
694 * @param body the body of the operation
695 * @param uriInfo URI info
696 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
699 // FIXME: identifier is just a *single* QName
700 @Path("/operations/{identifier:.+}")
702 MediaTypes.APPLICATION_YANG_DATA_XML,
703 MediaType.APPLICATION_XML,
707 MediaTypes.APPLICATION_YANG_DATA_JSON,
708 MediaTypes.APPLICATION_YANG_DATA_XML,
709 MediaType.APPLICATION_JSON,
710 MediaType.APPLICATION_XML,
713 public void operationsXmlPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
714 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
715 try (var xmlBody = new XmlOperationInputBody(body)) {
716 operationsPOST(identifier, uriInfo, ar, xmlBody);
721 * Invoke RPC operation.
723 * @param identifier module name and rpc identifier string for the desired operation
724 * @param body the body of the operation
725 * @param uriInfo URI info
726 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
729 // FIXME: identifier is just a *single* QName
730 @Path("/operations/{identifier:.+}")
732 MediaTypes.APPLICATION_YANG_DATA_JSON,
733 MediaType.APPLICATION_JSON,
736 MediaTypes.APPLICATION_YANG_DATA_JSON,
737 MediaTypes.APPLICATION_YANG_DATA_XML,
738 MediaType.APPLICATION_JSON,
739 MediaType.APPLICATION_XML,
742 public void operationsJsonPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
743 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
744 try (var jsonBody = new JsonOperationInputBody(body)) {
745 operationsPOST(identifier, uriInfo, ar, jsonBody);
749 private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
750 final OperationInputBody body) {
751 server.operationsPOST(uriInfo.getBaseUri(), identifier, body)
752 .addCallback(new JaxRsRestconfCallback<OperationsPostResult>(ar) {
754 Response transform(final OperationsPostResult result) {
755 final var body = result.output();
756 return body == null ? Response.noContent().build()
757 : Response.ok().entity(new NormalizedNodePayload(result.operation(), body)).build();
763 * Get revision of IETF YANG Library module.
765 * @param ar {@link AsyncResponse} which needs to be completed
768 @Path("/yang-library-version")
770 MediaTypes.APPLICATION_YANG_DATA_JSON,
771 MediaTypes.APPLICATION_YANG_DATA_XML,
772 MediaType.APPLICATION_JSON,
773 MediaType.APPLICATION_XML,
776 public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
777 server.yangLibraryVersionGET().addCallback(new JaxRsRestconfCallback<NormalizedNodePayload>(ar) {
779 Response transform(final NormalizedNodePayload result) {
780 return Response.ok().entity(result).build();
785 // FIXME: References to these resources are generated by our yang-library implementation. That means:
786 // - We really need to formalize the parameter structure so we get some help from JAX-RS during matching
788 // - optional yang-ext:mount prefix(es)
789 // - mandatory module name
790 // - optional module revision
791 // - We really should use /yang-library-module/{name}(/{revision})?
792 // - We seem to be lacking explicit support for submodules in there -- and those locations should then point
793 // to /yang-library-submodule/{moduleName}(/{moduleRevision})?/{name}(/{revision})? so as to look the
794 // submodule up efficiently and allow for the weird case where there are two submodules with the same name
795 // (that is currently not supported by the parser, but it will be in the future)
796 // - It does not make sense to support yang-ext:mount, unless we also intercept mount points and rewrite
797 // yang-library locations. We most likely want to do that to ensure users are not tempted to connect to
801 * Get schema of specific module.
803 * @param fileName source file name
804 * @param revision source revision
805 * @param ar {@link AsyncResponse} which needs to be completed
808 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
809 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
810 public void modulesYangGET(@PathParam("fileName") final String fileName,
811 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
812 completeModulesGET(server.modulesYangGET(fileName, revision), ar);
816 * Get schema of specific module.
818 * @param mountPath mount point path
819 * @param fileName source file name
820 * @param revision source revision
821 * @param ar {@link AsyncResponse} which needs to be completed
824 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
825 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
826 public void modulesYangGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
827 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
828 @Suspended final AsyncResponse ar) {
829 completeModulesGET(server.modulesYangGET(mountPath, fileName, revision), ar);
833 * Get schema of specific module.
835 * @param fileName source file name
836 * @param revision source revision
837 * @param ar {@link AsyncResponse} which needs to be completed
840 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
841 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
842 public void modulesYinGET(@PathParam("fileName") final String fileName,
843 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
844 completeModulesGET(server.modulesYinGET(fileName, revision), ar);
848 * Get schema of specific module.
850 * @param mountPath mount point path
851 * @param fileName source file name
852 * @param revision source revision
853 * @param ar {@link AsyncResponse} which needs to be completed
856 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
857 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
858 public void modulesYinGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
859 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
860 @Suspended final AsyncResponse ar) {
861 completeModulesGET(server.modulesYinGET(mountPath, fileName, revision), ar);
864 private static void completeModulesGET(final RestconfFuture<ModulesGetResult> future, final AsyncResponse ar) {
865 future.addCallback(new JaxRsRestconfCallback<>(ar) {
867 Response transform(final ModulesGetResult result) {
870 reader = result.source().openStream();
871 } catch (IOException e) {
872 throw new RestconfDocumentedException("Cannot open source", e);
874 return Response.ok(reader).build();