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.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 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;
132 * Delete the target data resource.
134 * @param identifier path to target
135 * @param ar {@link AsyncResponse} which needs to be completed
138 @Path("/data/{identifier:.+}")
139 @SuppressWarnings("checkstyle:abbreviationAsWordInName")
140 public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier,
141 @Suspended final AsyncResponse ar) {
142 server.dataDELETE(identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
144 Response transform(final Empty result) {
145 return Response.noContent().build();
151 * Get target data resource from data root.
153 * @param uriInfo URI info
154 * @param ar {@link AsyncResponse} which needs to be completed
159 MediaTypes.APPLICATION_YANG_DATA_JSON,
160 MediaTypes.APPLICATION_YANG_DATA_XML,
161 MediaType.APPLICATION_JSON,
162 MediaType.APPLICATION_XML,
165 public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
166 completeDataGET(server.dataGET(QueryParams.newDataGetParams(uriInfo)), ar);
170 * Get target data resource.
172 * @param identifier path to target
173 * @param uriInfo URI info
174 * @param ar {@link AsyncResponse} which needs to be completed
177 @Path("/data/{identifier:.+}")
179 MediaTypes.APPLICATION_YANG_DATA_JSON,
180 MediaTypes.APPLICATION_YANG_DATA_XML,
181 MediaType.APPLICATION_JSON,
182 MediaType.APPLICATION_XML,
185 public void dataGET(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
186 @Suspended final AsyncResponse ar) {
187 completeDataGET(server.dataGET(identifier, QueryParams.newDataGetParams(uriInfo)), ar);
190 private static void completeDataGET(final RestconfFuture<DataGetResult> future, final AsyncResponse ar) {
191 future.addCallback(new JaxRsRestconfCallback<>(ar) {
193 Response transform(final DataGetResult result) {
194 final var builder = Response.ok().entity(result.payload()).cacheControl(NO_CACHE);
195 fillConfigurationMetadata(builder, result);
196 return builder.build();
201 private static void fillConfigurationMetadata(final ResponseBuilder builder, final ConfigurationMetadata metadata) {
202 final var etag = metadata.entityTag();
204 builder.tag(new EntityTag(etag.value(), etag.weak()));
206 final var lastModified = metadata.lastModified();
207 if (lastModified != null) {
208 builder.lastModified(Date.from(lastModified));
213 * Partially modify the target data store, as defined in
214 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
216 * @param body data node for put to config DS
217 * @param ar {@link AsyncResponse} which needs to be completed
222 MediaTypes.APPLICATION_YANG_DATA_XML,
223 MediaType.APPLICATION_XML,
226 public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
227 try (var xmlBody = new XmlResourceBody(body)) {
228 completeDataPATCH(server.dataPATCH(xmlBody), ar);
233 * Partially modify the target data resource, as defined in
234 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
236 * @param identifier path to target
237 * @param body data node for put to config DS
238 * @param ar {@link AsyncResponse} which needs to be completed
241 @Path("/data/{identifier:.+}")
243 MediaTypes.APPLICATION_YANG_DATA_XML,
244 MediaType.APPLICATION_XML,
247 public void dataXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
248 @Suspended final AsyncResponse ar) {
249 try (var xmlBody = new XmlResourceBody(body)) {
250 completeDataPATCH(server.dataPATCH(identifier, xmlBody), ar);
255 * Partially modify the target data store, as defined in
256 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
258 * @param body data node for put to config DS
259 * @param ar {@link AsyncResponse} which needs to be completed
264 MediaTypes.APPLICATION_YANG_DATA_JSON,
265 MediaType.APPLICATION_JSON,
267 public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
268 try (var jsonBody = new JsonResourceBody(body)) {
269 completeDataPATCH(server.dataPATCH(jsonBody), ar);
274 * Partially modify the target data resource, as defined in
275 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
277 * @param identifier path to target
278 * @param body data node for put to config DS
279 * @param ar {@link AsyncResponse} which needs to be completed
282 @Path("/data/{identifier:.+}")
284 MediaTypes.APPLICATION_YANG_DATA_JSON,
285 MediaType.APPLICATION_JSON,
287 public void dataJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
288 @Suspended final AsyncResponse ar) {
289 try (var jsonBody = new JsonResourceBody(body)) {
290 completeDataPATCH(server.dataPATCH(identifier, jsonBody), ar);
294 private static void completeDataPATCH(final RestconfFuture<DataPatchResult> future, final AsyncResponse ar) {
295 future.addCallback(new JaxRsRestconfCallback<>(ar) {
297 Response transform(final DataPatchResult result) {
298 final var builder = Response.ok();
299 fillConfigurationMetadata(builder, result);
300 return builder.build();
306 * Ordered list of edits that are applied to the datastore by the server, as defined in
307 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
309 * @param body YANG Patch body
310 * @param uriInfo URI info
311 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
315 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
317 MediaTypes.APPLICATION_YANG_DATA_JSON,
318 MediaTypes.APPLICATION_YANG_DATA_XML
320 public void dataYangJsonPATCH(final InputStream body, @Context final UriInfo uriInfo,
321 @Suspended final AsyncResponse ar) {
322 try (var jsonBody = new JsonPatchBody(body)) {
323 completeDataYangPATCH(server.dataPATCH(QueryParams.normalize(uriInfo), jsonBody), ar);
328 * Ordered list of edits that are applied to the target datastore by the server, as defined in
329 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
331 * @param identifier path to target
332 * @param body YANG Patch body
333 * @param uriInfo URI info
334 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
337 @Path("/data/{identifier:.+}")
338 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
340 MediaTypes.APPLICATION_YANG_DATA_JSON,
341 MediaTypes.APPLICATION_YANG_DATA_XML
343 public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
344 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
345 try (var jsonBody = new JsonPatchBody(body)) {
346 completeDataYangPATCH(server.dataPATCH(identifier, QueryParams.normalize(uriInfo), jsonBody), ar);
351 * Ordered list of edits that are applied to the datastore by the server, as defined in
352 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
354 * @param body YANG Patch body
355 * @param uriInfo URI info
356 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
360 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
362 MediaTypes.APPLICATION_YANG_DATA_JSON,
363 MediaTypes.APPLICATION_YANG_DATA_XML
365 public void dataYangXmlPATCH(final InputStream body, @Context final UriInfo uriInfo,
366 @Suspended final AsyncResponse ar) {
367 try (var xmlBody = new XmlPatchBody(body)) {
368 completeDataYangPATCH(server.dataPATCH(QueryParams.normalize(uriInfo), xmlBody), ar);
373 * Ordered list of edits that are applied to the target datastore by the server, as defined in
374 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
376 * @param identifier path to target
377 * @param uriInfo URI info
378 * @param body YANG Patch body
379 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
382 @Path("/data/{identifier:.+}")
383 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
385 MediaTypes.APPLICATION_YANG_DATA_JSON,
386 MediaTypes.APPLICATION_YANG_DATA_XML
388 public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
389 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
390 try (var xmlBody = new XmlPatchBody(body)) {
391 completeDataYangPATCH(server.dataPATCH(identifier, QueryParams.normalize(uriInfo), xmlBody), ar);
395 private static void completeDataYangPATCH(final RestconfFuture<DataYangPatchResult> future,
396 final AsyncResponse ar) {
397 future.addCallback(new JaxRsRestconfCallback<>(ar) {
399 Response transform(final DataYangPatchResult result) {
400 final var status = result.status();
401 final var builder = Response.status(statusOf(status))
402 .entity(new YangPatchStatusBody(result.params(), status));
403 fillConfigurationMetadata(builder, result);
404 return builder.build();
407 private static Status statusOf(final PatchStatusContext result) {
411 final var globalErrors = result.globalErrors();
412 if (globalErrors != null && !globalErrors.isEmpty()) {
413 return statusOfFirst(globalErrors);
415 for (var edit : result.editCollection()) {
417 final var editErrors = edit.getEditErrors();
418 if (editErrors != null && !editErrors.isEmpty()) {
419 return statusOfFirst(editErrors);
423 return Status.INTERNAL_SERVER_ERROR;
426 private static Status statusOfFirst(final List<RestconfError> error) {
427 return ErrorTags.statusOf(error.get(0).getErrorTag());
433 * Create a top-level data resource.
435 * @param body data node for put to config DS
436 * @param uriInfo URI info
437 * @param ar {@link AsyncResponse} which needs to be completed
442 MediaTypes.APPLICATION_YANG_DATA_JSON,
443 MediaType.APPLICATION_JSON,
445 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
446 @Suspended final AsyncResponse ar) {
447 try (var jsonBody = new JsonChildBody(body)) {
448 completeDataPOST(server.dataPOST(jsonBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
453 * Create a data resource in target.
455 * @param identifier path to target
456 * @param body data node for put to config DS
457 * @param uriInfo URI info
458 * @param ar {@link AsyncResponse} which needs to be completed
461 @Path("/data/{identifier:.+}")
463 MediaTypes.APPLICATION_YANG_DATA_JSON,
464 MediaType.APPLICATION_JSON,
466 public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
467 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
468 completeDataPOST(server.dataPOST(identifier, new JsonDataPostBody(body), QueryParams.normalize(uriInfo)),
473 * Create a top-level data resource.
475 * @param body data node for put to config DS
476 * @param uriInfo URI info
477 * @param ar {@link AsyncResponse} which needs to be completed
482 MediaTypes.APPLICATION_YANG_DATA_XML,
483 MediaType.APPLICATION_XML,
486 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
487 try (var xmlBody = new XmlChildBody(body)) {
488 completeDataPOST(server.dataPOST(xmlBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
493 * Create a data resource in target.
495 * @param identifier path to target
496 * @param body data node for put to config DS
497 * @param uriInfo URI info
498 * @param ar {@link AsyncResponse} which needs to be completed
501 @Path("/data/{identifier:.+}")
503 MediaTypes.APPLICATION_YANG_DATA_XML,
504 MediaType.APPLICATION_XML,
507 public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
508 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
509 completeDataPOST(server.dataPOST(identifier, new XmlDataPostBody(body), QueryParams.normalize(uriInfo)),
513 private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future, final UriInfo uriInfo,
514 final AsyncResponse ar) {
515 future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
517 Response transform(final DataPostResult result) {
518 if (result instanceof CreateResourceResult createResource) {
519 final var builder = Response.created(uriInfo.getBaseUriBuilder()
521 .path(createResource.createdPath().toString())
523 fillConfigurationMetadata(builder, createResource);
524 return builder.build();
526 if (result instanceof InvokeResult invokeOperation) {
527 final var output = invokeOperation.output();
528 return output == null ? Response.noContent().build() : Response.ok().entity(output).build();
530 LOG.error("Unhandled result {}", result);
531 return Response.serverError().build();
537 * Replace the data store.
539 * @param uriInfo request URI information
540 * @param body data node for put to config DS
541 * @param ar {@link AsyncResponse} which needs to be completed
546 MediaTypes.APPLICATION_YANG_DATA_JSON,
547 MediaType.APPLICATION_JSON,
549 public void dataJsonPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
550 try (var jsonBody = new JsonResourceBody(body)) {
551 completeDataPUT(server.dataPUT(jsonBody, QueryParams.normalize(uriInfo)), ar);
556 * Create or replace the target data resource.
558 * @param identifier path to target
559 * @param uriInfo request URI information
560 * @param body data node for put to config DS
561 * @param ar {@link AsyncResponse} which needs to be completed
564 @Path("/data/{identifier:.+}")
566 MediaTypes.APPLICATION_YANG_DATA_JSON,
567 MediaType.APPLICATION_JSON,
569 public void dataJsonPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
570 final InputStream body, @Suspended final AsyncResponse ar) {
571 try (var jsonBody = new JsonResourceBody(body)) {
572 completeDataPUT(server.dataPUT(identifier, jsonBody, QueryParams.normalize(uriInfo)), ar);
577 * Replace the data store.
579 * @param uriInfo request URI information
580 * @param body data node for put to config DS
581 * @param ar {@link AsyncResponse} which needs to be completed
586 MediaTypes.APPLICATION_YANG_DATA_XML,
587 MediaType.APPLICATION_XML,
590 public void dataXmlPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
591 try (var xmlBody = new XmlResourceBody(body)) {
592 completeDataPUT(server.dataPUT(xmlBody, QueryParams.normalize(uriInfo)), ar);
597 * Create or replace the target data resource.
599 * @param identifier path to target
600 * @param uriInfo request URI information
601 * @param body data node for put to config DS
602 * @param ar {@link AsyncResponse} which needs to be completed
605 @Path("/data/{identifier:.+}")
607 MediaTypes.APPLICATION_YANG_DATA_XML,
608 MediaType.APPLICATION_XML,
611 public void dataXmlPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
612 final InputStream body, @Suspended final AsyncResponse ar) {
613 try (var xmlBody = new XmlResourceBody(body)) {
614 completeDataPUT(server.dataPUT(identifier, xmlBody, QueryParams.normalize(uriInfo)), ar);
618 private static void completeDataPUT(final RestconfFuture<DataPutResult> future, final AsyncResponse ar) {
619 future.addCallback(new JaxRsRestconfCallback<>(ar) {
621 Response transform(final DataPutResult result) {
622 // Note: no Location header, as it matches the request path
623 final var builder = result.created() ? Response.created(null) : Response.noContent();
624 fillConfigurationMetadata(builder, result);
625 return builder.build();
631 * List RPC and action operations.
633 * @param ar {@link AsyncResponse} which needs to be completed
638 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
639 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
641 public void operationsGET(@Suspended final AsyncResponse ar) {
642 completeOperationsGet(server.operationsGET(), ar);
646 * Retrieve list of operations and actions supported by the server or device.
648 * @param operation path parameter to identify device and/or operation
649 * @param ar {@link AsyncResponse} which needs to be completed
652 @Path("/operations/{operation:.+}")
654 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
655 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
657 public void operationsGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
658 completeOperationsGet(server.operationsGET(operation), ar);
661 private static void completeOperationsGet(final RestconfFuture<FormattableBody> future, final AsyncResponse ar) {
662 future.addCallback(new JaxRsRestconfCallback<FormattableBody>(ar) {
664 Response transform(final FormattableBody result) {
665 return Response.ok().entity(result).build();
671 * Invoke RPC operation.
673 * @param identifier module name and rpc identifier string for the desired operation
674 * @param body the body of the operation
675 * @param uriInfo URI info
676 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
679 // FIXME: identifier is just a *single* QName
680 @Path("/operations/{identifier:.+}")
682 MediaTypes.APPLICATION_YANG_DATA_XML,
683 MediaType.APPLICATION_XML,
687 MediaTypes.APPLICATION_YANG_DATA_JSON,
688 MediaTypes.APPLICATION_YANG_DATA_XML,
689 MediaType.APPLICATION_JSON,
690 MediaType.APPLICATION_XML,
693 public void operationsXmlPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
694 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
695 try (var xmlBody = new XmlOperationInputBody(body)) {
696 operationsPOST(identifier, uriInfo, ar, xmlBody);
701 * Invoke RPC operation.
703 * @param identifier module name and rpc identifier string for the desired operation
704 * @param body the body of the operation
705 * @param uriInfo URI info
706 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
709 // FIXME: identifier is just a *single* QName
710 @Path("/operations/{identifier:.+}")
712 MediaTypes.APPLICATION_YANG_DATA_JSON,
713 MediaType.APPLICATION_JSON,
716 MediaTypes.APPLICATION_YANG_DATA_JSON,
717 MediaTypes.APPLICATION_YANG_DATA_XML,
718 MediaType.APPLICATION_JSON,
719 MediaType.APPLICATION_XML,
722 public void operationsJsonPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
723 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
724 try (var jsonBody = new JsonOperationInputBody(body)) {
725 operationsPOST(identifier, uriInfo, ar, jsonBody);
729 private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
730 final OperationInputBody body) {
731 server.operationsPOST(uriInfo.getBaseUri(), identifier, QueryParams.normalize(uriInfo), body)
732 .addCallback(new JaxRsRestconfCallback<>(ar) {
734 Response transform(final InvokeResult result) {
735 final var body = result.output();
736 return body == null ? Response.noContent().build()
737 : Response.ok().entity(body).build();
743 * Get revision of IETF YANG Library module.
745 * @param ar {@link AsyncResponse} which needs to be completed
748 @Path("/yang-library-version")
750 MediaTypes.APPLICATION_YANG_DATA_JSON,
751 MediaTypes.APPLICATION_YANG_DATA_XML,
752 MediaType.APPLICATION_JSON,
753 MediaType.APPLICATION_XML,
756 public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
757 server.yangLibraryVersionGET().addCallback(new JaxRsRestconfCallback<NormalizedNodePayload>(ar) {
759 Response transform(final NormalizedNodePayload result) {
760 return Response.ok().entity(result).build();
765 // FIXME: References to these resources are generated by our yang-library implementation. That means:
766 // - We really need to formalize the parameter structure so we get some help from JAX-RS during matching
768 // - optional yang-ext:mount prefix(es)
769 // - mandatory module name
770 // - optional module revision
771 // - We really should use /yang-library-module/{name}(/{revision})?
772 // - We seem to be lacking explicit support for submodules in there -- and those locations should then point
773 // to /yang-library-submodule/{moduleName}(/{moduleRevision})?/{name}(/{revision})? so as to look the
774 // submodule up efficiently and allow for the weird case where there are two submodules with the same name
775 // (that is currently not supported by the parser, but it will be in the future)
776 // - It does not make sense to support yang-ext:mount, unless we also intercept mount points and rewrite
777 // yang-library locations. We most likely want to do that to ensure users are not tempted to connect to
781 * Get schema of specific module.
783 * @param fileName source file name
784 * @param revision source revision
785 * @param ar {@link AsyncResponse} which needs to be completed
788 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
789 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
790 public void modulesYangGET(@PathParam("fileName") final String fileName,
791 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
792 completeModulesGET(server.modulesYangGET(fileName, revision), ar);
796 * Get schema of specific module.
798 * @param mountPath mount point path
799 * @param fileName source file name
800 * @param revision source revision
801 * @param ar {@link AsyncResponse} which needs to be completed
804 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
805 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
806 public void modulesYangGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
807 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
808 @Suspended final AsyncResponse ar) {
809 completeModulesGET(server.modulesYangGET(mountPath, fileName, revision), ar);
813 * Get schema of specific module.
815 * @param fileName source file name
816 * @param revision source revision
817 * @param ar {@link AsyncResponse} which needs to be completed
820 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
821 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
822 public void modulesYinGET(@PathParam("fileName") final String fileName,
823 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
824 completeModulesGET(server.modulesYinGET(fileName, revision), ar);
828 * Get schema of specific module.
830 * @param mountPath mount point path
831 * @param fileName source file name
832 * @param revision source revision
833 * @param ar {@link AsyncResponse} which needs to be completed
836 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
837 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
838 public void modulesYinGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
839 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
840 @Suspended final AsyncResponse ar) {
841 completeModulesGET(server.modulesYinGET(mountPath, fileName, revision), ar);
844 private static void completeModulesGET(final RestconfFuture<ModulesGetResult> future, final AsyncResponse ar) {
845 future.addCallback(new JaxRsRestconfCallback<>(ar) {
847 Response transform(final ModulesGetResult result) {
850 reader = result.source().openStream();
851 } catch (IOException e) {
852 throw new RestconfDocumentedException("Cannot open source", e);
854 return Response.ok(reader).build();