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.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.HttpStatusCode;
48 import org.opendaylight.restconf.api.MediaTypes;
49 import org.opendaylight.restconf.api.QueryParameters;
50 import org.opendaylight.restconf.api.query.PrettyPrintParam;
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.ErrorTagMapping;
55 import org.opendaylight.restconf.nb.rfc8040.URLConstants;
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.ServerRequest;
75 import org.opendaylight.restconf.server.api.XmlChildBody;
76 import org.opendaylight.restconf.server.api.XmlDataPostBody;
77 import org.opendaylight.restconf.server.api.XmlOperationInputBody;
78 import org.opendaylight.restconf.server.api.XmlPatchBody;
79 import org.opendaylight.restconf.server.api.XmlResourceBody;
80 import org.opendaylight.restconf.server.spi.YangPatchStatusBody;
81 import org.opendaylight.yangtools.yang.common.Empty;
82 import org.opendaylight.yangtools.yang.common.YangConstants;
83 import org.slf4j.Logger;
84 import org.slf4j.LoggerFactory;
87 * Baseline RESTCONF implementation with JAX-RS. Interfaces to a {@link RestconfServer}. Since we need {@link ApiPath}
88 * arguments, we also implement {@link ParamConverterProvider} and provide the appropriate converter. This has the nice
89 * side-effect of suppressing <a href="https://github.com/eclipse-ee4j/jersey/issues/3700">Jersey warnings</a>.
93 public final class JaxRsRestconf implements ParamConverterProvider {
94 private static final Logger LOG = LoggerFactory.getLogger(JaxRsRestconf.class);
95 private static final CacheControl NO_CACHE = CacheControl.valueOf("no-cache");
96 private static final ParamConverter<ApiPath> API_PATH_CONVERTER = new ParamConverter<>() {
98 public ApiPath fromString(final String value) {
99 final var str = nonnull(value);
101 return ApiPath.parseUrl(str);
102 } catch (ParseException e) {
103 throw new IllegalArgumentException(e.getMessage(), e);
108 public String toString(final ApiPath value) {
109 return nonnull(value).toString();
112 private static <T> @NonNull T nonnull(final @Nullable T value) {
114 throw new IllegalArgumentException("value must not be null");
120 private final @NonNull RestconfServer server;
121 private final @NonNull ServerRequest emptyRequest;
122 private final @NonNull PrettyPrintParam prettyPrint;
123 private final @NonNull ErrorTagMapping errorTagMapping;
125 public JaxRsRestconf(final RestconfServer server, final ErrorTagMapping errorTagMapping,
126 final PrettyPrintParam prettyPrint) {
127 this.server = requireNonNull(server);
128 this.errorTagMapping = requireNonNull(errorTagMapping);
129 this.prettyPrint = requireNonNull(prettyPrint);
130 emptyRequest = ServerRequest.of(QueryParameters.of(), prettyPrint);
132 LOG.info("RESTCONF data-missing condition is reported as HTTP status {}", switch (errorTagMapping) {
133 case ERRATA_5565 -> "404 (Errata 5565)";
134 case RFC8040 -> "409 (RFC8040)";
138 private @NonNull ServerRequest requestOf(final UriInfo uriInfo) {
139 final QueryParameters params;
141 params = QueryParameters.ofMultiValue(uriInfo.getQueryParameters());
142 } catch (IllegalArgumentException e) {
143 throw new BadRequestException(e.getMessage(), e);
145 return params.isEmpty() ? emptyRequest : ServerRequest.of(params, prettyPrint);
149 @SuppressWarnings("unchecked")
150 public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType,
151 final Annotation[] annotations) {
152 return ApiPath.class.equals(rawType) ? (ParamConverter<T>) API_PATH_CONVERTER : null;
156 * Delete the target data resource.
158 * @param identifier path to target
159 * @param ar {@link AsyncResponse} which needs to be completed
162 @Path("/data/{identifier:.+}")
163 @SuppressWarnings("checkstyle:abbreviationAsWordInName")
164 public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier,
165 @Suspended final AsyncResponse ar) {
166 server.dataDELETE(emptyRequest, identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
168 Response transform(final Empty result) {
169 return Response.noContent().build();
175 * Get target data resource from data root.
177 * @param uriInfo URI info
178 * @param ar {@link AsyncResponse} which needs to be completed
183 MediaTypes.APPLICATION_YANG_DATA_JSON,
184 MediaTypes.APPLICATION_YANG_DATA_XML,
185 MediaType.APPLICATION_JSON,
186 MediaType.APPLICATION_XML,
189 public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
190 completeDataGET(server.dataGET(requestOf(uriInfo)), ar);
194 * Get target data resource.
196 * @param identifier path to target
197 * @param uriInfo URI info
198 * @param ar {@link AsyncResponse} which needs to be completed
201 @Path("/data/{identifier:.+}")
203 MediaTypes.APPLICATION_YANG_DATA_JSON,
204 MediaTypes.APPLICATION_YANG_DATA_XML,
205 MediaType.APPLICATION_JSON,
206 MediaType.APPLICATION_XML,
209 public void dataGET(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
210 @Suspended final AsyncResponse ar) {
211 completeDataGET(server.dataGET(requestOf(uriInfo), identifier), ar);
214 private static void completeDataGET(final RestconfFuture<DataGetResult> future, final AsyncResponse ar) {
215 future.addCallback(new JaxRsRestconfCallback<>(ar) {
217 Response transform(final DataGetResult result) {
218 final var builder = Response.ok().entity(result.payload()).cacheControl(NO_CACHE);
219 fillConfigurationMetadata(builder, result);
220 return builder.build();
225 private static void fillConfigurationMetadata(final ResponseBuilder builder, final ConfigurationMetadata metadata) {
226 final var etag = metadata.entityTag();
228 builder.tag(new EntityTag(etag.value(), etag.weak()));
230 final var lastModified = metadata.lastModified();
231 if (lastModified != null) {
232 builder.lastModified(Date.from(lastModified));
237 * Partially modify the target data store, as defined in
238 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
240 * @param body data node for put to config DS
241 * @param ar {@link AsyncResponse} which needs to be completed
246 MediaTypes.APPLICATION_YANG_DATA_XML,
247 MediaType.APPLICATION_XML,
250 public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
251 try (var xmlBody = new XmlResourceBody(body)) {
252 completeDataPATCH(server.dataPATCH(emptyRequest, xmlBody), ar);
257 * Partially modify the target data resource, 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 identifier path to target
261 * @param body data node for put to config DS
262 * @param ar {@link AsyncResponse} which needs to be completed
265 @Path("/data/{identifier:.+}")
267 MediaTypes.APPLICATION_YANG_DATA_XML,
268 MediaType.APPLICATION_XML,
271 public void dataXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
272 @Suspended final AsyncResponse ar) {
273 try (var xmlBody = new XmlResourceBody(body)) {
274 completeDataPATCH(server.dataPATCH(emptyRequest, identifier, xmlBody), ar);
279 * Partially modify the target data store, as defined in
280 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
282 * @param body data node for put to config DS
283 * @param ar {@link AsyncResponse} which needs to be completed
288 MediaTypes.APPLICATION_YANG_DATA_JSON,
289 MediaType.APPLICATION_JSON,
291 public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
292 try (var jsonBody = new JsonResourceBody(body)) {
293 completeDataPATCH(server.dataPATCH(emptyRequest, jsonBody), ar);
298 * Partially modify the target data resource, as defined in
299 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
301 * @param identifier path to target
302 * @param body data node for put to config DS
303 * @param ar {@link AsyncResponse} which needs to be completed
306 @Path("/data/{identifier:.+}")
308 MediaTypes.APPLICATION_YANG_DATA_JSON,
309 MediaType.APPLICATION_JSON,
311 public void dataJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
312 @Suspended final AsyncResponse ar) {
313 try (var jsonBody = new JsonResourceBody(body)) {
314 completeDataPATCH(server.dataPATCH(emptyRequest, identifier, jsonBody), ar);
318 private static void completeDataPATCH(final RestconfFuture<DataPatchResult> future, final AsyncResponse ar) {
319 future.addCallback(new JaxRsRestconfCallback<>(ar) {
321 Response transform(final DataPatchResult result) {
322 final var builder = Response.ok();
323 fillConfigurationMetadata(builder, result);
324 return builder.build();
330 * Ordered list of edits that are applied to the datastore by the server, as defined in
331 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
333 * @param body YANG Patch body
334 * @param uriInfo URI info
335 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
339 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
341 MediaTypes.APPLICATION_YANG_DATA_JSON,
342 MediaTypes.APPLICATION_YANG_DATA_XML
344 public void dataYangJsonPATCH(final InputStream body, @Context final UriInfo uriInfo,
345 @Suspended final AsyncResponse ar) {
346 try (var jsonBody = new JsonPatchBody(body)) {
347 completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), jsonBody), ar);
352 * Ordered list of edits that are applied to the target datastore by the server, as defined in
353 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
355 * @param identifier path to target
356 * @param body YANG Patch body
357 * @param uriInfo URI info
358 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
361 @Path("/data/{identifier:.+}")
362 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
364 MediaTypes.APPLICATION_YANG_DATA_JSON,
365 MediaTypes.APPLICATION_YANG_DATA_XML
367 public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
368 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
369 try (var jsonBody = new JsonPatchBody(body)) {
370 completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), identifier, jsonBody), ar);
375 * Ordered list of edits that are applied to the datastore by the server, as defined in
376 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
378 * @param body YANG Patch body
379 * @param uriInfo URI info
380 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
384 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
386 MediaTypes.APPLICATION_YANG_DATA_JSON,
387 MediaTypes.APPLICATION_YANG_DATA_XML
389 public void dataYangXmlPATCH(final InputStream body, @Context final UriInfo uriInfo,
390 @Suspended final AsyncResponse ar) {
391 try (var xmlBody = new XmlPatchBody(body)) {
392 completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), xmlBody), ar);
397 * Ordered list of edits that are applied to the target datastore by the server, as defined in
398 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
400 * @param identifier path to target
401 * @param uriInfo URI info
402 * @param body YANG Patch body
403 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
406 @Path("/data/{identifier:.+}")
407 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
409 MediaTypes.APPLICATION_YANG_DATA_JSON,
410 MediaTypes.APPLICATION_YANG_DATA_XML
412 public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
413 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
414 try (var xmlBody = new XmlPatchBody(body)) {
415 completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), identifier, xmlBody), ar);
419 private void completeDataYangPATCH(final RestconfFuture<DataYangPatchResult> future,
420 final AsyncResponse ar) {
421 future.addCallback(new JaxRsRestconfCallback<>(ar) {
423 Response transform(final DataYangPatchResult result) {
424 final var patchStatus = result.status();
425 final var statusCode = statusOf(patchStatus);
426 final var builder = Response.status(statusCode.code(), statusCode.phrase())
427 .entity(new YangPatchStatusBody(patchStatus));
428 fillConfigurationMetadata(builder, result);
429 return builder.build();
432 private HttpStatusCode statusOf(final PatchStatusContext result) {
434 return HttpStatusCode.OK;
436 final var globalErrors = result.globalErrors();
437 if (globalErrors != null && !globalErrors.isEmpty()) {
438 return statusOfFirst(globalErrors);
440 for (var edit : result.editCollection()) {
442 final var editErrors = edit.getEditErrors();
443 if (editErrors != null && !editErrors.isEmpty()) {
444 return statusOfFirst(editErrors);
448 return HttpStatusCode.INTERNAL_SERVER_ERROR;
451 private @NonNull HttpStatusCode statusOfFirst(final List<RestconfError> error) {
452 return errorTagMapping.statusOf(error.get(0).getErrorTag());
458 * Create a top-level data resource.
460 * @param body data node for put to config DS
461 * @param uriInfo URI info
462 * @param ar {@link AsyncResponse} which needs to be completed
467 MediaTypes.APPLICATION_YANG_DATA_JSON,
468 MediaType.APPLICATION_JSON,
470 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
471 @Suspended final AsyncResponse ar) {
472 try (var jsonBody = new JsonChildBody(body)) {
473 final var request = requestOf(uriInfo);
474 completeDataPOST(server.dataPOST(request, jsonBody), request.prettyPrint(), uriInfo, ar);
479 * Create a data resource in target.
481 * @param identifier path to target
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
487 @Path("/data/{identifier:.+}")
489 MediaTypes.APPLICATION_YANG_DATA_JSON,
490 MediaType.APPLICATION_JSON,
492 public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
493 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
494 final var request = requestOf(uriInfo);
495 completeDataPOST(server.dataPOST(request, identifier, new JsonDataPostBody(body)), request.prettyPrint(),
500 * Create a top-level data resource.
502 * @param body data node for put to config DS
503 * @param uriInfo URI info
504 * @param ar {@link AsyncResponse} which needs to be completed
509 MediaTypes.APPLICATION_YANG_DATA_XML,
510 MediaType.APPLICATION_XML,
513 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
514 try (var xmlBody = new XmlChildBody(body)) {
515 final var request = requestOf(uriInfo);
516 completeDataPOST(server.dataPOST(request, xmlBody), request.prettyPrint(), uriInfo, ar);
521 * Create a data resource in target.
523 * @param identifier path to target
524 * @param body data node for put to config DS
525 * @param uriInfo URI info
526 * @param ar {@link AsyncResponse} which needs to be completed
529 @Path("/data/{identifier:.+}")
531 MediaTypes.APPLICATION_YANG_DATA_XML,
532 MediaType.APPLICATION_XML,
535 public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
536 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
537 final var request = requestOf(uriInfo);
538 completeDataPOST(server.dataPOST(request, identifier, new XmlDataPostBody(body)), request.prettyPrint(),
542 private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future,
543 final PrettyPrintParam prettyPrint, final UriInfo uriInfo, final AsyncResponse ar) {
544 future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
546 Response transform(final DataPostResult result) {
547 if (result instanceof CreateResourceResult createResource) {
548 final var builder = Response.created(uriInfo.getBaseUriBuilder()
550 .path(createResource.createdPath().toString())
552 fillConfigurationMetadata(builder, createResource);
553 return builder.build();
555 if (result instanceof InvokeResult invokeOperation) {
556 final var output = invokeOperation.output();
557 return output == null ? Response.noContent().build()
558 : Response.ok().entity(new JaxRsFormattableBody(output, prettyPrint)).build();
560 LOG.error("Unhandled result {}", result);
561 return Response.serverError().build();
567 * Replace the data store.
569 * @param uriInfo request URI information
570 * @param body data node for put to config DS
571 * @param ar {@link AsyncResponse} which needs to be completed
576 MediaTypes.APPLICATION_YANG_DATA_JSON,
577 MediaType.APPLICATION_JSON,
579 public void dataJsonPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
580 try (var jsonBody = new JsonResourceBody(body)) {
581 completeDataPUT(server.dataPUT(requestOf(uriInfo), jsonBody), 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_JSON,
597 MediaType.APPLICATION_JSON,
599 public void dataJsonPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
600 final InputStream body, @Suspended final AsyncResponse ar) {
601 try (var jsonBody = new JsonResourceBody(body)) {
602 completeDataPUT(server.dataPUT(requestOf(uriInfo), identifier, jsonBody), ar);
607 * Replace the data store.
609 * @param uriInfo request URI information
610 * @param body data node for put to config DS
611 * @param ar {@link AsyncResponse} which needs to be completed
616 MediaTypes.APPLICATION_YANG_DATA_XML,
617 MediaType.APPLICATION_XML,
620 public void dataXmlPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
621 try (var xmlBody = new XmlResourceBody(body)) {
622 completeDataPUT(server.dataPUT(requestOf(uriInfo), xmlBody), ar);
627 * Create or replace the target data resource.
629 * @param identifier path to target
630 * @param uriInfo request URI information
631 * @param body data node for put to config DS
632 * @param ar {@link AsyncResponse} which needs to be completed
635 @Path("/data/{identifier:.+}")
637 MediaTypes.APPLICATION_YANG_DATA_XML,
638 MediaType.APPLICATION_XML,
641 public void dataXmlPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
642 final InputStream body, @Suspended final AsyncResponse ar) {
643 try (var xmlBody = new XmlResourceBody(body)) {
644 completeDataPUT(server.dataPUT(requestOf(uriInfo), identifier, xmlBody), ar);
648 private static void completeDataPUT(final RestconfFuture<DataPutResult> future, final AsyncResponse ar) {
649 future.addCallback(new JaxRsRestconfCallback<>(ar) {
651 Response transform(final DataPutResult result) {
652 // Note: no Location header, as it matches the request path
653 final var builder = result.created() ? Response.created(null) : Response.noContent();
654 fillConfigurationMetadata(builder, result);
655 return builder.build();
661 * List RPC and action operations.
663 * @param ar {@link AsyncResponse} which needs to be completed
668 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
669 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
671 public void operationsGET(@Suspended final AsyncResponse ar) {
672 server.operationsGET(emptyRequest).addCallback(new FormattableBodyCallback(ar, prettyPrint));
676 * Retrieve list of operations and actions supported by the server or device.
678 * @param operation path parameter to identify device and/or operation
679 * @param ar {@link AsyncResponse} which needs to be completed
682 @Path("/operations/{operation:.+}")
684 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
685 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
687 public void operationsGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
688 server.operationsGET(emptyRequest, operation)
689 .addCallback(new FormattableBodyCallback(ar, prettyPrint));
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_XML,
705 MediaType.APPLICATION_XML,
709 MediaTypes.APPLICATION_YANG_DATA_JSON,
710 MediaTypes.APPLICATION_YANG_DATA_XML,
711 MediaType.APPLICATION_JSON,
712 MediaType.APPLICATION_XML,
715 public void operationsXmlPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
716 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
717 try (var xmlBody = new XmlOperationInputBody(body)) {
718 operationsPOST(identifier, uriInfo, ar, xmlBody);
723 * Invoke RPC operation.
725 * @param identifier module name and rpc identifier string for the desired operation
726 * @param body the body of the operation
727 * @param uriInfo URI info
728 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
731 // FIXME: identifier is just a *single* QName
732 @Path("/operations/{identifier:.+}")
734 MediaTypes.APPLICATION_YANG_DATA_JSON,
735 MediaType.APPLICATION_JSON,
738 MediaTypes.APPLICATION_YANG_DATA_JSON,
739 MediaTypes.APPLICATION_YANG_DATA_XML,
740 MediaType.APPLICATION_JSON,
741 MediaType.APPLICATION_XML,
744 public void operationsJsonPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
745 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
746 try (var jsonBody = new JsonOperationInputBody(body)) {
747 operationsPOST(identifier, uriInfo, ar, jsonBody);
751 private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
752 final OperationInputBody body) {
753 server.operationsPOST(requestOf(uriInfo), uriInfo.getBaseUri(), identifier, body)
754 .addCallback(new JaxRsRestconfCallback<>(ar) {
756 Response transform(final InvokeResult result) {
757 final var body = result.output();
758 return body == null ? Response.noContent().build()
759 : Response.ok().entity(body).build();
765 * Get revision of IETF YANG Library module.
767 * @param ar {@link AsyncResponse} which needs to be completed
770 @Path("/yang-library-version")
772 MediaTypes.APPLICATION_YANG_DATA_JSON,
773 MediaTypes.APPLICATION_YANG_DATA_XML,
774 MediaType.APPLICATION_JSON,
775 MediaType.APPLICATION_XML,
778 public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
779 server.yangLibraryVersionGET(emptyRequest).addCallback(new FormattableBodyCallback(ar, prettyPrint));
782 // FIXME: References to these resources are generated by our yang-library implementation. That means:
783 // - We really need to formalize the parameter structure so we get some help from JAX-RS during matching
785 // - optional yang-ext:mount prefix(es)
786 // - mandatory module name
787 // - optional module revision
788 // - We really should use /yang-library-module/{name}(/{revision})?
789 // - We seem to be lacking explicit support for submodules in there -- and those locations should then point
790 // to /yang-library-submodule/{moduleName}(/{moduleRevision})?/{name}(/{revision})? so as to look the
791 // submodule up efficiently and allow for the weird case where there are two submodules with the same name
792 // (that is currently not supported by the parser, but it will be in the future)
793 // - It does not make sense to support yang-ext:mount, unless we also intercept mount points and rewrite
794 // yang-library locations. We most likely want to do that to ensure users are not tempted to connect to
798 * Get schema of specific module.
800 * @param fileName source file name
801 * @param revision source revision
802 * @param ar {@link AsyncResponse} which needs to be completed
805 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
806 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
807 public void modulesYangGET(@PathParam("fileName") final String fileName,
808 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
809 completeModulesGET(server.modulesYangGET(emptyRequest, fileName, revision), ar);
813 * Get schema of specific module.
815 * @param mountPath mount point path
816 * @param fileName source file name
817 * @param revision source revision
818 * @param ar {@link AsyncResponse} which needs to be completed
821 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
822 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
823 public void modulesYangGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
824 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
825 @Suspended final AsyncResponse ar) {
826 completeModulesGET(server.modulesYangGET(emptyRequest, mountPath, fileName, revision), ar);
830 * Get schema of specific module.
832 * @param fileName source file name
833 * @param revision source revision
834 * @param ar {@link AsyncResponse} which needs to be completed
837 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
838 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
839 public void modulesYinGET(@PathParam("fileName") final String fileName,
840 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
841 completeModulesGET(server.modulesYinGET(emptyRequest, fileName, revision), ar);
845 * Get schema of specific module.
847 * @param mountPath mount point path
848 * @param fileName source file name
849 * @param revision source revision
850 * @param ar {@link AsyncResponse} which needs to be completed
853 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
854 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
855 public void modulesYinGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
856 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
857 @Suspended final AsyncResponse ar) {
858 completeModulesGET(server.modulesYinGET(emptyRequest, mountPath, fileName, revision), ar);
861 private static void completeModulesGET(final RestconfFuture<ModulesGetResult> future, final AsyncResponse ar) {
862 future.addCallback(new JaxRsRestconfCallback<>(ar) {
864 Response transform(final ModulesGetResult result) {
867 reader = result.source().openStream();
868 } catch (IOException e) {
869 throw new RestconfDocumentedException("Cannot open source", e);
871 return Response.ok(reader).build();