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.FormatParameters;
49 import org.opendaylight.restconf.api.MediaTypes;
50 import org.opendaylight.restconf.api.QueryParameters;
51 import org.opendaylight.restconf.api.query.PrettyPrintParam;
52 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
53 import org.opendaylight.restconf.common.errors.RestconfError;
54 import org.opendaylight.restconf.common.errors.RestconfFuture;
55 import org.opendaylight.restconf.nb.rfc8040.URLConstants;
56 import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
57 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
58 import org.opendaylight.restconf.server.api.ConfigurationMetadata;
59 import org.opendaylight.restconf.server.api.CreateResourceResult;
60 import org.opendaylight.restconf.server.api.DataGetResult;
61 import org.opendaylight.restconf.server.api.DataPatchResult;
62 import org.opendaylight.restconf.server.api.DataPostResult;
63 import org.opendaylight.restconf.server.api.DataPutResult;
64 import org.opendaylight.restconf.server.api.DataYangPatchResult;
65 import org.opendaylight.restconf.server.api.InvokeResult;
66 import org.opendaylight.restconf.server.api.JsonChildBody;
67 import org.opendaylight.restconf.server.api.JsonDataPostBody;
68 import org.opendaylight.restconf.server.api.JsonOperationInputBody;
69 import org.opendaylight.restconf.server.api.JsonPatchBody;
70 import org.opendaylight.restconf.server.api.JsonResourceBody;
71 import org.opendaylight.restconf.server.api.ModulesGetResult;
72 import org.opendaylight.restconf.server.api.OperationInputBody;
73 import org.opendaylight.restconf.server.api.PatchStatusContext;
74 import org.opendaylight.restconf.server.api.RestconfServer;
75 import org.opendaylight.restconf.server.api.ServerRequest;
76 import org.opendaylight.restconf.server.api.XmlChildBody;
77 import org.opendaylight.restconf.server.api.XmlDataPostBody;
78 import org.opendaylight.restconf.server.api.XmlOperationInputBody;
79 import org.opendaylight.restconf.server.api.XmlPatchBody;
80 import org.opendaylight.restconf.server.api.XmlResourceBody;
81 import org.opendaylight.restconf.server.spi.YangPatchStatusBody;
82 import org.opendaylight.yangtools.yang.common.Empty;
83 import org.opendaylight.yangtools.yang.common.YangConstants;
84 import org.slf4j.Logger;
85 import org.slf4j.LoggerFactory;
88 * Baseline RESTCONF implementation with JAX-RS. Interfaces to a {@link RestconfServer}. Since we need {@link ApiPath}
89 * arguments, we also implement {@link ParamConverterProvider} and provide the appropriate converter. This has the nice
90 * side-effect of suppressing <a href="https://github.com/eclipse-ee4j/jersey/issues/3700">Jersey warnings</a>.
94 public final class JaxRsRestconf implements ParamConverterProvider {
95 private static final Logger LOG = LoggerFactory.getLogger(JaxRsRestconf.class);
96 private static final CacheControl NO_CACHE = CacheControl.valueOf("no-cache");
97 private static final ParamConverter<ApiPath> API_PATH_CONVERTER = new ParamConverter<>() {
99 public ApiPath fromString(final String value) {
100 final var str = nonnull(value);
102 return ApiPath.parseUrl(str);
103 } catch (ParseException e) {
104 throw new IllegalArgumentException(e.getMessage(), e);
109 public String toString(final ApiPath value) {
110 return nonnull(value).toString();
113 private static <T> @NonNull T nonnull(final @Nullable T value) {
115 throw new IllegalArgumentException("value must not be null");
121 private final @NonNull RestconfServer server;
122 private final @NonNull ServerRequest emptyRequest;
123 private final @NonNull PrettyPrintParam prettyPrint;
125 public JaxRsRestconf(final RestconfServer server, final PrettyPrintParam prettyPrint) {
126 this.server = requireNonNull(server);
127 this.prettyPrint = requireNonNull(prettyPrint);
128 emptyRequest = ServerRequest.of(QueryParameters.of(), prettyPrint);
131 private @NonNull ServerRequest requestOf(final UriInfo uriInfo) {
132 final QueryParameters params;
134 params = QueryParameters.ofMultiValue(uriInfo.getQueryParameters());
135 } catch (IllegalArgumentException e) {
136 throw new BadRequestException(e.getMessage(), e);
138 return params.isEmpty() ? emptyRequest : ServerRequest.of(params, prettyPrint);
142 @SuppressWarnings("unchecked")
143 public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType,
144 final Annotation[] annotations) {
145 return ApiPath.class.equals(rawType) ? (ParamConverter<T>) API_PATH_CONVERTER : null;
149 * Delete the target data resource.
151 * @param identifier path to target
152 * @param ar {@link AsyncResponse} which needs to be completed
155 @Path("/data/{identifier:.+}")
156 @SuppressWarnings("checkstyle:abbreviationAsWordInName")
157 public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier,
158 @Suspended final AsyncResponse ar) {
159 server.dataDELETE(emptyRequest, identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
161 Response transform(final Empty result) {
162 return Response.noContent().build();
168 * Get target data resource from data root.
170 * @param uriInfo URI info
171 * @param ar {@link AsyncResponse} which needs to be completed
176 MediaTypes.APPLICATION_YANG_DATA_JSON,
177 MediaTypes.APPLICATION_YANG_DATA_XML,
178 MediaType.APPLICATION_JSON,
179 MediaType.APPLICATION_XML,
182 public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
183 completeDataGET(server.dataGET(requestOf(uriInfo)), ar);
187 * Get target data resource.
189 * @param identifier path to target
190 * @param uriInfo URI info
191 * @param ar {@link AsyncResponse} which needs to be completed
194 @Path("/data/{identifier:.+}")
196 MediaTypes.APPLICATION_YANG_DATA_JSON,
197 MediaTypes.APPLICATION_YANG_DATA_XML,
198 MediaType.APPLICATION_JSON,
199 MediaType.APPLICATION_XML,
202 public void dataGET(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
203 @Suspended final AsyncResponse ar) {
204 completeDataGET(server.dataGET(requestOf(uriInfo), identifier), ar);
207 private static void completeDataGET(final RestconfFuture<DataGetResult> future, final AsyncResponse ar) {
208 future.addCallback(new JaxRsRestconfCallback<>(ar) {
210 Response transform(final DataGetResult result) {
211 final var builder = Response.ok().entity(result.payload()).cacheControl(NO_CACHE);
212 fillConfigurationMetadata(builder, result);
213 return builder.build();
218 private static void fillConfigurationMetadata(final ResponseBuilder builder, final ConfigurationMetadata metadata) {
219 final var etag = metadata.entityTag();
221 builder.tag(new EntityTag(etag.value(), etag.weak()));
223 final var lastModified = metadata.lastModified();
224 if (lastModified != null) {
225 builder.lastModified(Date.from(lastModified));
230 * Partially modify the target data store, as defined in
231 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
233 * @param body data node for put to config DS
234 * @param ar {@link AsyncResponse} which needs to be completed
239 MediaTypes.APPLICATION_YANG_DATA_XML,
240 MediaType.APPLICATION_XML,
243 public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
244 try (var xmlBody = new XmlResourceBody(body)) {
245 completeDataPATCH(server.dataPATCH(emptyRequest, xmlBody), ar);
250 * Partially modify the target data resource, as defined in
251 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
253 * @param identifier path to target
254 * @param body data node for put to config DS
255 * @param ar {@link AsyncResponse} which needs to be completed
258 @Path("/data/{identifier:.+}")
260 MediaTypes.APPLICATION_YANG_DATA_XML,
261 MediaType.APPLICATION_XML,
264 public void dataXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
265 @Suspended final AsyncResponse ar) {
266 try (var xmlBody = new XmlResourceBody(body)) {
267 completeDataPATCH(server.dataPATCH(emptyRequest, identifier, xmlBody), ar);
272 * Partially modify the target data store, as defined in
273 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
275 * @param body data node for put to config DS
276 * @param ar {@link AsyncResponse} which needs to be completed
281 MediaTypes.APPLICATION_YANG_DATA_JSON,
282 MediaType.APPLICATION_JSON,
284 public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
285 try (var jsonBody = new JsonResourceBody(body)) {
286 completeDataPATCH(server.dataPATCH(emptyRequest, jsonBody), ar);
291 * Partially modify the target data resource, as defined in
292 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
294 * @param identifier path to target
295 * @param body data node for put to config DS
296 * @param ar {@link AsyncResponse} which needs to be completed
299 @Path("/data/{identifier:.+}")
301 MediaTypes.APPLICATION_YANG_DATA_JSON,
302 MediaType.APPLICATION_JSON,
304 public void dataJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
305 @Suspended final AsyncResponse ar) {
306 try (var jsonBody = new JsonResourceBody(body)) {
307 completeDataPATCH(server.dataPATCH(emptyRequest, identifier, jsonBody), ar);
311 private static void completeDataPATCH(final RestconfFuture<DataPatchResult> future, final AsyncResponse ar) {
312 future.addCallback(new JaxRsRestconfCallback<>(ar) {
314 Response transform(final DataPatchResult result) {
315 final var builder = Response.ok();
316 fillConfigurationMetadata(builder, result);
317 return builder.build();
323 * Ordered list of edits that are applied to the datastore by the server, as defined in
324 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
326 * @param body YANG Patch body
327 * @param uriInfo URI info
328 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
332 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
334 MediaTypes.APPLICATION_YANG_DATA_JSON,
335 MediaTypes.APPLICATION_YANG_DATA_XML
337 public void dataYangJsonPATCH(final InputStream body, @Context final UriInfo uriInfo,
338 @Suspended final AsyncResponse ar) {
339 try (var jsonBody = new JsonPatchBody(body)) {
340 completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), jsonBody), ar);
345 * Ordered list of edits that are applied to the target datastore by the server, as defined in
346 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
348 * @param identifier path to target
349 * @param body YANG Patch body
350 * @param uriInfo URI info
351 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
354 @Path("/data/{identifier:.+}")
355 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
357 MediaTypes.APPLICATION_YANG_DATA_JSON,
358 MediaTypes.APPLICATION_YANG_DATA_XML
360 public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
361 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
362 try (var jsonBody = new JsonPatchBody(body)) {
363 completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), identifier, jsonBody), ar);
368 * Ordered list of edits that are applied to the datastore by the server, as defined in
369 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
371 * @param body YANG Patch body
372 * @param uriInfo URI info
373 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
377 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
379 MediaTypes.APPLICATION_YANG_DATA_JSON,
380 MediaTypes.APPLICATION_YANG_DATA_XML
382 public void dataYangXmlPATCH(final InputStream body, @Context final UriInfo uriInfo,
383 @Suspended final AsyncResponse ar) {
384 try (var xmlBody = new XmlPatchBody(body)) {
385 completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), xmlBody), ar);
390 * Ordered list of edits that are applied to the target datastore by the server, as defined in
391 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
393 * @param identifier path to target
394 * @param uriInfo URI info
395 * @param body YANG Patch body
396 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
399 @Path("/data/{identifier:.+}")
400 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
402 MediaTypes.APPLICATION_YANG_DATA_JSON,
403 MediaTypes.APPLICATION_YANG_DATA_XML
405 public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
406 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
407 try (var xmlBody = new XmlPatchBody(body)) {
408 completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), identifier, xmlBody), ar);
412 private static void completeDataYangPATCH(final RestconfFuture<DataYangPatchResult> future,
413 final AsyncResponse ar) {
414 future.addCallback(new JaxRsRestconfCallback<>(ar) {
416 Response transform(final DataYangPatchResult result) {
417 final var status = result.status();
418 final var builder = Response.status(statusOf(status))
419 .entity(new YangPatchStatusBody(status));
420 fillConfigurationMetadata(builder, result);
421 return builder.build();
424 private static Status statusOf(final PatchStatusContext result) {
428 final var globalErrors = result.globalErrors();
429 if (globalErrors != null && !globalErrors.isEmpty()) {
430 return statusOfFirst(globalErrors);
432 for (var edit : result.editCollection()) {
434 final var editErrors = edit.getEditErrors();
435 if (editErrors != null && !editErrors.isEmpty()) {
436 return statusOfFirst(editErrors);
440 return Status.INTERNAL_SERVER_ERROR;
443 private static Status statusOfFirst(final List<RestconfError> error) {
444 return ErrorTags.statusOf(error.get(0).getErrorTag());
450 * Create a top-level data resource.
452 * @param body data node for put to config DS
453 * @param uriInfo URI info
454 * @param ar {@link AsyncResponse} which needs to be completed
459 MediaTypes.APPLICATION_YANG_DATA_JSON,
460 MediaType.APPLICATION_JSON,
462 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
463 @Suspended final AsyncResponse ar) {
464 try (var jsonBody = new JsonChildBody(body)) {
465 final var request = requestOf(uriInfo);
466 completeDataPOST(server.dataPOST(request, jsonBody), request.format(), uriInfo, ar);
471 * Create a data resource in target.
473 * @param identifier path to target
474 * @param body data node for put to config DS
475 * @param uriInfo URI info
476 * @param ar {@link AsyncResponse} which needs to be completed
479 @Path("/data/{identifier:.+}")
481 MediaTypes.APPLICATION_YANG_DATA_JSON,
482 MediaType.APPLICATION_JSON,
484 public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
485 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
486 final var request = requestOf(uriInfo);
487 completeDataPOST(server.dataPOST(request, identifier, new JsonDataPostBody(body)), request.format(), uriInfo,
492 * Create a top-level data resource.
494 * @param body data node for put to config DS
495 * @param uriInfo URI info
496 * @param ar {@link AsyncResponse} which needs to be completed
501 MediaTypes.APPLICATION_YANG_DATA_XML,
502 MediaType.APPLICATION_XML,
505 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
506 try (var xmlBody = new XmlChildBody(body)) {
507 final var request = requestOf(uriInfo);
508 completeDataPOST(server.dataPOST(request, xmlBody), request.format(), uriInfo, ar);
513 * Create a data resource in target.
515 * @param identifier path to target
516 * @param body data node for put to config DS
517 * @param uriInfo URI info
518 * @param ar {@link AsyncResponse} which needs to be completed
521 @Path("/data/{identifier:.+}")
523 MediaTypes.APPLICATION_YANG_DATA_XML,
524 MediaType.APPLICATION_XML,
527 public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
528 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
529 final var request = requestOf(uriInfo);
530 completeDataPOST(server.dataPOST(request, identifier, new XmlDataPostBody(body)), request.format(), uriInfo,
534 private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future,
535 final FormatParameters format, final UriInfo uriInfo, final AsyncResponse ar) {
536 future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
538 Response transform(final DataPostResult result) {
539 if (result instanceof CreateResourceResult createResource) {
540 final var builder = Response.created(uriInfo.getBaseUriBuilder()
542 .path(createResource.createdPath().toString())
544 fillConfigurationMetadata(builder, createResource);
545 return builder.build();
547 if (result instanceof InvokeResult invokeOperation) {
548 final var output = invokeOperation.output();
549 return output == null ? Response.noContent().build()
550 : Response.ok().entity(new JaxRsFormattableBody(output, format)).build();
552 LOG.error("Unhandled result {}", result);
553 return Response.serverError().build();
559 * Replace the data store.
561 * @param uriInfo request URI information
562 * @param body data node for put to config DS
563 * @param ar {@link AsyncResponse} which needs to be completed
568 MediaTypes.APPLICATION_YANG_DATA_JSON,
569 MediaType.APPLICATION_JSON,
571 public void dataJsonPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
572 try (var jsonBody = new JsonResourceBody(body)) {
573 completeDataPUT(server.dataPUT(requestOf(uriInfo), jsonBody), ar);
578 * Create or replace the target data resource.
580 * @param identifier path to target
581 * @param uriInfo request URI information
582 * @param body data node for put to config DS
583 * @param ar {@link AsyncResponse} which needs to be completed
586 @Path("/data/{identifier:.+}")
588 MediaTypes.APPLICATION_YANG_DATA_JSON,
589 MediaType.APPLICATION_JSON,
591 public void dataJsonPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
592 final InputStream body, @Suspended final AsyncResponse ar) {
593 try (var jsonBody = new JsonResourceBody(body)) {
594 completeDataPUT(server.dataPUT(requestOf(uriInfo), identifier, jsonBody), ar);
599 * Replace the data store.
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
608 MediaTypes.APPLICATION_YANG_DATA_XML,
609 MediaType.APPLICATION_XML,
612 public void dataXmlPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
613 try (var xmlBody = new XmlResourceBody(body)) {
614 completeDataPUT(server.dataPUT(requestOf(uriInfo), xmlBody), ar);
619 * Create or replace the target data resource.
621 * @param identifier path to target
622 * @param uriInfo request URI information
623 * @param body data node for put to config DS
624 * @param ar {@link AsyncResponse} which needs to be completed
627 @Path("/data/{identifier:.+}")
629 MediaTypes.APPLICATION_YANG_DATA_XML,
630 MediaType.APPLICATION_XML,
633 public void dataXmlPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
634 final InputStream body, @Suspended final AsyncResponse ar) {
635 try (var xmlBody = new XmlResourceBody(body)) {
636 completeDataPUT(server.dataPUT(requestOf(uriInfo), identifier, xmlBody), ar);
640 private static void completeDataPUT(final RestconfFuture<DataPutResult> future, final AsyncResponse ar) {
641 future.addCallback(new JaxRsRestconfCallback<>(ar) {
643 Response transform(final DataPutResult result) {
644 // Note: no Location header, as it matches the request path
645 final var builder = result.created() ? Response.created(null) : Response.noContent();
646 fillConfigurationMetadata(builder, result);
647 return builder.build();
653 * List RPC and action operations.
655 * @param ar {@link AsyncResponse} which needs to be completed
660 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
661 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
663 public void operationsGET(@Suspended final AsyncResponse ar) {
664 server.operationsGET(emptyRequest).addCallback(new FormattableBodyCallback(ar, emptyRequest.format()));
668 * Retrieve list of operations and actions supported by the server or device.
670 * @param operation path parameter to identify device and/or operation
671 * @param ar {@link AsyncResponse} which needs to be completed
674 @Path("/operations/{operation:.+}")
676 MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML,
677 MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
679 public void operationsGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
680 server.operationsGET(emptyRequest, operation)
681 .addCallback(new FormattableBodyCallback(ar, emptyRequest.format()));
685 * Invoke RPC operation.
687 * @param identifier module name and rpc identifier string for the desired operation
688 * @param body the body of the operation
689 * @param uriInfo URI info
690 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
693 // FIXME: identifier is just a *single* QName
694 @Path("/operations/{identifier:.+}")
696 MediaTypes.APPLICATION_YANG_DATA_XML,
697 MediaType.APPLICATION_XML,
701 MediaTypes.APPLICATION_YANG_DATA_JSON,
702 MediaTypes.APPLICATION_YANG_DATA_XML,
703 MediaType.APPLICATION_JSON,
704 MediaType.APPLICATION_XML,
707 public void operationsXmlPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
708 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
709 try (var xmlBody = new XmlOperationInputBody(body)) {
710 operationsPOST(identifier, uriInfo, ar, xmlBody);
715 * Invoke RPC operation.
717 * @param identifier module name and rpc identifier string for the desired operation
718 * @param body the body of the operation
719 * @param uriInfo URI info
720 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
723 // FIXME: identifier is just a *single* QName
724 @Path("/operations/{identifier:.+}")
726 MediaTypes.APPLICATION_YANG_DATA_JSON,
727 MediaType.APPLICATION_JSON,
730 MediaTypes.APPLICATION_YANG_DATA_JSON,
731 MediaTypes.APPLICATION_YANG_DATA_XML,
732 MediaType.APPLICATION_JSON,
733 MediaType.APPLICATION_XML,
736 public void operationsJsonPOST(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
737 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
738 try (var jsonBody = new JsonOperationInputBody(body)) {
739 operationsPOST(identifier, uriInfo, ar, jsonBody);
743 private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
744 final OperationInputBody body) {
745 server.operationsPOST(requestOf(uriInfo), uriInfo.getBaseUri(), identifier, body)
746 .addCallback(new JaxRsRestconfCallback<>(ar) {
748 Response transform(final InvokeResult result) {
749 final var body = result.output();
750 return body == null ? Response.noContent().build()
751 : Response.ok().entity(body).build();
757 * Get revision of IETF YANG Library module.
759 * @param ar {@link AsyncResponse} which needs to be completed
762 @Path("/yang-library-version")
764 MediaTypes.APPLICATION_YANG_DATA_JSON,
765 MediaTypes.APPLICATION_YANG_DATA_XML,
766 MediaType.APPLICATION_JSON,
767 MediaType.APPLICATION_XML,
770 public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
771 server.yangLibraryVersionGET(emptyRequest).addCallback(new FormattableBodyCallback(ar, emptyRequest.format()));
774 // FIXME: References to these resources are generated by our yang-library implementation. That means:
775 // - We really need to formalize the parameter structure so we get some help from JAX-RS during matching
777 // - optional yang-ext:mount prefix(es)
778 // - mandatory module name
779 // - optional module revision
780 // - We really should use /yang-library-module/{name}(/{revision})?
781 // - We seem to be lacking explicit support for submodules in there -- and those locations should then point
782 // to /yang-library-submodule/{moduleName}(/{moduleRevision})?/{name}(/{revision})? so as to look the
783 // submodule up efficiently and allow for the weird case where there are two submodules with the same name
784 // (that is currently not supported by the parser, but it will be in the future)
785 // - It does not make sense to support yang-ext:mount, unless we also intercept mount points and rewrite
786 // yang-library locations. We most likely want to do that to ensure users are not tempted to connect to
790 * Get schema of specific module.
792 * @param fileName source file name
793 * @param revision source revision
794 * @param ar {@link AsyncResponse} which needs to be completed
797 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
798 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
799 public void modulesYangGET(@PathParam("fileName") final String fileName,
800 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
801 completeModulesGET(server.modulesYangGET(emptyRequest, fileName, revision), ar);
805 * Get schema of specific module.
807 * @param mountPath mount point path
808 * @param fileName source file name
809 * @param revision source revision
810 * @param ar {@link AsyncResponse} which needs to be completed
813 @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
814 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
815 public void modulesYangGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
816 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
817 @Suspended final AsyncResponse ar) {
818 completeModulesGET(server.modulesYangGET(emptyRequest, mountPath, fileName, revision), ar);
822 * Get schema of specific module.
824 * @param fileName source file name
825 * @param revision source revision
826 * @param ar {@link AsyncResponse} which needs to be completed
829 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
830 @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
831 public void modulesYinGET(@PathParam("fileName") final String fileName,
832 @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
833 completeModulesGET(server.modulesYinGET(emptyRequest, fileName, revision), ar);
837 * Get schema of specific module.
839 * @param mountPath mount point path
840 * @param fileName source file name
841 * @param revision source revision
842 * @param ar {@link AsyncResponse} which needs to be completed
845 @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
846 @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
847 public void modulesYinGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
848 @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
849 @Suspended final AsyncResponse ar) {
850 completeModulesGET(server.modulesYinGET(emptyRequest, mountPath, fileName, revision), ar);
853 private static void completeModulesGET(final RestconfFuture<ModulesGetResult> future, final AsyncResponse ar) {
854 future.addCallback(new JaxRsRestconfCallback<>(ar) {
856 Response transform(final ModulesGetResult result) {
859 reader = result.source().openStream();
860 } catch (IOException e) {
861 throw new RestconfDocumentedException("Cannot open source", e);
863 return Response.ok(reader).build();