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.InputStream;
13 import java.time.Clock;
14 import java.time.LocalDateTime;
15 import java.time.format.DateTimeFormatter;
16 import java.util.List;
17 import java.util.function.Function;
18 import javax.ws.rs.Consumes;
19 import javax.ws.rs.DELETE;
20 import javax.ws.rs.Encoded;
21 import javax.ws.rs.GET;
22 import javax.ws.rs.PATCH;
23 import javax.ws.rs.POST;
24 import javax.ws.rs.PUT;
25 import javax.ws.rs.Path;
26 import javax.ws.rs.PathParam;
27 import javax.ws.rs.Produces;
28 import javax.ws.rs.container.AsyncResponse;
29 import javax.ws.rs.container.Suspended;
30 import javax.ws.rs.core.Context;
31 import javax.ws.rs.core.MediaType;
32 import javax.ws.rs.core.Response;
33 import javax.ws.rs.core.Response.Status;
34 import javax.ws.rs.core.UriInfo;
35 import org.opendaylight.restconf.api.ApiPath;
36 import org.opendaylight.restconf.api.MediaTypes;
37 import org.opendaylight.restconf.common.errors.RestconfError;
38 import org.opendaylight.restconf.common.errors.RestconfFuture;
39 import org.opendaylight.restconf.common.patch.PatchStatusContext;
40 import org.opendaylight.restconf.nb.rfc8040.ReadDataParams;
41 import org.opendaylight.restconf.nb.rfc8040.databind.JsonChildBody;
42 import org.opendaylight.restconf.nb.rfc8040.databind.JsonDataPostBody;
43 import org.opendaylight.restconf.nb.rfc8040.databind.JsonOperationInputBody;
44 import org.opendaylight.restconf.nb.rfc8040.databind.JsonPatchBody;
45 import org.opendaylight.restconf.nb.rfc8040.databind.JsonResourceBody;
46 import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
47 import org.opendaylight.restconf.nb.rfc8040.databind.XmlChildBody;
48 import org.opendaylight.restconf.nb.rfc8040.databind.XmlDataPostBody;
49 import org.opendaylight.restconf.nb.rfc8040.databind.XmlOperationInputBody;
50 import org.opendaylight.restconf.nb.rfc8040.databind.XmlPatchBody;
51 import org.opendaylight.restconf.nb.rfc8040.databind.XmlResourceBody;
52 import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
53 import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
54 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
55 import org.opendaylight.restconf.server.api.DataPostResult;
56 import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
57 import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
58 import org.opendaylight.restconf.server.api.DataPutResult;
59 import org.opendaylight.restconf.server.api.OperationsGetResult;
60 import org.opendaylight.restconf.server.api.RestconfServer;
61 import org.opendaylight.restconf.server.spi.OperationOutput;
62 import org.opendaylight.yangtools.yang.common.Empty;
63 import org.opendaylight.yangtools.yang.common.Revision;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
68 * Baseline RESTCONF implementation with JAX-RS. Interfaces to a {@link RestconfServer}.
71 public final class JaxRsRestconf {
72 private static final Logger LOG = LoggerFactory.getLogger(JaxRsRestconf.class);
73 private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss");
75 private final RestconfServer server;
77 public JaxRsRestconf(final RestconfServer server) {
78 this.server = requireNonNull(server);
82 * Delete the target data resource.
84 * @param identifier path to target
85 * @param ar {@link AsyncResponse} which needs to be completed
88 @Path("/data/{identifier:.+}")
89 @SuppressWarnings("checkstyle:abbreviationAsWordInName")
90 public void dataDELETE(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
91 @Suspended final AsyncResponse ar) {
92 server.dataDELETE(identifier.apiPath).addCallback(new JaxRsRestconfCallback<>(ar) {
94 protected Response transform(final Empty result) {
95 return Response.noContent().build();
101 * Get target data resource from data root.
103 * @param uriInfo URI info
104 * @param ar {@link AsyncResponse} which needs to be completed
109 MediaTypes.APPLICATION_YANG_DATA_JSON,
110 MediaTypes.APPLICATION_YANG_DATA_XML,
111 MediaType.APPLICATION_JSON,
112 MediaType.APPLICATION_XML,
115 public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
116 final var readParams = QueryParams.newReadDataParams(uriInfo);
117 completeDataGET(server.dataGET(readParams), readParams, ar);
121 * Get target data resource.
123 * @param identifier path to target
124 * @param uriInfo URI info
125 * @param ar {@link AsyncResponse} which needs to be completed
128 @Path("/data/{identifier:.+}")
130 MediaTypes.APPLICATION_YANG_DATA_JSON,
131 MediaTypes.APPLICATION_YANG_DATA_XML,
132 MediaType.APPLICATION_JSON,
133 MediaType.APPLICATION_XML,
136 public void dataGET(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
137 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
138 final var readParams = QueryParams.newReadDataParams(uriInfo);
139 completeDataGET(server.dataGET(identifier.apiPath, readParams), readParams, ar);
142 private static void completeDataGET(final RestconfFuture<NormalizedNodePayload> future,
143 final ReadDataParams readParams, final AsyncResponse ar) {
144 future.addCallback(new JaxRsRestconfCallback<>(ar) {
146 protected Response transform(final NormalizedNodePayload result) {
147 return switch (readParams.content()) {
148 case ALL, CONFIG -> {
149 final var type = result.data().name().getNodeType();
150 yield Response.status(Status.OK)
152 // FIXME: is this ETag okay?
153 // FIXME: use tag() method instead
154 .header("ETag", '"' + type.getModule().getRevision().map(Revision::toString).orElse(null)
155 + "-" + type.getLocalName() + '"')
156 // FIXME: use lastModified() method instead
157 .header("Last-Modified", FORMATTER.format(LocalDateTime.now(Clock.systemUTC())))
160 case NONCONFIG -> Response.status(Status.OK).entity(result).build();
167 * Partially modify the target data store, as defined in
168 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
170 * @param body data node for put to config DS
171 * @param ar {@link AsyncResponse} which needs to be completed
176 MediaTypes.APPLICATION_YANG_DATA_XML,
177 MediaType.APPLICATION_XML,
180 public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
181 try (var xmlBody = new XmlResourceBody(body)) {
182 completeDataPATCH(server.dataPATCH(xmlBody), ar);
187 * Partially modify the target data resource, as defined in
188 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
190 * @param identifier path to target
191 * @param body data node for put to config DS
192 * @param ar {@link AsyncResponse} which needs to be completed
195 @Path("/data/{identifier:.+}")
197 MediaTypes.APPLICATION_YANG_DATA_XML,
198 MediaType.APPLICATION_XML,
201 public void dataXmlPATCH(@Encoded @PathParam("identifier") final JaxRsApiPath identifier, final InputStream body,
202 @Suspended final AsyncResponse ar) {
203 try (var xmlBody = new XmlResourceBody(body)) {
204 completeDataPATCH(server.dataPATCH(identifier.apiPath, xmlBody), ar);
209 * Partially modify the target data store, as defined in
210 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
212 * @param body data node for put to config DS
213 * @param ar {@link AsyncResponse} which needs to be completed
218 MediaTypes.APPLICATION_YANG_DATA_JSON,
219 MediaType.APPLICATION_JSON,
221 public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
222 try (var jsonBody = new JsonResourceBody(body)) {
223 completeDataPATCH(server.dataPATCH(jsonBody), ar);
228 * Partially modify the target data resource, as defined in
229 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
231 * @param identifier path to target
232 * @param body data node for put to config DS
233 * @param ar {@link AsyncResponse} which needs to be completed
236 @Path("/data/{identifier:.+}")
238 MediaTypes.APPLICATION_YANG_DATA_JSON,
239 MediaType.APPLICATION_JSON,
241 public void dataJsonPATCH(@Encoded @PathParam("identifier") final JaxRsApiPath identifier, final InputStream body,
242 @Suspended final AsyncResponse ar) {
243 try (var jsonBody = new JsonResourceBody(body)) {
244 completeDataPATCH(server.dataPATCH(identifier.apiPath, jsonBody), ar);
248 private static void completeDataPATCH(final RestconfFuture<Empty> future, final AsyncResponse ar) {
249 future.addCallback(new JaxRsRestconfCallback<>(ar) {
251 protected Response transform(final Empty result) {
252 return Response.ok().build();
258 * Ordered list of edits that are applied to the datastore by the server, as defined in
259 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
261 * @param body YANG Patch body
262 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
266 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
268 MediaTypes.APPLICATION_YANG_DATA_JSON,
269 MediaTypes.APPLICATION_YANG_DATA_XML
271 public void dataYangJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
272 try (var jsonBody = new JsonPatchBody(body)) {
273 completeDataYangPATCH(server.dataPATCH(jsonBody), ar);
278 * Ordered list of edits that are applied to the target datastore by the server, as defined in
279 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
281 * @param identifier path to target
282 * @param body YANG Patch body
283 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
286 @Path("/data/{identifier:.+}")
287 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
289 MediaTypes.APPLICATION_YANG_DATA_JSON,
290 MediaTypes.APPLICATION_YANG_DATA_XML
292 public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
293 final InputStream body, @Suspended final AsyncResponse ar) {
294 try (var jsonBody = new JsonPatchBody(body)) {
295 completeDataYangPATCH(server.dataPATCH(identifier.apiPath, jsonBody), ar);
300 * Ordered list of edits that are applied to the datastore by the server, as defined in
301 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
303 * @param body YANG Patch body
304 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
308 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
310 MediaTypes.APPLICATION_YANG_DATA_JSON,
311 MediaTypes.APPLICATION_YANG_DATA_XML
313 public void dataYangXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
314 try (var xmlBody = new XmlPatchBody(body)) {
315 completeDataYangPATCH(server.dataPATCH(xmlBody), ar);
320 * Ordered list of edits that are applied to the target datastore by the server, as defined in
321 * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
323 * @param identifier path to target
324 * @param body YANG Patch body
325 * @param ar {@link AsyncResponse} which needs to be completed with a {@link PatchStatusContext}
328 @Path("/data/{identifier:.+}")
329 @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
331 MediaTypes.APPLICATION_YANG_DATA_JSON,
332 MediaTypes.APPLICATION_YANG_DATA_XML
334 public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
335 final InputStream body, @Suspended final AsyncResponse ar) {
336 try (var xmlBody = new XmlPatchBody(body)) {
337 completeDataYangPATCH(server.dataPATCH(identifier.apiPath, xmlBody), ar);
341 private static void completeDataYangPATCH(final RestconfFuture<PatchStatusContext> future, final AsyncResponse ar) {
342 future.addCallback(new JaxRsRestconfCallback<>(ar) {
344 protected Response transform(final PatchStatusContext result) {
345 return Response.status(statusOf(result)).entity(result).build();
348 private static Status statusOf(final PatchStatusContext result) {
352 final var globalErrors = result.globalErrors();
353 if (globalErrors != null && !globalErrors.isEmpty()) {
354 return statusOfFirst(globalErrors);
356 for (var edit : result.editCollection()) {
358 final var editErrors = edit.getEditErrors();
359 if (editErrors != null && !editErrors.isEmpty()) {
360 return statusOfFirst(editErrors);
364 return Status.INTERNAL_SERVER_ERROR;
367 private static Status statusOfFirst(final List<RestconfError> error) {
368 return ErrorTags.statusOf(error.get(0).getErrorTag());
374 * Create a top-level data resource.
376 * @param body data node for put to config DS
377 * @param uriInfo URI info
378 * @param ar {@link AsyncResponse} which needs to be completed
383 MediaTypes.APPLICATION_YANG_DATA_JSON,
384 MediaType.APPLICATION_JSON,
386 public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
387 @Suspended final AsyncResponse ar) {
388 try (var jsonBody = new JsonChildBody(body)) {
389 completeDataPOST(server.dataPOST(jsonBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
394 * Create a data resource in target.
396 * @param identifier path to target
397 * @param body data node for put to config DS
398 * @param uriInfo URI info
399 * @param ar {@link AsyncResponse} which needs to be completed
402 @Path("/data/{identifier:.+}")
404 MediaTypes.APPLICATION_YANG_DATA_JSON,
405 MediaType.APPLICATION_JSON,
407 public void postDataJSON(@Encoded @PathParam("identifier") final JaxRsApiPath identifier, final InputStream body,
408 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
409 completeDataPOST(server.dataPOST(identifier.apiPath, new JsonDataPostBody(body),
410 QueryParams.normalize(uriInfo)), uriInfo, ar);
414 * Create a top-level data resource.
416 * @param body data node for put to config DS
417 * @param uriInfo URI info
418 * @param ar {@link AsyncResponse} which needs to be completed
423 MediaTypes.APPLICATION_YANG_DATA_XML,
424 MediaType.APPLICATION_XML,
427 public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
428 try (var xmlBody = new XmlChildBody(body)) {
429 completeDataPOST(server.dataPOST(xmlBody, QueryParams.normalize(uriInfo)), uriInfo, ar);
434 * Create a data resource in target.
436 * @param identifier path to target
437 * @param body data node for put to config DS
438 * @param uriInfo URI info
439 * @param ar {@link AsyncResponse} which needs to be completed
442 @Path("/data/{identifier:.+}")
444 MediaTypes.APPLICATION_YANG_DATA_XML,
445 MediaType.APPLICATION_XML,
448 public void postDataXML(@Encoded @PathParam("identifier") final JaxRsApiPath identifier, final InputStream body,
449 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
450 completeDataPOST(server.dataPOST(identifier.apiPath, new XmlDataPostBody(body), QueryParams.normalize(uriInfo)),
454 private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future, final UriInfo uriInfo,
455 final AsyncResponse ar) {
456 future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
458 protected Response transform(final DataPostResult result) {
459 if (result instanceof CreateResource createResource) {
460 return Response.created(uriInfo.getBaseUriBuilder()
462 .path(createResource.createdPath())
466 if (result instanceof InvokeOperation invokeOperation) {
467 final var output = invokeOperation.output();
468 return output == null ? Response.status(Status.NO_CONTENT).build()
469 : Response.status(Status.OK).entity(output).build();
471 LOG.error("Unhandled result {}", result);
472 return Response.serverError().build();
478 * Replace the data store.
480 * @param uriInfo request URI information
481 * @param body data node for put to config DS
482 * @param ar {@link AsyncResponse} which needs to be completed
487 MediaTypes.APPLICATION_YANG_DATA_JSON,
488 MediaType.APPLICATION_JSON,
490 public void dataJsonPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
491 try (var jsonBody = new JsonResourceBody(body)) {
492 completeDataPUT(server.dataPUT(jsonBody, QueryParams.normalize(uriInfo)), ar);
497 * Create or replace the target data resource.
499 * @param identifier path to target
500 * @param uriInfo request URI information
501 * @param body data node for put to config DS
502 * @param ar {@link AsyncResponse} which needs to be completed
505 @Path("/data/{identifier:.+}")
507 MediaTypes.APPLICATION_YANG_DATA_JSON,
508 MediaType.APPLICATION_JSON,
510 public void dataJsonPUT(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
511 @Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
512 try (var jsonBody = new JsonResourceBody(body)) {
513 completeDataPUT(server.dataPUT(identifier.apiPath, jsonBody, QueryParams.normalize(uriInfo)), ar);
518 * Replace the data store.
520 * @param uriInfo request URI information
521 * @param body data node for put to config DS
522 * @param ar {@link AsyncResponse} which needs to be completed
527 MediaTypes.APPLICATION_YANG_DATA_XML,
528 MediaType.APPLICATION_XML,
531 public void dataXmlPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
532 try (var xmlBody = new XmlResourceBody(body)) {
533 completeDataPUT(server.dataPUT(xmlBody, QueryParams.normalize(uriInfo)), ar);
538 * Create or replace the target data resource.
540 * @param identifier path to target
541 * @param uriInfo request URI information
542 * @param body data node for put to config DS
543 * @param ar {@link AsyncResponse} which needs to be completed
546 @Path("/data/{identifier:.+}")
548 MediaTypes.APPLICATION_YANG_DATA_XML,
549 MediaType.APPLICATION_XML,
552 public void dataXmlPUT(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
553 @Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
554 try (var xmlBody = new XmlResourceBody(body)) {
555 completeDataPUT(server.dataPUT(identifier.apiPath, xmlBody, QueryParams.normalize(uriInfo)), ar);
559 private static void completeDataPUT(final RestconfFuture<DataPutResult> future, final AsyncResponse ar) {
560 future.addCallback(new JaxRsRestconfCallback<>(ar) {
562 protected Response transform(final DataPutResult result) {
563 return switch (result) {
564 // Note: no Location header, as it matches the request path
565 case CREATED -> Response.status(Status.CREATED).build();
566 case REPLACED -> Response.noContent().build();
573 * List RPC and action operations in RFC7951 format.
575 * @param ar {@link AsyncResponse} which needs to be completed
579 @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
580 public void operationsJsonGET(@Suspended final AsyncResponse ar) {
581 completeOperationsJsonGet(server.operationsGET(), ar);
585 * Retrieve list of operations and actions supported by the server or device in JSON format.
587 * @param operation path parameter to identify device and/or operation
588 * @param ar {@link AsyncResponse} which needs to be completed
591 @Path("/operations/{operation:.+}")
592 @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
593 public void operationsJsonGET(@PathParam("operation") final JaxRsApiPath operation,
594 @Suspended final AsyncResponse ar) {
595 completeOperationsGet(server.operationsGET(operation.apiPath), ar, OperationsGetResult::toJSON);
598 private static void completeOperationsJsonGet(final RestconfFuture<OperationsGetResult> future,
599 final AsyncResponse ar) {
600 completeOperationsGet(future, ar, OperationsGetResult::toJSON);
604 * List RPC and action operations in RFC8040 XML format.
606 * @param ar {@link AsyncResponse} which needs to be completed
610 @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
611 public void operationsXmlGET(@Suspended final AsyncResponse ar) {
612 completeOperationsXmlGet(server.operationsGET(), ar);
616 * Retrieve list of operations and actions supported by the server or device in XML format.
618 * @param operation path parameter to identify device and/or operation
619 * @param ar {@link AsyncResponse} which needs to be completed
622 @Path("/operations/{operation:.+}")
623 @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
624 public void operationsXmlGET(@PathParam("operation") final JaxRsApiPath operation,
625 @Suspended final AsyncResponse ar) {
626 completeOperationsXmlGet(server.operationsGET(operation.apiPath), ar);
629 private static void completeOperationsXmlGet(final RestconfFuture<OperationsGetResult> future,
630 final AsyncResponse ar) {
631 completeOperationsGet(future, ar, OperationsGetResult::toXML);
634 private static void completeOperationsGet(final RestconfFuture<OperationsGetResult> future, final AsyncResponse ar,
635 final Function<OperationsGetResult, String> toString) {
636 future.addCallback(new JaxRsRestconfCallback<OperationsGetResult>(ar) {
638 protected Response transform(final OperationsGetResult result) {
639 return Response.ok().entity(toString.apply(result)).build();
645 * Invoke RPC operation.
647 * @param identifier module name and rpc identifier string for the desired operation
648 * @param body the body of the operation
649 * @param uriInfo URI info
650 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
653 // FIXME: identifier is just a *single* QName
654 @Path("/operations/{identifier:.+}")
656 MediaTypes.APPLICATION_YANG_DATA_XML,
657 MediaType.APPLICATION_XML,
661 MediaTypes.APPLICATION_YANG_DATA_JSON,
662 MediaTypes.APPLICATION_YANG_DATA_XML,
663 MediaType.APPLICATION_JSON,
664 MediaType.APPLICATION_XML,
667 public void operationsXmlPOST(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
668 final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
669 try (var xmlBody = new XmlOperationInputBody(body)) {
670 operationsPOST(identifier.apiPath, uriInfo, ar, xmlBody);
675 * Invoke RPC operation.
677 * @param identifier module name and rpc identifier string for the desired operation
678 * @param body the body of the operation
679 * @param uriInfo URI info
680 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
683 // FIXME: identifier is just a *single* QName
684 @Path("/operations/{identifier:.+}")
686 MediaTypes.APPLICATION_YANG_DATA_JSON,
687 MediaType.APPLICATION_JSON,
690 MediaTypes.APPLICATION_YANG_DATA_JSON,
691 MediaTypes.APPLICATION_YANG_DATA_XML,
692 MediaType.APPLICATION_JSON,
693 MediaType.APPLICATION_XML,
696 public void operationsJsonPOST(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
697 final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
698 try (var jsonBody = new JsonOperationInputBody(body)) {
699 operationsPOST(identifier.apiPath, uriInfo, ar, jsonBody);
703 private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
704 final OperationInputBody body) {
705 server.operationsPOST(uriInfo.getBaseUri(), identifier, body)
706 .addCallback(new JaxRsRestconfCallback<OperationOutput>(ar) {
708 protected Response transform(final OperationOutput result) {
709 final var body = result.output();
710 return body == null ? Response.noContent().build()
711 : Response.ok().entity(new NormalizedNodePayload(result.operation(), body)).build();
717 * Get revision of IETF YANG Library module.
719 * @param ar {@link AsyncResponse} which needs to be completed
722 @Path("/yang-library-version")
724 MediaTypes.APPLICATION_YANG_DATA_JSON,
725 MediaTypes.APPLICATION_YANG_DATA_XML,
726 MediaType.APPLICATION_JSON,
727 MediaType.APPLICATION_XML,
730 public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
731 server.yangLibraryVersionGET().addCallback(new JaxRsRestconfCallback<NormalizedNodePayload>(ar) {
733 protected Response transform(final NormalizedNodePayload result) {
734 return Response.ok().entity(result).build();