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.FormattableBody;
49 import org.opendaylight.restconf.api.MediaTypes;
50 import org.opendaylight.restconf.api.QueryParameters;
51 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
52 import org.opendaylight.restconf.common.errors.RestconfError;
53 import org.opendaylight.restconf.common.errors.RestconfFuture;
54 import org.opendaylight.restconf.nb.rfc8040.URLConstants;
55 import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
56 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
57 import org.opendaylight.restconf.server.api.ConfigurationMetadata;
58 import org.opendaylight.restconf.server.api.CreateResourceResult;
59 import org.opendaylight.restconf.server.api.DataGetResult;
60 import org.opendaylight.restconf.server.api.DataPatchResult;
61 import org.opendaylight.restconf.server.api.DataPostResult;
62 import org.opendaylight.restconf.server.api.DataPutResult;
63 import org.opendaylight.restconf.server.api.DataYangPatchResult;
64 import org.opendaylight.restconf.server.api.InvokeResult;
65 import org.opendaylight.restconf.server.api.JsonChildBody;
66 import org.opendaylight.restconf.server.api.JsonDataPostBody;
67 import org.opendaylight.restconf.server.api.JsonOperationInputBody;
68 import org.opendaylight.restconf.server.api.JsonPatchBody;
69 import org.opendaylight.restconf.server.api.JsonResourceBody;
70 import org.opendaylight.restconf.server.api.ModulesGetResult;
71 import org.opendaylight.restconf.server.api.OperationInputBody;
72 import org.opendaylight.restconf.server.api.PatchStatusContext;
73 import org.opendaylight.restconf.server.api.RestconfServer;
74 import org.opendaylight.restconf.server.api.XmlChildBody;
75 import org.opendaylight.restconf.server.api.XmlDataPostBody;
76 import org.opendaylight.restconf.server.api.XmlOperationInputBody;
77 import org.opendaylight.restconf.server.api.XmlPatchBody;
78 import org.opendaylight.restconf.server.api.XmlResourceBody;
79 import org.opendaylight.restconf.server.spi.YangPatchStatusBody;
80 import org.opendaylight.yangtools.yang.common.Empty;
81 import org.opendaylight.yangtools.yang.common.YangConstants;
82 import org.slf4j.Logger;
83 import org.slf4j.LoggerFactory;
86 * Baseline RESTCONF implementation with JAX-RS. Interfaces to a {@link RestconfServer}. Since we need {@link ApiPath}
87 * arguments, we also implement {@link ParamConverterProvider} and provide the appropriate converter. This has the nice
88 * side-effect of suppressing <a href="https://github.com/eclipse-ee4j/jersey/issues/3700">Jersey warnings</a>.
92 public final class JaxRsRestconf implements ParamConverterProvider {
93 private static final Logger LOG = LoggerFactory.getLogger(JaxRsRestconf.class);
94 private static final CacheControl NO_CACHE = CacheControl.valueOf("no-cache");
95 private static final ParamConverter<ApiPath> API_PATH_CONVERTER = new ParamConverter<>() {
97 public ApiPath fromString(final String value) {
98 final var str = nonnull(value);
100 return ApiPath.parseUrl(str);
101 } catch (ParseException e) {
102 throw new IllegalArgumentException(e.getMessage(), e);
107 public String toString(final ApiPath value) {
108 return nonnull(value).toString();
111 private static <T> @NonNull T nonnull(final @Nullable T value) {
113 throw new IllegalArgumentException("value must not be null");
119 private final @NonNull RestconfServer server;
121 public JaxRsRestconf(final RestconfServer server) {
122 this.server = requireNonNull(server);
126 @SuppressWarnings("unchecked")
127 public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType,
128 final Annotation[] annotations) {
129 return ApiPath.class.equals(rawType) ? (ParamConverter<T>) API_PATH_CONVERTER : null;
132 private static @NonNull QueryParameters queryParams(final UriInfo uriInfo) {
134 return QueryParameters.ofMultiValue(uriInfo.getQueryParameters());
135 } catch (IllegalArgumentException e) {
136 throw new BadRequestException(e.getMessage(), e);
141 * Delete the target data resource.
143 * @param identifier path to target
144 * @param ar {@link AsyncResponse} which needs to be completed
147 @Path("/data/{identifier:.+}")
148 @SuppressWarnings("checkstyle:abbreviationAsWordInName")
149 public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier,
150 @Suspended final AsyncResponse ar) {
151 server.dataDELETE(identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
153 Response transform(final Empty result) {
154 return Response.noContent().build();
160 * Get target data resource from data root.
162 * @param uriInfo URI info
163 * @param ar {@link AsyncResponse} which needs to be completed
168 MediaTypes.APPLICATION_YANG_DATA_JSON,
169 MediaTypes.APPLICATION_YANG_DATA_XML,
170 MediaType.APPLICATION_JSON,
171 MediaType.APPLICATION_XML,
174 public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
175 completeDataGET(server.dataGET(queryParams(uriInfo)), ar);
179 * Get target data resource.
181 * @param identifier path to target
182 * @param uriInfo URI info
183 * @param ar {@link AsyncResponse} which needs to be completed
186 @Path("/data/{identifier:.+}")
188 MediaTypes.APPLICATION_YANG_DATA_JSON,
189 MediaTypes.APPLICATION_YANG_DATA_XML,
190 MediaType.APPLICATION_JSON,
191 MediaType.APPLICATION_XML,
194 public void dataGET(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
195 @Suspended final AsyncResponse ar) {
196 completeDataGET(server.dataGET(identifier, queryParams(uriInfo)), ar);
199 private static void completeDataGET(final RestconfFuture<DataGetResult> future, final AsyncResponse ar) {
200 future.addCallback(new JaxRsRestconfCallback<>(ar) {
202 Response transform(final DataGetResult result) {
203 final var builder = Response.ok().entity(result.payload()).cacheControl(NO_CACHE);
204 fillConfigurationMetadata(builder, result);
205 return builder.build();
210 private static void fillConfigurationMetadata(final ResponseBuilder builder, final ConfigurationMetadata metadata) {
211 final var etag = metadata.entityTag();
213 builder.tag(new EntityTag(etag.value(), etag.weak()));
215 final var lastModified = metadata.lastModified();
216 if (lastModified != null) {
217 builder.lastModified(Date.from(lastModified));
222 * Partially modify the target data store, as defined in
223 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
225 * @param body data node for put to config DS
226 * @param ar {@link AsyncResponse} which needs to be completed
231 MediaTypes.APPLICATION_YANG_DATA_XML,
232 MediaType.APPLICATION_XML,
235 public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
236 try (var xmlBody = new XmlResourceBody(body)) {
237 completeDataPATCH(server.dataPATCH(xmlBody), ar);
242 * Partially modify the target data resource, as defined in
243 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
245 * @param identifier path to target
246 * @param body data node for put to config DS
247 * @param ar {@link AsyncResponse} which needs to be completed
250 @Path("/data/{identifier:.+}")
252 MediaTypes.APPLICATION_YANG_DATA_XML,
253 MediaType.APPLICATION_XML,
256 public void dataXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
257 @Suspended final AsyncResponse ar) {
258 try (var xmlBody = new XmlResourceBody(body)) {
259 completeDataPATCH(server.dataPATCH(identifier, xmlBody), ar);
264 * Partially modify the target data store, as defined in
265 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
267 * @param body data node for put to config DS
268 * @param ar {@link AsyncResponse} which needs to be completed
273 MediaTypes.APPLICATION_YANG_DATA_JSON,
274 MediaType.APPLICATION_JSON,
276 public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
277 try (var jsonBody = new JsonResourceBody(body)) {
278 completeDataPATCH(server.dataPATCH(jsonBody), ar);
283 * Partially modify the target data resource, as defined in
284 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
286 * @param identifier path to target
287 * @param body data node for put to config DS
288 * @param ar {@link AsyncResponse} which needs to be completed
291 @Path("/data/{identifier:.+}")
293 MediaTypes.APPLICATION_YANG_DATA_JSON,
294 MediaType.APPLICATION_JSON,
296 public void dataJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
297 @Suspended final AsyncResponse ar) {
298 try (var jsonBody = new JsonResourceBody(body)) {
299 completeDataPATCH(server.dataPATCH(identifier, jsonBody), ar);
303 private static void completeDataPATCH(final RestconfFuture<DataPatchResult> future, final AsyncResponse ar) {
304 future.addCallback(new JaxRsRestconfCallback<>(ar) {
306 Response transform(final DataPatchResult result) {
307 final var builder = Response.ok();
308 fillConfigurationMetadata(builder, result);
309 return builder.build();
315 * Ordered list of edits that are applied to the datastore by the server, as defined in
316 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
318 * @param body YANG Patch body
319 * @param uriInfo URI info
320 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
324 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
326 MediaTypes.APPLICATION_YANG_DATA_JSON,
327 MediaTypes.APPLICATION_YANG_DATA_XML
329 public void dataYangJsonPATCH(final InputStream body, @Context final UriInfo uriInfo,
330 @Suspended final AsyncResponse ar) {
331 try (var jsonBody = new JsonPatchBody(body)) {
332 completeDataYangPATCH(server.dataPATCH(queryParams(uriInfo), jsonBody), ar);
337 * Ordered list of edits that are applied to the target datastore by the server, as defined in
338 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
340 * @param identifier path to target
341 * @param body YANG Patch body
342 * @param uriInfo URI info
343 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
346 @Path("/data/{identifier:.+}")
347 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
349 MediaTypes.APPLICATION_YANG_DATA_JSON,
350 MediaTypes.APPLICATION_YANG_DATA_XML
352 public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
353 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
354 try (var jsonBody = new JsonPatchBody(body)) {
355 completeDataYangPATCH(server.dataPATCH(identifier, queryParams(uriInfo), jsonBody), ar);
360 * Ordered list of edits that are applied to the datastore by the server, as defined in
361 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
363 * @param body YANG Patch body
364 * @param uriInfo URI info
365 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
369 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
371 MediaTypes.APPLICATION_YANG_DATA_JSON,
372 MediaTypes.APPLICATION_YANG_DATA_XML
374 public void dataYangXmlPATCH(final InputStream body, @Context final UriInfo uriInfo,
375 @Suspended final AsyncResponse ar) {
376 try (var xmlBody = new XmlPatchBody(body)) {
377 completeDataYangPATCH(server.dataPATCH(queryParams(uriInfo), xmlBody), ar);
382 * Ordered list of edits that are applied to the target datastore by the server, as defined in
383 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
385 * @param identifier path to target
386 * @param uriInfo URI info
387 * @param body YANG Patch body
388 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
391 @Path("/data/{identifier:.+}")
392 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
394 MediaTypes.APPLICATION_YANG_DATA_JSON,
395 MediaTypes.APPLICATION_YANG_DATA_XML
397 public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
398 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
399 try (var xmlBody = new XmlPatchBody(body)) {
400 completeDataYangPATCH(server.dataPATCH(identifier, queryParams(uriInfo), xmlBody), ar);
404 private static void completeDataYangPATCH(final RestconfFuture<DataYangPatchResult> future,
405 final AsyncResponse ar) {
406 future.addCallback(new JaxRsRestconfCallback<>(ar) {
408 Response transform(final DataYangPatchResult result) {
409 final var status = result.status();
410 final var builder = Response.status(statusOf(status))
411 .entity(new YangPatchStatusBody(result.params(), status));
412 fillConfigurationMetadata(builder, result);
413 return builder.build();
416 private static Status statusOf(final PatchStatusContext result) {
420 final var globalErrors = result.globalErrors();
421 if (globalErrors != null && !globalErrors.isEmpty()) {
422 return statusOfFirst(globalErrors);
424 for (var edit : result.editCollection()) {
426 final var editErrors = edit.getEditErrors();
427 if (editErrors != null && !editErrors.isEmpty()) {
428 return statusOfFirst(editErrors);
432 return Status.INTERNAL_SERVER_ERROR;
435 private static Status statusOfFirst(final List<RestconfError> error) {
436 return ErrorTags.statusOf(error.get(0).getErrorTag());
442 * Create a top-level data resource.
444 * @param body data node for put to config DS
445 * @param uriInfo URI info
446 * @param ar {@link AsyncResponse} which needs to be completed
451 MediaTypes.APPLICATION_YANG_DATA_JSON,
452 MediaType.APPLICATION_JSON,
454 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
455 @Suspended final AsyncResponse ar) {
456 try (var jsonBody = new JsonChildBody(body)) {
457 completeDataPOST(server.dataPOST(queryParams(uriInfo), jsonBody), uriInfo, ar);
462 * Create a data resource in target.
464 * @param identifier path to target
465 * @param body data node for put to config DS
466 * @param uriInfo URI info
467 * @param ar {@link AsyncResponse} which needs to be completed
470 @Path("/data/{identifier:.+}")
472 MediaTypes.APPLICATION_YANG_DATA_JSON,
473 MediaType.APPLICATION_JSON,
475 public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
476 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
477 completeDataPOST(server.dataPOST(identifier, queryParams(uriInfo), new JsonDataPostBody(body)), uriInfo, ar);
481 * Create a top-level data resource.
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
490 MediaTypes.APPLICATION_YANG_DATA_XML,
491 MediaType.APPLICATION_XML,
494 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
495 try (var xmlBody = new XmlChildBody(body)) {
496 completeDataPOST(server.dataPOST(queryParams(uriInfo), xmlBody), uriInfo, ar);
501 * Create a data resource in target.
503 * @param identifier path to target
504 * @param body data node for put to config DS
505 * @param uriInfo URI info
506 * @param ar {@link AsyncResponse} which needs to be completed
509 @Path("/data/{identifier:.+}")
511 MediaTypes.APPLICATION_YANG_DATA_XML,
512 MediaType.APPLICATION_XML,
515 public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
516 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
517 completeDataPOST(server.dataPOST(identifier, queryParams(uriInfo), new XmlDataPostBody(body)), uriInfo, ar);
520 private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future, final UriInfo uriInfo,
521 final AsyncResponse ar) {
522 future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
524 Response transform(final DataPostResult result) {
525 if (result instanceof CreateResourceResult createResource) {
526 final var builder = Response.created(uriInfo.getBaseUriBuilder()
528 .path(createResource.createdPath().toString())
530 fillConfigurationMetadata(builder, createResource);
531 return builder.build();
533 if (result instanceof InvokeResult invokeOperation) {
534 final var output = invokeOperation.output();
535 return output == null ? Response.noContent().build() : Response.ok().entity(output).build();
537 LOG.error("Unhandled result {}", result);
538 return Response.serverError().build();
544 * Replace the data store.
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
553 MediaTypes.APPLICATION_YANG_DATA_JSON,
554 MediaType.APPLICATION_JSON,
556 public void dataJsonPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
557 try (var jsonBody = new JsonResourceBody(body)) {
558 completeDataPUT(server.dataPUT(queryParams(uriInfo), jsonBody), ar);
563 * Create or replace the target data resource.
565 * @param identifier path to target
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
571 @Path("/data/{identifier:.+}")
573 MediaTypes.APPLICATION_YANG_DATA_JSON,
574 MediaType.APPLICATION_JSON,
576 public void dataJsonPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
577 final InputStream body, @Suspended final AsyncResponse ar) {
578 try (var jsonBody = new JsonResourceBody(body)) {
579 completeDataPUT(server.dataPUT(identifier, queryParams(uriInfo), jsonBody), ar);
584 * Replace the data store.
586 * @param uriInfo request URI information
587 * @param body data node for put to config DS
588 * @param ar {@link AsyncResponse} which needs to be completed
593 MediaTypes.APPLICATION_YANG_DATA_XML,
594 MediaType.APPLICATION_XML,
597 public void dataXmlPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
598 try (var xmlBody = new XmlResourceBody(body)) {
599 completeDataPUT(server.dataPUT(queryParams(uriInfo), xmlBody), ar);
604 * Create or replace the target data resource.
606 * @param identifier path to target
607 * @param uriInfo request URI information
608 * @param body data node for put to config DS
609 * @param ar {@link AsyncResponse} which needs to be completed
612 @Path("/data/{identifier:.+}")
614 MediaTypes.APPLICATION_YANG_DATA_XML,
615 MediaType.APPLICATION_XML,
618 public void dataXmlPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
619 final InputStream body, @Suspended final AsyncResponse ar) {
620 try (var xmlBody = new XmlResourceBody(body)) {
621 completeDataPUT(server.dataPUT(identifier, queryParams(uriInfo), xmlBody), ar);
625 private static void completeDataPUT(final RestconfFuture<DataPutResult> future, final AsyncResponse ar) {
626 future.addCallback(new JaxRsRestconfCallback<>(ar) {
628 Response transform(final DataPutResult result) {
629 // Note: no Location header, as it matches the request path
630 final var builder = result.created() ? Response.created(null) : Response.noContent();
631 fillConfigurationMetadata(builder, result);
632 return builder.build();
638 * List RPC and action operations.
640 * @param ar {@link AsyncResponse} which needs to be completed
645 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
646 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
648 public void operationsGET(@Suspended final AsyncResponse ar) {
649 completeOperationsGet(server.operationsGET(), ar);
653 * Retrieve list of operations and actions supported by the server or device.
655 * @param operation path parameter to identify device and/or operation
656 * @param ar {@link AsyncResponse} which needs to be completed
659 @Path("/operations/{operation:.+}")
661 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
662 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
664 public void operationsGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
665 completeOperationsGet(server.operationsGET(operation), ar);
668 private static void completeOperationsGet(final RestconfFuture<FormattableBody> future, final AsyncResponse ar) {
669 future.addCallback(new JaxRsRestconfCallback<FormattableBody>(ar) {
671 Response transform(final FormattableBody result) {
672 return Response.ok().entity(result).build();
678 * Invoke RPC operation.
680 * @param identifier module name and rpc identifier string for the desired operation
681 * @param body the body of the operation
682 * @param uriInfo URI info
683 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
686 // FIXME: identifier is just a *single* QName
687 @Path("/operations/{identifier:.+}")
689 MediaTypes.APPLICATION_YANG_DATA_XML,
690 MediaType.APPLICATION_XML,
694 MediaTypes.APPLICATION_YANG_DATA_JSON,
695 MediaTypes.APPLICATION_YANG_DATA_XML,
696 MediaType.APPLICATION_JSON,
697 MediaType.APPLICATION_XML,
700 public void operationsXmlPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
701 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
702 try (var xmlBody = new XmlOperationInputBody(body)) {
703 operationsPOST(identifier, uriInfo, ar, xmlBody);
708 * Invoke RPC operation.
710 * @param identifier module name and rpc identifier string for the desired operation
711 * @param body the body of the operation
712 * @param uriInfo URI info
713 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
716 // FIXME: identifier is just a *single* QName
717 @Path("/operations/{identifier:.+}")
719 MediaTypes.APPLICATION_YANG_DATA_JSON,
720 MediaType.APPLICATION_JSON,
723 MediaTypes.APPLICATION_YANG_DATA_JSON,
724 MediaTypes.APPLICATION_YANG_DATA_XML,
725 MediaType.APPLICATION_JSON,
726 MediaType.APPLICATION_XML,
729 public void operationsJsonPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
730 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
731 try (var jsonBody = new JsonOperationInputBody(body)) {
732 operationsPOST(identifier, uriInfo, ar, jsonBody);
736 private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
737 final OperationInputBody body) {
738 server.operationsPOST(uriInfo.getBaseUri(), identifier, queryParams(uriInfo), body)
739 .addCallback(new JaxRsRestconfCallback<>(ar) {
741 Response transform(final InvokeResult result) {
742 final var body = result.output();
743 return body == null ? Response.noContent().build()
744 : Response.ok().entity(body).build();
750 * Get revision of IETF YANG Library module.
752 * @param ar {@link AsyncResponse} which needs to be completed
755 @Path("/yang-library-version")
757 MediaTypes.APPLICATION_YANG_DATA_JSON,
758 MediaTypes.APPLICATION_YANG_DATA_XML,
759 MediaType.APPLICATION_JSON,
760 MediaType.APPLICATION_XML,
763 public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
764 server.yangLibraryVersionGET().addCallback(new JaxRsRestconfCallback<NormalizedNodePayload>(ar) {
766 Response transform(final NormalizedNodePayload result) {
767 return Response.ok().entity(result).build();
772 // FIXME: References to these resources are generated by our yang-library implementation. That means:
773 // - We really need to formalize the parameter structure so we get some help from JAX-RS during matching
775 // - optional yang-ext:mount prefix(es)
776 // - mandatory module name
777 // - optional module revision
778 // - We really should use /yang-library-module/{name}(/{revision})?
779 // - We seem to be lacking explicit support for submodules in there -- and those locations should then point
780 // to /yang-library-submodule/{moduleName}(/{moduleRevision})?/{name}(/{revision})? so as to look the
781 // submodule up efficiently and allow for the weird case where there are two submodules with the same name
782 // (that is currently not supported by the parser, but it will be in the future)
783 // - It does not make sense to support yang-ext:mount, unless we also intercept mount points and rewrite
784 // yang-library locations. We most likely want to do that to ensure users are not tempted to connect to
788 * Get schema of specific module.
790 * @param fileName source file name
791 * @param revision source revision
792 * @param ar {@link AsyncResponse} which needs to be completed
795 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
796 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
797 public void modulesYangGET(@PathParam("fileName") final String fileName,
798 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
799 completeModulesGET(server.modulesYangGET(fileName, revision), ar);
803 * Get schema of specific module.
805 * @param mountPath mount point path
806 * @param fileName source file name
807 * @param revision source revision
808 * @param ar {@link AsyncResponse} which needs to be completed
811 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
812 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
813 public void modulesYangGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
814 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
815 @Suspended final AsyncResponse ar) {
816 completeModulesGET(server.modulesYangGET(mountPath, fileName, revision), ar);
820 * Get schema of specific module.
822 * @param fileName source file name
823 * @param revision source revision
824 * @param ar {@link AsyncResponse} which needs to be completed
827 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
828 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
829 public void modulesYinGET(@PathParam("fileName") final String fileName,
830 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
831 completeModulesGET(server.modulesYinGET(fileName, revision), ar);
835 * Get schema of specific module.
837 * @param mountPath mount point path
838 * @param fileName source file name
839 * @param revision source revision
840 * @param ar {@link AsyncResponse} which needs to be completed
843 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
844 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
845 public void modulesYinGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
846 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
847 @Suspended final AsyncResponse ar) {
848 completeModulesGET(server.modulesYinGET(mountPath, fileName, revision), ar);
851 private static void completeModulesGET(final RestconfFuture<ModulesGetResult> future, final AsyncResponse ar) {
852 future.addCallback(new JaxRsRestconfCallback<>(ar) {
854 Response transform(final ModulesGetResult result) {
857 reader = result.source().openStream();
858 } catch (IOException e) {
859 throw new RestconfDocumentedException("Cannot open source", e);
861 return Response.ok(reader).build();