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.Consumes;
22 import javax.ws.rs.DELETE;
23 import javax.ws.rs.Encoded;
24 import javax.ws.rs.GET;
25 import javax.ws.rs.PATCH;
26 import javax.ws.rs.POST;
27 import javax.ws.rs.PUT;
28 import javax.ws.rs.Path;
29 import javax.ws.rs.PathParam;
30 import javax.ws.rs.Produces;
31 import javax.ws.rs.QueryParam;
32 import javax.ws.rs.container.AsyncResponse;
33 import javax.ws.rs.container.Suspended;
34 import javax.ws.rs.core.CacheControl;
35 import javax.ws.rs.core.Context;
36 import javax.ws.rs.core.EntityTag;
37 import javax.ws.rs.core.MediaType;
38 import javax.ws.rs.core.Response;
39 import javax.ws.rs.core.Response.ResponseBuilder;
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.FormattableBody;
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.nb.rfc8040.URLConstants;
53 import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
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.yangtools.yang.common.Empty;
79 import org.opendaylight.yangtools.yang.common.YangConstants;
80 import org.slf4j.Logger;
81 import org.slf4j.LoggerFactory;
84 * Baseline RESTCONF implementation with JAX-RS. Interfaces to a {@link RestconfServer}. Since we need {@link ApiPath}
85 * arguments, we also implement {@link ParamConverterProvider} and provide the appropriate converter. This has the nice
86 * side-effect of suppressing <a href="https://github.com/eclipse-ee4j/jersey/issues/3700">Jersey warnings</a>.
90 public final class JaxRsRestconf implements ParamConverterProvider {
91 private static final Logger LOG = LoggerFactory.getLogger(JaxRsRestconf.class);
92 private static final CacheControl NO_CACHE = CacheControl.valueOf("no-cache");
93 private static final ParamConverter<ApiPath> API_PATH_CONVERTER = new ParamConverter<>() {
95 public ApiPath fromString(final String value) {
96 final var str = nonnull(value);
98 return ApiPath.parseUrl(str);
99 } catch (ParseException e) {
100 throw new IllegalArgumentException(e.getMessage(), e);
105 public String toString(final ApiPath value) {
106 return nonnull(value).toString();
109 private static <T> @NonNull T nonnull(final @Nullable T value) {
111 throw new IllegalArgumentException("value must not be null");
117 private final RestconfServer server;
119 public JaxRsRestconf(final RestconfServer server) {
120 this.server = requireNonNull(server);
124 @SuppressWarnings("unchecked")
125 public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType,
126 final Annotation[] annotations) {
127 return ApiPath.class.equals(rawType) ? (ParamConverter<T>) API_PATH_CONVERTER : null;
131 * Delete the target data resource.
133 * @param identifier path to target
134 * @param ar {@link AsyncResponse} which needs to be completed
137 @Path("/data/{identifier:.+}")
138 @SuppressWarnings("checkstyle:abbreviationAsWordInName")
139 public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier,
140 @Suspended final AsyncResponse ar) {
141 server.dataDELETE(identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
143 Response transform(final Empty result) {
144 return Response.noContent().build();
150 * Get target data resource from data root.
152 * @param uriInfo URI info
153 * @param ar {@link AsyncResponse} which needs to be completed
158 MediaTypes.APPLICATION_YANG_DATA_JSON,
159 MediaTypes.APPLICATION_YANG_DATA_XML,
160 MediaType.APPLICATION_JSON,
161 MediaType.APPLICATION_XML,
164 public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
165 completeDataGET(server.dataGET(QueryParams.newDataGetParams(uriInfo)), 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 completeDataGET(server.dataGET(identifier, QueryParams.newDataGetParams(uriInfo)), ar);
189 private static void completeDataGET(final RestconfFuture<DataGetResult> future, final AsyncResponse ar) {
190 future.addCallback(new JaxRsRestconfCallback<>(ar) {
192 Response transform(final DataGetResult result) {
193 final var builder = Response.ok().entity(result.payload()).cacheControl(NO_CACHE);
194 fillConfigurationMetadata(builder, result);
195 return builder.build();
200 private static void fillConfigurationMetadata(final ResponseBuilder builder, final ConfigurationMetadata metadata) {
201 final var etag = metadata.entityTag();
203 builder.tag(new EntityTag(etag.value(), etag.weak()));
205 final var lastModified = metadata.lastModified();
206 if (lastModified != null) {
207 builder.lastModified(Date.from(lastModified));
212 * Partially modify the target data store, as defined in
213 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
215 * @param body data node for put to config DS
216 * @param ar {@link AsyncResponse} which needs to be completed
221 MediaTypes.APPLICATION_YANG_DATA_XML,
222 MediaType.APPLICATION_XML,
225 public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
226 try (var xmlBody = new XmlResourceBody(body)) {
227 completeDataPATCH(server.dataPATCH(xmlBody), ar);
232 * Partially modify the target data resource, as defined in
233 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
235 * @param identifier path to target
236 * @param body data node for put to config DS
237 * @param ar {@link AsyncResponse} which needs to be completed
240 @Path("/data/{identifier:.+}")
242 MediaTypes.APPLICATION_YANG_DATA_XML,
243 MediaType.APPLICATION_XML,
246 public void dataXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
247 @Suspended final AsyncResponse ar) {
248 try (var xmlBody = new XmlResourceBody(body)) {
249 completeDataPATCH(server.dataPATCH(identifier, xmlBody), ar);
254 * Partially modify the target data store, as defined in
255 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
257 * @param body data node for put to config DS
258 * @param ar {@link AsyncResponse} which needs to be completed
263 MediaTypes.APPLICATION_YANG_DATA_JSON,
264 MediaType.APPLICATION_JSON,
266 public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
267 try (var jsonBody = new JsonResourceBody(body)) {
268 completeDataPATCH(server.dataPATCH(jsonBody), ar);
273 * Partially modify the target data resource, as defined in
274 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
276 * @param identifier path to target
277 * @param body data node for put to config DS
278 * @param ar {@link AsyncResponse} which needs to be completed
281 @Path("/data/{identifier:.+}")
283 MediaTypes.APPLICATION_YANG_DATA_JSON,
284 MediaType.APPLICATION_JSON,
286 public void dataJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
287 @Suspended final AsyncResponse ar) {
288 try (var jsonBody = new JsonResourceBody(body)) {
289 completeDataPATCH(server.dataPATCH(identifier, jsonBody), ar);
293 private static void completeDataPATCH(final RestconfFuture<DataPatchResult> future, final AsyncResponse ar) {
294 future.addCallback(new JaxRsRestconfCallback<>(ar) {
296 Response transform(final DataPatchResult result) {
297 final var builder = Response.ok();
298 fillConfigurationMetadata(builder, result);
299 return builder.build();
305 * Ordered list of edits that are applied to the datastore by the server, as defined in
306 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
308 * @param body YANG Patch body
309 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
313 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
315 MediaTypes.APPLICATION_YANG_DATA_JSON,
316 MediaTypes.APPLICATION_YANG_DATA_XML
318 public void dataYangJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
319 try (var jsonBody = new JsonPatchBody(body)) {
320 completeDataYangPATCH(server.dataPATCH(jsonBody), ar);
325 * Ordered list of edits that are applied to the target datastore by the server, as defined in
326 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
328 * @param identifier path to target
329 * @param body YANG Patch body
330 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
333 @Path("/data/{identifier:.+}")
334 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
336 MediaTypes.APPLICATION_YANG_DATA_JSON,
337 MediaTypes.APPLICATION_YANG_DATA_XML
339 public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
340 @Suspended final AsyncResponse ar) {
341 try (var jsonBody = new JsonPatchBody(body)) {
342 completeDataYangPATCH(server.dataPATCH(identifier, jsonBody), ar);
347 * Ordered list of edits that are applied to the datastore by the server, as defined in
348 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
350 * @param body YANG Patch body
351 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
355 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
357 MediaTypes.APPLICATION_YANG_DATA_JSON,
358 MediaTypes.APPLICATION_YANG_DATA_XML
360 public void dataYangXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
361 try (var xmlBody = new XmlPatchBody(body)) {
362 completeDataYangPATCH(server.dataPATCH(xmlBody), ar);
367 * Ordered list of edits that are applied to the target datastore by the server, as defined in
368 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
370 * @param identifier path to target
371 * @param body YANG Patch body
372 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
375 @Path("/data/{identifier:.+}")
376 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
378 MediaTypes.APPLICATION_YANG_DATA_JSON,
379 MediaTypes.APPLICATION_YANG_DATA_XML
381 public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
382 @Suspended final AsyncResponse ar) {
383 try (var xmlBody = new XmlPatchBody(body)) {
384 completeDataYangPATCH(server.dataPATCH(identifier, xmlBody), ar);
388 private static void completeDataYangPATCH(final RestconfFuture<DataYangPatchResult> future,
389 final AsyncResponse ar) {
390 future.addCallback(new JaxRsRestconfCallback<>(ar) {
392 Response transform(final DataYangPatchResult result) {
393 final var status = result.status();
394 final var builder = Response.status(statusOf(status)).entity(status);
395 fillConfigurationMetadata(builder, result);
396 return builder.build();
399 private static Status statusOf(final PatchStatusContext result) {
403 final var globalErrors = result.globalErrors();
404 if (globalErrors != null && !globalErrors.isEmpty()) {
405 return statusOfFirst(globalErrors);
407 for (var edit : result.editCollection()) {
409 final var editErrors = edit.getEditErrors();
410 if (editErrors != null && !editErrors.isEmpty()) {
411 return statusOfFirst(editErrors);
415 return Status.INTERNAL_SERVER_ERROR;
418 private static Status statusOfFirst(final List<RestconfError> error) {
419 return ErrorTags.statusOf(error.get(0).getErrorTag());
425 * Create a top-level data resource.
427 * @param body data node for put to config DS
428 * @param uriInfo URI info
429 * @param ar {@link AsyncResponse} which needs to be completed
434 MediaTypes.APPLICATION_YANG_DATA_JSON,
435 MediaType.APPLICATION_JSON,
437 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
438 @Suspended final AsyncResponse ar) {
439 try (var jsonBody = new JsonChildBody(body)) {
440 completeDataPOST(server.dataPOST(jsonBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
445 * Create a data resource in target.
447 * @param identifier path to target
448 * @param body data node for put to config DS
449 * @param uriInfo URI info
450 * @param ar {@link AsyncResponse} which needs to be completed
453 @Path("/data/{identifier:.+}")
455 MediaTypes.APPLICATION_YANG_DATA_JSON,
456 MediaType.APPLICATION_JSON,
458 public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
459 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
460 completeDataPOST(server.dataPOST(identifier, new JsonDataPostBody(body), QueryParams.normalize(uriInfo)),
465 * Create a top-level data resource.
467 * @param body data node for put to config DS
468 * @param uriInfo URI info
469 * @param ar {@link AsyncResponse} which needs to be completed
474 MediaTypes.APPLICATION_YANG_DATA_XML,
475 MediaType.APPLICATION_XML,
478 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
479 try (var xmlBody = new XmlChildBody(body)) {
480 completeDataPOST(server.dataPOST(xmlBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
485 * Create a data resource in target.
487 * @param identifier path to target
488 * @param body data node for put to config DS
489 * @param uriInfo URI info
490 * @param ar {@link AsyncResponse} which needs to be completed
493 @Path("/data/{identifier:.+}")
495 MediaTypes.APPLICATION_YANG_DATA_XML,
496 MediaType.APPLICATION_XML,
499 public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
500 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
501 completeDataPOST(server.dataPOST(identifier, new XmlDataPostBody(body), QueryParams.normalize(uriInfo)),
505 private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future, final UriInfo uriInfo,
506 final AsyncResponse ar) {
507 future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
509 Response transform(final DataPostResult result) {
510 if (result instanceof CreateResourceResult createResource) {
511 final var builder = Response.created(uriInfo.getBaseUriBuilder()
513 .path(createResource.createdPath().toString())
515 fillConfigurationMetadata(builder, createResource);
516 return builder.build();
518 if (result instanceof InvokeResult invokeOperation) {
519 final var output = invokeOperation.output();
520 return output == null ? Response.noContent().build() : Response.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.created(null) : Response.noContent();
616 fillConfigurationMetadata(builder, result);
617 return builder.build();
623 * List RPC and action operations.
625 * @param ar {@link AsyncResponse} which needs to be completed
630 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
631 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
633 public void operationsGET(@Suspended final AsyncResponse ar) {
634 completeOperationsGet(server.operationsGET(), ar);
638 * Retrieve list of operations and actions supported by the server or device.
640 * @param operation path parameter to identify device and/or operation
641 * @param ar {@link AsyncResponse} which needs to be completed
644 @Path("/operations/{operation:.+}")
646 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
647 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
649 public void operationsGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
650 completeOperationsGet(server.operationsGET(operation), ar);
653 private static void completeOperationsGet(final RestconfFuture<FormattableBody> future, final AsyncResponse ar) {
654 future.addCallback(new JaxRsRestconfCallback<FormattableBody>(ar) {
656 Response transform(final FormattableBody result) {
657 return Response.ok().entity(result).build();
663 * Invoke RPC operation.
665 * @param identifier module name and rpc identifier string for the desired operation
666 * @param body the body of the operation
667 * @param uriInfo URI info
668 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
671 // FIXME: identifier is just a *single* QName
672 @Path("/operations/{identifier:.+}")
674 MediaTypes.APPLICATION_YANG_DATA_XML,
675 MediaType.APPLICATION_XML,
679 MediaTypes.APPLICATION_YANG_DATA_JSON,
680 MediaTypes.APPLICATION_YANG_DATA_XML,
681 MediaType.APPLICATION_JSON,
682 MediaType.APPLICATION_XML,
685 public void operationsXmlPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
686 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
687 try (var xmlBody = new XmlOperationInputBody(body)) {
688 operationsPOST(identifier, uriInfo, ar, xmlBody);
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_JSON,
705 MediaType.APPLICATION_JSON,
708 MediaTypes.APPLICATION_YANG_DATA_JSON,
709 MediaTypes.APPLICATION_YANG_DATA_XML,
710 MediaType.APPLICATION_JSON,
711 MediaType.APPLICATION_XML,
714 public void operationsJsonPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
715 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
716 try (var jsonBody = new JsonOperationInputBody(body)) {
717 operationsPOST(identifier, uriInfo, ar, jsonBody);
721 private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
722 final OperationInputBody body) {
723 server.operationsPOST(uriInfo.getBaseUri(), identifier, QueryParams.normalize(uriInfo), body)
724 .addCallback(new JaxRsRestconfCallback<>(ar) {
726 Response transform(final InvokeResult result) {
727 final var body = result.output();
728 return body == null ? Response.noContent().build()
729 : Response.ok().entity(body).build();
735 * Get revision of IETF YANG Library module.
737 * @param ar {@link AsyncResponse} which needs to be completed
740 @Path("/yang-library-version")
742 MediaTypes.APPLICATION_YANG_DATA_JSON,
743 MediaTypes.APPLICATION_YANG_DATA_XML,
744 MediaType.APPLICATION_JSON,
745 MediaType.APPLICATION_XML,
748 public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
749 server.yangLibraryVersionGET().addCallback(new JaxRsRestconfCallback<NormalizedNodePayload>(ar) {
751 Response transform(final NormalizedNodePayload result) {
752 return Response.ok().entity(result).build();
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();