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 javax.inject.Singleton;
21 import javax.ws.rs.BadRequestException;
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.api.QueryParameters;
50 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
51 import org.opendaylight.restconf.common.errors.RestconfError;
52 import org.opendaylight.restconf.common.errors.RestconfFuture;
53 import org.opendaylight.restconf.nb.rfc8040.URLConstants;
54 import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
55 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
56 import org.opendaylight.restconf.server.api.ConfigurationMetadata;
57 import org.opendaylight.restconf.server.api.CreateResourceResult;
58 import org.opendaylight.restconf.server.api.DataGetResult;
59 import org.opendaylight.restconf.server.api.DataPatchResult;
60 import org.opendaylight.restconf.server.api.DataPostResult;
61 import org.opendaylight.restconf.server.api.DataPutResult;
62 import org.opendaylight.restconf.server.api.DataYangPatchResult;
63 import org.opendaylight.restconf.server.api.InvokeResult;
64 import org.opendaylight.restconf.server.api.JsonChildBody;
65 import org.opendaylight.restconf.server.api.JsonDataPostBody;
66 import org.opendaylight.restconf.server.api.JsonOperationInputBody;
67 import org.opendaylight.restconf.server.api.JsonPatchBody;
68 import org.opendaylight.restconf.server.api.JsonResourceBody;
69 import org.opendaylight.restconf.server.api.ModulesGetResult;
70 import org.opendaylight.restconf.server.api.OperationInputBody;
71 import org.opendaylight.restconf.server.api.PatchStatusContext;
72 import org.opendaylight.restconf.server.api.RestconfServer;
73 import org.opendaylight.restconf.server.api.XmlChildBody;
74 import org.opendaylight.restconf.server.api.XmlDataPostBody;
75 import org.opendaylight.restconf.server.api.XmlOperationInputBody;
76 import org.opendaylight.restconf.server.api.XmlPatchBody;
77 import org.opendaylight.restconf.server.api.XmlResourceBody;
78 import org.opendaylight.restconf.server.spi.YangPatchStatusBody;
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 @NonNull 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;
131 private static @NonNull QueryParameters queryParams(final UriInfo uriInfo) {
133 return QueryParameters.ofMultiValue(uriInfo.getQueryParameters());
134 } catch (IllegalArgumentException e) {
135 throw new BadRequestException(e.getMessage(), e);
140 * Delete the target data resource.
142 * @param identifier path to target
143 * @param ar {@link AsyncResponse} which needs to be completed
146 @Path("/data/{identifier:.+}")
147 @SuppressWarnings("checkstyle:abbreviationAsWordInName")
148 public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier,
149 @Suspended final AsyncResponse ar) {
150 server.dataDELETE(identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
152 Response transform(final Empty result) {
153 return Response.noContent().build();
159 * Get target data resource from data root.
161 * @param uriInfo URI info
162 * @param ar {@link AsyncResponse} which needs to be completed
167 MediaTypes.APPLICATION_YANG_DATA_JSON,
168 MediaTypes.APPLICATION_YANG_DATA_XML,
169 MediaType.APPLICATION_JSON,
170 MediaType.APPLICATION_XML,
173 public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
174 completeDataGET(server.dataGET(queryParams(uriInfo)), ar);
178 * Get target data resource.
180 * @param identifier path to target
181 * @param uriInfo URI info
182 * @param ar {@link AsyncResponse} which needs to be completed
185 @Path("/data/{identifier:.+}")
187 MediaTypes.APPLICATION_YANG_DATA_JSON,
188 MediaTypes.APPLICATION_YANG_DATA_XML,
189 MediaType.APPLICATION_JSON,
190 MediaType.APPLICATION_XML,
193 public void dataGET(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
194 @Suspended final AsyncResponse ar) {
195 completeDataGET(server.dataGET(identifier, queryParams(uriInfo)), ar);
198 private static void completeDataGET(final RestconfFuture<DataGetResult> future, final AsyncResponse ar) {
199 future.addCallback(new JaxRsRestconfCallback<>(ar) {
201 Response transform(final DataGetResult result) {
202 final var builder = Response.ok().entity(result.payload()).cacheControl(NO_CACHE);
203 fillConfigurationMetadata(builder, result);
204 return builder.build();
209 private static void fillConfigurationMetadata(final ResponseBuilder builder, final ConfigurationMetadata metadata) {
210 final var etag = metadata.entityTag();
212 builder.tag(new EntityTag(etag.value(), etag.weak()));
214 final var lastModified = metadata.lastModified();
215 if (lastModified != null) {
216 builder.lastModified(Date.from(lastModified));
221 * Partially modify the target data store, as defined in
222 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
224 * @param body data node for put to config DS
225 * @param ar {@link AsyncResponse} which needs to be completed
230 MediaTypes.APPLICATION_YANG_DATA_XML,
231 MediaType.APPLICATION_XML,
234 public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
235 try (var xmlBody = new XmlResourceBody(body)) {
236 completeDataPATCH(server.dataPATCH(xmlBody), ar);
241 * Partially modify the target data resource, as defined in
242 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
244 * @param identifier path to target
245 * @param body data node for put to config DS
246 * @param ar {@link AsyncResponse} which needs to be completed
249 @Path("/data/{identifier:.+}")
251 MediaTypes.APPLICATION_YANG_DATA_XML,
252 MediaType.APPLICATION_XML,
255 public void dataXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
256 @Suspended final AsyncResponse ar) {
257 try (var xmlBody = new XmlResourceBody(body)) {
258 completeDataPATCH(server.dataPATCH(identifier, xmlBody), ar);
263 * Partially modify the target data store, as defined in
264 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
266 * @param body data node for put to config DS
267 * @param ar {@link AsyncResponse} which needs to be completed
272 MediaTypes.APPLICATION_YANG_DATA_JSON,
273 MediaType.APPLICATION_JSON,
275 public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
276 try (var jsonBody = new JsonResourceBody(body)) {
277 completeDataPATCH(server.dataPATCH(jsonBody), ar);
282 * Partially modify the target data resource, as defined in
283 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
285 * @param identifier path to target
286 * @param body data node for put to config DS
287 * @param ar {@link AsyncResponse} which needs to be completed
290 @Path("/data/{identifier:.+}")
292 MediaTypes.APPLICATION_YANG_DATA_JSON,
293 MediaType.APPLICATION_JSON,
295 public void dataJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
296 @Suspended final AsyncResponse ar) {
297 try (var jsonBody = new JsonResourceBody(body)) {
298 completeDataPATCH(server.dataPATCH(identifier, jsonBody), ar);
302 private static void completeDataPATCH(final RestconfFuture<DataPatchResult> future, final AsyncResponse ar) {
303 future.addCallback(new JaxRsRestconfCallback<>(ar) {
305 Response transform(final DataPatchResult result) {
306 final var builder = Response.ok();
307 fillConfigurationMetadata(builder, result);
308 return builder.build();
314 * Ordered list of edits that are applied to the datastore by the server, as defined in
315 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
317 * @param body YANG Patch body
318 * @param uriInfo URI info
319 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
323 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
325 MediaTypes.APPLICATION_YANG_DATA_JSON,
326 MediaTypes.APPLICATION_YANG_DATA_XML
328 public void dataYangJsonPATCH(final InputStream body, @Context final UriInfo uriInfo,
329 @Suspended final AsyncResponse ar) {
330 try (var jsonBody = new JsonPatchBody(body)) {
331 completeDataYangPATCH(server.dataPATCH(queryParams(uriInfo), jsonBody), ar);
336 * Ordered list of edits that are applied to the target datastore by the server, as defined in
337 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
339 * @param identifier path to target
340 * @param body YANG Patch body
341 * @param uriInfo URI info
342 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
345 @Path("/data/{identifier:.+}")
346 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
348 MediaTypes.APPLICATION_YANG_DATA_JSON,
349 MediaTypes.APPLICATION_YANG_DATA_XML
351 public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
352 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
353 try (var jsonBody = new JsonPatchBody(body)) {
354 completeDataYangPATCH(server.dataPATCH(identifier, queryParams(uriInfo), jsonBody), ar);
359 * Ordered list of edits that are applied to the datastore by the server, as defined in
360 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
362 * @param body YANG Patch body
363 * @param uriInfo URI info
364 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
368 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
370 MediaTypes.APPLICATION_YANG_DATA_JSON,
371 MediaTypes.APPLICATION_YANG_DATA_XML
373 public void dataYangXmlPATCH(final InputStream body, @Context final UriInfo uriInfo,
374 @Suspended final AsyncResponse ar) {
375 try (var xmlBody = new XmlPatchBody(body)) {
376 completeDataYangPATCH(server.dataPATCH(queryParams(uriInfo), xmlBody), ar);
381 * Ordered list of edits that are applied to the target datastore by the server, as defined in
382 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
384 * @param identifier path to target
385 * @param uriInfo URI info
386 * @param body YANG Patch body
387 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
390 @Path("/data/{identifier:.+}")
391 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
393 MediaTypes.APPLICATION_YANG_DATA_JSON,
394 MediaTypes.APPLICATION_YANG_DATA_XML
396 public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
397 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
398 try (var xmlBody = new XmlPatchBody(body)) {
399 completeDataYangPATCH(server.dataPATCH(identifier, queryParams(uriInfo), xmlBody), ar);
403 private static void completeDataYangPATCH(final RestconfFuture<DataYangPatchResult> future,
404 final AsyncResponse ar) {
405 future.addCallback(new JaxRsRestconfCallback<>(ar) {
407 Response transform(final DataYangPatchResult result) {
408 final var status = result.status();
409 final var builder = Response.status(statusOf(status))
410 .entity(new YangPatchStatusBody(result.params(), status));
411 fillConfigurationMetadata(builder, result);
412 return builder.build();
415 private static Status statusOf(final PatchStatusContext result) {
419 final var globalErrors = result.globalErrors();
420 if (globalErrors != null && !globalErrors.isEmpty()) {
421 return statusOfFirst(globalErrors);
423 for (var edit : result.editCollection()) {
425 final var editErrors = edit.getEditErrors();
426 if (editErrors != null && !editErrors.isEmpty()) {
427 return statusOfFirst(editErrors);
431 return Status.INTERNAL_SERVER_ERROR;
434 private static Status statusOfFirst(final List<RestconfError> error) {
435 return ErrorTags.statusOf(error.get(0).getErrorTag());
441 * Create a top-level data resource.
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
450 MediaTypes.APPLICATION_YANG_DATA_JSON,
451 MediaType.APPLICATION_JSON,
453 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
454 @Suspended final AsyncResponse ar) {
455 try (var jsonBody = new JsonChildBody(body)) {
456 completeDataPOST(server.dataPOST(queryParams(uriInfo), jsonBody), uriInfo, ar);
461 * Create a data resource in target.
463 * @param identifier path to target
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
469 @Path("/data/{identifier:.+}")
471 MediaTypes.APPLICATION_YANG_DATA_JSON,
472 MediaType.APPLICATION_JSON,
474 public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
475 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
476 completeDataPOST(server.dataPOST(identifier, queryParams(uriInfo), new JsonDataPostBody(body)), uriInfo, ar);
480 * Create a top-level data resource.
482 * @param body data node for put to config DS
483 * @param uriInfo URI info
484 * @param ar {@link AsyncResponse} which needs to be completed
489 MediaTypes.APPLICATION_YANG_DATA_XML,
490 MediaType.APPLICATION_XML,
493 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
494 try (var xmlBody = new XmlChildBody(body)) {
495 completeDataPOST(server.dataPOST(queryParams(uriInfo), xmlBody), uriInfo, ar);
500 * Create a data resource in target.
502 * @param identifier path to target
503 * @param body data node for put to config DS
504 * @param uriInfo URI info
505 * @param ar {@link AsyncResponse} which needs to be completed
508 @Path("/data/{identifier:.+}")
510 MediaTypes.APPLICATION_YANG_DATA_XML,
511 MediaType.APPLICATION_XML,
514 public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
515 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
516 completeDataPOST(server.dataPOST(identifier, queryParams(uriInfo), new XmlDataPostBody(body)), uriInfo, ar);
519 private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future, final UriInfo uriInfo,
520 final AsyncResponse ar) {
521 future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
523 Response transform(final DataPostResult result) {
524 if (result instanceof CreateResourceResult createResource) {
525 final var builder = Response.created(uriInfo.getBaseUriBuilder()
527 .path(createResource.createdPath().toString())
529 fillConfigurationMetadata(builder, createResource);
530 return builder.build();
532 if (result instanceof InvokeResult invokeOperation) {
533 final var output = invokeOperation.output();
534 return output == null ? Response.noContent().build() : Response.ok().entity(output).build();
536 LOG.error("Unhandled result {}", result);
537 return Response.serverError().build();
543 * Replace the data store.
545 * @param uriInfo request URI information
546 * @param body data node for put to config DS
547 * @param ar {@link AsyncResponse} which needs to be completed
552 MediaTypes.APPLICATION_YANG_DATA_JSON,
553 MediaType.APPLICATION_JSON,
555 public void dataJsonPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
556 try (var jsonBody = new JsonResourceBody(body)) {
557 completeDataPUT(server.dataPUT(queryParams(uriInfo), jsonBody), ar);
562 * Create or replace the target data resource.
564 * @param identifier path to target
565 * @param uriInfo request URI information
566 * @param body data node for put to config DS
567 * @param ar {@link AsyncResponse} which needs to be completed
570 @Path("/data/{identifier:.+}")
572 MediaTypes.APPLICATION_YANG_DATA_JSON,
573 MediaType.APPLICATION_JSON,
575 public void dataJsonPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
576 final InputStream body, @Suspended final AsyncResponse ar) {
577 try (var jsonBody = new JsonResourceBody(body)) {
578 completeDataPUT(server.dataPUT(identifier, queryParams(uriInfo), jsonBody), ar);
583 * Replace the data store.
585 * @param uriInfo request URI information
586 * @param body data node for put to config DS
587 * @param ar {@link AsyncResponse} which needs to be completed
592 MediaTypes.APPLICATION_YANG_DATA_XML,
593 MediaType.APPLICATION_XML,
596 public void dataXmlPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
597 try (var xmlBody = new XmlResourceBody(body)) {
598 completeDataPUT(server.dataPUT(queryParams(uriInfo), xmlBody), ar);
603 * Create or replace the target data resource.
605 * @param identifier path to target
606 * @param uriInfo request URI information
607 * @param body data node for put to config DS
608 * @param ar {@link AsyncResponse} which needs to be completed
611 @Path("/data/{identifier:.+}")
613 MediaTypes.APPLICATION_YANG_DATA_XML,
614 MediaType.APPLICATION_XML,
617 public void dataXmlPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
618 final InputStream body, @Suspended final AsyncResponse ar) {
619 try (var xmlBody = new XmlResourceBody(body)) {
620 completeDataPUT(server.dataPUT(identifier, queryParams(uriInfo), xmlBody), ar);
624 private static void completeDataPUT(final RestconfFuture<DataPutResult> future, final AsyncResponse ar) {
625 future.addCallback(new JaxRsRestconfCallback<>(ar) {
627 Response transform(final DataPutResult result) {
628 // Note: no Location header, as it matches the request path
629 final var builder = result.created() ? Response.created(null) : Response.noContent();
630 fillConfigurationMetadata(builder, result);
631 return builder.build();
637 * List RPC and action operations.
639 * @param ar {@link AsyncResponse} which needs to be completed
644 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
645 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
647 public void operationsGET(@Suspended final AsyncResponse ar) {
648 server.operationsGET().addCallback(new FormattableBodyCallback(ar));
652 * Retrieve list of operations and actions supported by the server or device.
654 * @param operation path parameter to identify device and/or operation
655 * @param ar {@link AsyncResponse} which needs to be completed
658 @Path("/operations/{operation:.+}")
660 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
661 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
663 public void operationsGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
664 server.operationsGET(operation).addCallback(new FormattableBodyCallback(ar));
668 * Invoke RPC operation.
670 * @param identifier module name and rpc identifier string for the desired operation
671 * @param body the body of the operation
672 * @param uriInfo URI info
673 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
676 // FIXME: identifier is just a *single* QName
677 @Path("/operations/{identifier:.+}")
679 MediaTypes.APPLICATION_YANG_DATA_XML,
680 MediaType.APPLICATION_XML,
684 MediaTypes.APPLICATION_YANG_DATA_JSON,
685 MediaTypes.APPLICATION_YANG_DATA_XML,
686 MediaType.APPLICATION_JSON,
687 MediaType.APPLICATION_XML,
690 public void operationsXmlPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
691 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
692 try (var xmlBody = new XmlOperationInputBody(body)) {
693 operationsPOST(identifier, uriInfo, ar, xmlBody);
698 * Invoke RPC operation.
700 * @param identifier module name and rpc identifier string for the desired operation
701 * @param body the body of the operation
702 * @param uriInfo URI info
703 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
706 // FIXME: identifier is just a *single* QName
707 @Path("/operations/{identifier:.+}")
709 MediaTypes.APPLICATION_YANG_DATA_JSON,
710 MediaType.APPLICATION_JSON,
713 MediaTypes.APPLICATION_YANG_DATA_JSON,
714 MediaTypes.APPLICATION_YANG_DATA_XML,
715 MediaType.APPLICATION_JSON,
716 MediaType.APPLICATION_XML,
719 public void operationsJsonPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
720 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
721 try (var jsonBody = new JsonOperationInputBody(body)) {
722 operationsPOST(identifier, uriInfo, ar, jsonBody);
726 private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
727 final OperationInputBody body) {
728 server.operationsPOST(uriInfo.getBaseUri(), identifier, queryParams(uriInfo), body)
729 .addCallback(new JaxRsRestconfCallback<>(ar) {
731 Response transform(final InvokeResult result) {
732 final var body = result.output();
733 return body == null ? Response.noContent().build()
734 : Response.ok().entity(body).build();
740 * Get revision of IETF YANG Library module.
742 * @param ar {@link AsyncResponse} which needs to be completed
745 @Path("/yang-library-version")
747 MediaTypes.APPLICATION_YANG_DATA_JSON,
748 MediaTypes.APPLICATION_YANG_DATA_XML,
749 MediaType.APPLICATION_JSON,
750 MediaType.APPLICATION_XML,
753 public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
754 server.yangLibraryVersionGET().addCallback(new FormattableBodyCallback(ar));
757 // FIXME: References to these resources are generated by our yang-library implementation. That means:
758 // - We really need to formalize the parameter structure so we get some help from JAX-RS during matching
760 // - optional yang-ext:mount prefix(es)
761 // - mandatory module name
762 // - optional module revision
763 // - We really should use /yang-library-module/{name}(/{revision})?
764 // - We seem to be lacking explicit support for submodules in there -- and those locations should then point
765 // to /yang-library-submodule/{moduleName}(/{moduleRevision})?/{name}(/{revision})? so as to look the
766 // submodule up efficiently and allow for the weird case where there are two submodules with the same name
767 // (that is currently not supported by the parser, but it will be in the future)
768 // - It does not make sense to support yang-ext:mount, unless we also intercept mount points and rewrite
769 // yang-library locations. We most likely want to do that to ensure users are not tempted to connect to
773 * Get schema of specific module.
775 * @param fileName source file name
776 * @param revision source revision
777 * @param ar {@link AsyncResponse} which needs to be completed
780 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
781 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
782 public void modulesYangGET(@PathParam("fileName") final String fileName,
783 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
784 completeModulesGET(server.modulesYangGET(fileName, revision), ar);
788 * Get schema of specific module.
790 * @param mountPath mount point path
791 * @param fileName source file name
792 * @param revision source revision
793 * @param ar {@link AsyncResponse} which needs to be completed
796 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
797 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
798 public void modulesYangGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
799 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
800 @Suspended final AsyncResponse ar) {
801 completeModulesGET(server.modulesYangGET(mountPath, fileName, revision), ar);
805 * Get schema of specific module.
807 * @param fileName source file name
808 * @param revision source revision
809 * @param ar {@link AsyncResponse} which needs to be completed
812 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
813 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
814 public void modulesYinGET(@PathParam("fileName") final String fileName,
815 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
816 completeModulesGET(server.modulesYinGET(fileName, revision), ar);
820 * Get schema of specific module.
822 * @param mountPath mount point path
823 * @param fileName source file name
824 * @param revision source revision
825 * @param ar {@link AsyncResponse} which needs to be completed
828 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
829 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
830 public void modulesYinGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
831 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
832 @Suspended final AsyncResponse ar) {
833 completeModulesGET(server.modulesYinGET(mountPath, fileName, revision), ar);
836 private static void completeModulesGET(final RestconfFuture<ModulesGetResult> future, final AsyncResponse ar) {
837 future.addCallback(new JaxRsRestconfCallback<>(ar) {
839 Response transform(final ModulesGetResult result) {
842 reader = result.source().openStream();
843 } catch (IOException e) {
844 throw new RestconfDocumentedException("Cannot open source", e);
846 return Response.ok(reader).build();