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.api.query.PrettyPrintParam;
50 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
51 import org.opendaylight.restconf.common.errors.RestconfError;
52 import org.opendaylight.restconf.common.errors.RestconfFuture;
53 import org.opendaylight.restconf.nb.rfc8040.URLConstants;
54 import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
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 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;
133 * Delete the target data resource.
135 * @param identifier path to target
136 * @param ar {@link AsyncResponse} which needs to be completed
139 @Path("/data/{identifier:.+}")
140 @SuppressWarnings("checkstyle:abbreviationAsWordInName")
141 public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier,
142 @Suspended final AsyncResponse ar) {
143 server.dataDELETE(identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
145 Response transform(final Empty result) {
146 return Response.noContent().build();
152 * Get target data resource from data root.
154 * @param uriInfo URI info
155 * @param ar {@link AsyncResponse} which needs to be completed
160 MediaTypes.APPLICATION_YANG_DATA_JSON,
161 MediaTypes.APPLICATION_YANG_DATA_XML,
162 MediaType.APPLICATION_JSON,
163 MediaType.APPLICATION_XML,
166 public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
167 completeDataGET(server.dataGET(QueryParams.newDataGetParams(uriInfo)), ar);
171 * Get target data resource.
173 * @param identifier path to target
174 * @param uriInfo URI info
175 * @param ar {@link AsyncResponse} which needs to be completed
178 @Path("/data/{identifier:.+}")
180 MediaTypes.APPLICATION_YANG_DATA_JSON,
181 MediaTypes.APPLICATION_YANG_DATA_XML,
182 MediaType.APPLICATION_JSON,
183 MediaType.APPLICATION_XML,
186 public void dataGET(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
187 @Suspended final AsyncResponse ar) {
188 completeDataGET(server.dataGET(identifier, QueryParams.newDataGetParams(uriInfo)), ar);
191 private static void completeDataGET(final RestconfFuture<DataGetResult> future, final AsyncResponse ar) {
192 future.addCallback(new JaxRsRestconfCallback<>(ar) {
194 Response transform(final DataGetResult result) {
195 final var builder = Response.ok().entity(result.payload()).cacheControl(NO_CACHE);
196 fillConfigurationMetadata(builder, result);
197 return builder.build();
202 private static void fillConfigurationMetadata(final ResponseBuilder builder, final ConfigurationMetadata metadata) {
203 final var etag = metadata.entityTag();
205 builder.tag(new EntityTag(etag.value(), etag.weak()));
207 final var lastModified = metadata.lastModified();
208 if (lastModified != null) {
209 builder.lastModified(Date.from(lastModified));
214 * Partially modify the target data store, as defined in
215 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
217 * @param body data node for put to config DS
218 * @param ar {@link AsyncResponse} which needs to be completed
223 MediaTypes.APPLICATION_YANG_DATA_XML,
224 MediaType.APPLICATION_XML,
227 public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
228 try (var xmlBody = new XmlResourceBody(body)) {
229 completeDataPATCH(server.dataPATCH(xmlBody), ar);
234 * Partially modify the target data resource, as defined in
235 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
237 * @param identifier path to target
238 * @param body data node for put to config DS
239 * @param ar {@link AsyncResponse} which needs to be completed
242 @Path("/data/{identifier:.+}")
244 MediaTypes.APPLICATION_YANG_DATA_XML,
245 MediaType.APPLICATION_XML,
248 public void dataXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
249 @Suspended final AsyncResponse ar) {
250 try (var xmlBody = new XmlResourceBody(body)) {
251 completeDataPATCH(server.dataPATCH(identifier, xmlBody), ar);
256 * Partially modify the target data store, as defined in
257 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
259 * @param body data node for put to config DS
260 * @param ar {@link AsyncResponse} which needs to be completed
265 MediaTypes.APPLICATION_YANG_DATA_JSON,
266 MediaType.APPLICATION_JSON,
268 public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
269 try (var jsonBody = new JsonResourceBody(body)) {
270 completeDataPATCH(server.dataPATCH(jsonBody), ar);
275 * Partially modify the target data resource, as defined in
276 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
278 * @param identifier path to target
279 * @param body data node for put to config DS
280 * @param ar {@link AsyncResponse} which needs to be completed
283 @Path("/data/{identifier:.+}")
285 MediaTypes.APPLICATION_YANG_DATA_JSON,
286 MediaType.APPLICATION_JSON,
288 public void dataJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
289 @Suspended final AsyncResponse ar) {
290 try (var jsonBody = new JsonResourceBody(body)) {
291 completeDataPATCH(server.dataPATCH(identifier, jsonBody), ar);
295 private static void completeDataPATCH(final RestconfFuture<DataPatchResult> future, final AsyncResponse ar) {
296 future.addCallback(new JaxRsRestconfCallback<>(ar) {
298 Response transform(final DataPatchResult result) {
299 final var builder = Response.ok();
300 fillConfigurationMetadata(builder, result);
301 return builder.build();
307 * Ordered list of edits that are applied to the datastore by the server, as defined in
308 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
310 * @param body YANG Patch body
311 * @param uriInfo URI info
312 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
316 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
318 MediaTypes.APPLICATION_YANG_DATA_JSON,
319 MediaTypes.APPLICATION_YANG_DATA_XML
321 public void dataYangJsonPATCH(final InputStream body, @Context final UriInfo uriInfo,
322 @Suspended final AsyncResponse ar) {
323 try (var jsonBody = new JsonPatchBody(body)) {
324 completeDataYangPATCH(server.dataPATCH(QueryParams.normalize(uriInfo), jsonBody), ar);
329 * Ordered list of edits that are applied to the target datastore by the server, as defined in
330 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
332 * @param identifier path to target
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}
338 @Path("/data/{identifier:.+}")
339 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
341 MediaTypes.APPLICATION_YANG_DATA_JSON,
342 MediaTypes.APPLICATION_YANG_DATA_XML
344 public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
345 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
346 try (var jsonBody = new JsonPatchBody(body)) {
347 completeDataYangPATCH(server.dataPATCH(identifier, QueryParams.normalize(uriInfo), jsonBody), ar);
352 * Ordered list of edits that are applied to the 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 body YANG Patch body
356 * @param uriInfo URI info
357 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
361 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
363 MediaTypes.APPLICATION_YANG_DATA_JSON,
364 MediaTypes.APPLICATION_YANG_DATA_XML
366 public void dataYangXmlPATCH(final InputStream body, @Context final UriInfo uriInfo,
367 @Suspended final AsyncResponse ar) {
368 try (var xmlBody = new XmlPatchBody(body)) {
369 completeDataYangPATCH(server.dataPATCH(QueryParams.normalize(uriInfo), xmlBody), ar);
374 * Ordered list of edits that are applied to the target datastore by the server, as defined in
375 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
377 * @param identifier path to target
378 * @param uriInfo URI info
379 * @param body YANG Patch body
380 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
383 @Path("/data/{identifier:.+}")
384 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
386 MediaTypes.APPLICATION_YANG_DATA_JSON,
387 MediaTypes.APPLICATION_YANG_DATA_XML
389 public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
390 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
391 try (var xmlBody = new XmlPatchBody(body)) {
392 completeDataYangPATCH(server.dataPATCH(identifier, QueryParams.normalize(uriInfo), xmlBody), ar);
396 private static void completeDataYangPATCH(final RestconfFuture<DataYangPatchResult> future,
397 final AsyncResponse ar) {
398 future.addCallback(new JaxRsRestconfCallback<>(ar) {
400 Response transform(final DataYangPatchResult result) {
401 final var status = result.status();
402 final var builder = Response.status(statusOf(status))
403 .entity(new YangPatchStatusBody(() -> PrettyPrintParam.FALSE, status));
404 fillConfigurationMetadata(builder, result);
405 return builder.build();
408 private static Status statusOf(final PatchStatusContext result) {
412 final var globalErrors = result.globalErrors();
413 if (globalErrors != null && !globalErrors.isEmpty()) {
414 return statusOfFirst(globalErrors);
416 for (var edit : result.editCollection()) {
418 final var editErrors = edit.getEditErrors();
419 if (editErrors != null && !editErrors.isEmpty()) {
420 return statusOfFirst(editErrors);
424 return Status.INTERNAL_SERVER_ERROR;
427 private static Status statusOfFirst(final List<RestconfError> error) {
428 return ErrorTags.statusOf(error.get(0).getErrorTag());
434 * Create a top-level data resource.
436 * @param body data node for put to config DS
437 * @param uriInfo URI info
438 * @param ar {@link AsyncResponse} which needs to be completed
443 MediaTypes.APPLICATION_YANG_DATA_JSON,
444 MediaType.APPLICATION_JSON,
446 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
447 @Suspended final AsyncResponse ar) {
448 try (var jsonBody = new JsonChildBody(body)) {
449 completeDataPOST(server.dataPOST(jsonBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
454 * Create a data resource in target.
456 * @param identifier path to target
457 * @param body data node for put to config DS
458 * @param uriInfo URI info
459 * @param ar {@link AsyncResponse} which needs to be completed
462 @Path("/data/{identifier:.+}")
464 MediaTypes.APPLICATION_YANG_DATA_JSON,
465 MediaType.APPLICATION_JSON,
467 public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
468 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
469 completeDataPOST(server.dataPOST(identifier, new JsonDataPostBody(body), QueryParams.normalize(uriInfo)),
474 * Create a top-level data resource.
476 * @param body data node for put to config DS
477 * @param uriInfo URI info
478 * @param ar {@link AsyncResponse} which needs to be completed
483 MediaTypes.APPLICATION_YANG_DATA_XML,
484 MediaType.APPLICATION_XML,
487 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
488 try (var xmlBody = new XmlChildBody(body)) {
489 completeDataPOST(server.dataPOST(xmlBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
494 * Create a data resource in target.
496 * @param identifier path to target
497 * @param body data node for put to config DS
498 * @param uriInfo URI info
499 * @param ar {@link AsyncResponse} which needs to be completed
502 @Path("/data/{identifier:.+}")
504 MediaTypes.APPLICATION_YANG_DATA_XML,
505 MediaType.APPLICATION_XML,
508 public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
509 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
510 completeDataPOST(server.dataPOST(identifier, new XmlDataPostBody(body), QueryParams.normalize(uriInfo)),
514 private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future, final UriInfo uriInfo,
515 final AsyncResponse ar) {
516 future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
518 Response transform(final DataPostResult result) {
519 if (result instanceof CreateResourceResult createResource) {
520 final var builder = Response.created(uriInfo.getBaseUriBuilder()
522 .path(createResource.createdPath().toString())
524 fillConfigurationMetadata(builder, createResource);
525 return builder.build();
527 if (result instanceof InvokeResult invokeOperation) {
528 final var output = invokeOperation.output();
529 return output == null ? Response.noContent().build() : Response.ok().entity(output).build();
531 LOG.error("Unhandled result {}", result);
532 return Response.serverError().build();
538 * Replace the data store.
540 * @param uriInfo request URI information
541 * @param body data node for put to config DS
542 * @param ar {@link AsyncResponse} which needs to be completed
547 MediaTypes.APPLICATION_YANG_DATA_JSON,
548 MediaType.APPLICATION_JSON,
550 public void dataJsonPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
551 try (var jsonBody = new JsonResourceBody(body)) {
552 completeDataPUT(server.dataPUT(jsonBody, QueryParams.normalize(uriInfo)), ar);
557 * Create or replace the target data resource.
559 * @param identifier path to target
560 * @param uriInfo request URI information
561 * @param body data node for put to config DS
562 * @param ar {@link AsyncResponse} which needs to be completed
565 @Path("/data/{identifier:.+}")
567 MediaTypes.APPLICATION_YANG_DATA_JSON,
568 MediaType.APPLICATION_JSON,
570 public void dataJsonPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
571 final InputStream body, @Suspended final AsyncResponse ar) {
572 try (var jsonBody = new JsonResourceBody(body)) {
573 completeDataPUT(server.dataPUT(identifier, jsonBody, QueryParams.normalize(uriInfo)), ar);
578 * Replace the data store.
580 * @param uriInfo request URI information
581 * @param body data node for put to config DS
582 * @param ar {@link AsyncResponse} which needs to be completed
587 MediaTypes.APPLICATION_YANG_DATA_XML,
588 MediaType.APPLICATION_XML,
591 public void dataXmlPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
592 try (var xmlBody = new XmlResourceBody(body)) {
593 completeDataPUT(server.dataPUT(xmlBody, QueryParams.normalize(uriInfo)), ar);
598 * Create or replace the target data resource.
600 * @param identifier path to target
601 * @param uriInfo request URI information
602 * @param body data node for put to config DS
603 * @param ar {@link AsyncResponse} which needs to be completed
606 @Path("/data/{identifier:.+}")
608 MediaTypes.APPLICATION_YANG_DATA_XML,
609 MediaType.APPLICATION_XML,
612 public void dataXmlPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
613 final InputStream body, @Suspended final AsyncResponse ar) {
614 try (var xmlBody = new XmlResourceBody(body)) {
615 completeDataPUT(server.dataPUT(identifier, xmlBody, QueryParams.normalize(uriInfo)), ar);
619 private static void completeDataPUT(final RestconfFuture<DataPutResult> future, final AsyncResponse ar) {
620 future.addCallback(new JaxRsRestconfCallback<>(ar) {
622 Response transform(final DataPutResult result) {
623 // Note: no Location header, as it matches the request path
624 final var builder = result.created() ? Response.created(null) : Response.noContent();
625 fillConfigurationMetadata(builder, result);
626 return builder.build();
632 * List RPC and action operations.
634 * @param ar {@link AsyncResponse} which needs to be completed
639 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
640 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
642 public void operationsGET(@Suspended final AsyncResponse ar) {
643 completeOperationsGet(server.operationsGET(), ar);
647 * Retrieve list of operations and actions supported by the server or device.
649 * @param operation path parameter to identify device and/or operation
650 * @param ar {@link AsyncResponse} which needs to be completed
653 @Path("/operations/{operation:.+}")
655 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
656 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
658 public void operationsGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
659 completeOperationsGet(server.operationsGET(operation), ar);
662 private static void completeOperationsGet(final RestconfFuture<FormattableBody> future, final AsyncResponse ar) {
663 future.addCallback(new JaxRsRestconfCallback<FormattableBody>(ar) {
665 Response transform(final FormattableBody result) {
666 return Response.ok().entity(result).build();
672 * Invoke RPC operation.
674 * @param identifier module name and rpc identifier string for the desired operation
675 * @param body the body of the operation
676 * @param uriInfo URI info
677 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
680 // FIXME: identifier is just a *single* QName
681 @Path("/operations/{identifier:.+}")
683 MediaTypes.APPLICATION_YANG_DATA_XML,
684 MediaType.APPLICATION_XML,
688 MediaTypes.APPLICATION_YANG_DATA_JSON,
689 MediaTypes.APPLICATION_YANG_DATA_XML,
690 MediaType.APPLICATION_JSON,
691 MediaType.APPLICATION_XML,
694 public void operationsXmlPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
695 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
696 try (var xmlBody = new XmlOperationInputBody(body)) {
697 operationsPOST(identifier, uriInfo, ar, xmlBody);
702 * Invoke RPC operation.
704 * @param identifier module name and rpc identifier string for the desired operation
705 * @param body the body of the operation
706 * @param uriInfo URI info
707 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
710 // FIXME: identifier is just a *single* QName
711 @Path("/operations/{identifier:.+}")
713 MediaTypes.APPLICATION_YANG_DATA_JSON,
714 MediaType.APPLICATION_JSON,
717 MediaTypes.APPLICATION_YANG_DATA_JSON,
718 MediaTypes.APPLICATION_YANG_DATA_XML,
719 MediaType.APPLICATION_JSON,
720 MediaType.APPLICATION_XML,
723 public void operationsJsonPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
724 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
725 try (var jsonBody = new JsonOperationInputBody(body)) {
726 operationsPOST(identifier, uriInfo, ar, jsonBody);
730 private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
731 final OperationInputBody body) {
732 server.operationsPOST(uriInfo.getBaseUri(), identifier, QueryParams.normalize(uriInfo), body)
733 .addCallback(new JaxRsRestconfCallback<>(ar) {
735 Response transform(final InvokeResult result) {
736 final var body = result.output();
737 return body == null ? Response.noContent().build()
738 : Response.ok().entity(body).build();
744 * Get revision of IETF YANG Library module.
746 * @param ar {@link AsyncResponse} which needs to be completed
749 @Path("/yang-library-version")
751 MediaTypes.APPLICATION_YANG_DATA_JSON,
752 MediaTypes.APPLICATION_YANG_DATA_XML,
753 MediaType.APPLICATION_JSON,
754 MediaType.APPLICATION_XML,
757 public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
758 server.yangLibraryVersionGET().addCallback(new JaxRsRestconfCallback<NormalizedNodePayload>(ar) {
760 Response transform(final NormalizedNodePayload result) {
761 return Response.ok().entity(result).build();
766 // FIXME: References to these resources are generated by our yang-library implementation. That means:
767 // - We really need to formalize the parameter structure so we get some help from JAX-RS during matching
769 // - optional yang-ext:mount prefix(es)
770 // - mandatory module name
771 // - optional module revision
772 // - We really should use /yang-library-module/{name}(/{revision})?
773 // - We seem to be lacking explicit support for submodules in there -- and those locations should then point
774 // to /yang-library-submodule/{moduleName}(/{moduleRevision})?/{name}(/{revision})? so as to look the
775 // submodule up efficiently and allow for the weird case where there are two submodules with the same name
776 // (that is currently not supported by the parser, but it will be in the future)
777 // - It does not make sense to support yang-ext:mount, unless we also intercept mount points and rewrite
778 // yang-library locations. We most likely want to do that to ensure users are not tempted to connect to
782 * Get schema of specific module.
784 * @param fileName source file name
785 * @param revision source revision
786 * @param ar {@link AsyncResponse} which needs to be completed
789 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
790 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
791 public void modulesYangGET(@PathParam("fileName") final String fileName,
792 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
793 completeModulesGET(server.modulesYangGET(fileName, revision), ar);
797 * Get schema of specific module.
799 * @param mountPath mount point path
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 + "/{mountPath:.+}/{fileName : [^/]+}")
807 public void modulesYangGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
808 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
809 @Suspended final AsyncResponse ar) {
810 completeModulesGET(server.modulesYangGET(mountPath, fileName, revision), ar);
814 * Get schema of specific module.
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_YIN_MEDIA_TYPE)
822 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
823 public void modulesYinGET(@PathParam("fileName") final String fileName,
824 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
825 completeModulesGET(server.modulesYinGET(fileName, revision), ar);
829 * Get schema of specific module.
831 * @param mountPath mount point path
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 + "/{mountPath:.+}/{fileName : [^/]+}")
839 public void modulesYinGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
840 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
841 @Suspended final AsyncResponse ar) {
842 completeModulesGET(server.modulesYinGET(mountPath, fileName, revision), ar);
845 private static void completeModulesGET(final RestconfFuture<ModulesGetResult> future, final AsyncResponse ar) {
846 future.addCallback(new JaxRsRestconfCallback<>(ar) {
848 Response transform(final ModulesGetResult result) {
851 reader = result.source().openStream();
852 } catch (IOException e) {
853 throw new RestconfDocumentedException("Cannot open source", e);
855 return Response.ok(reader).build();