Add RestconfServer.dataDELETE()
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / services / impl / RestconfImpl.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.restconf.nb.rfc8040.rests.services.impl;
9
10 import static java.util.Objects.requireNonNull;
11
12 import java.io.InputStream;
13 import java.time.Clock;
14 import java.time.LocalDateTime;
15 import java.time.format.DateTimeFormatter;
16 import javax.ws.rs.Consumes;
17 import javax.ws.rs.DELETE;
18 import javax.ws.rs.Encoded;
19 import javax.ws.rs.GET;
20 import javax.ws.rs.NotFoundException;
21 import javax.ws.rs.POST;
22 import javax.ws.rs.Path;
23 import javax.ws.rs.PathParam;
24 import javax.ws.rs.Produces;
25 import javax.ws.rs.container.AsyncResponse;
26 import javax.ws.rs.container.Suspended;
27 import javax.ws.rs.core.Context;
28 import javax.ws.rs.core.MediaType;
29 import javax.ws.rs.core.Response;
30 import javax.ws.rs.core.Response.Status;
31 import javax.ws.rs.core.UriInfo;
32 import org.eclipse.jdt.annotation.NonNull;
33 import org.opendaylight.restconf.common.errors.RestconfFuture;
34 import org.opendaylight.restconf.nb.rfc8040.MediaTypes;
35 import org.opendaylight.restconf.nb.rfc8040.ReadDataParams;
36 import org.opendaylight.restconf.nb.rfc8040.databind.JsonOperationInputBody;
37 import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
38 import org.opendaylight.restconf.nb.rfc8040.databind.XmlOperationInputBody;
39 import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
40 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
41 import org.opendaylight.restconf.server.api.OperationsContent;
42 import org.opendaylight.restconf.server.api.RestconfServer;
43 import org.opendaylight.restconf.server.spi.OperationOutput;
44 import org.opendaylight.yangtools.yang.common.Empty;
45 import org.opendaylight.yangtools.yang.common.Revision;
46
47 /**
48  * Baseline RESTCONF implementation with JAX-RS.
49  */
50 @Path("/")
51 public final class RestconfImpl {
52     private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss");
53
54     private final RestconfServer server;
55
56     public RestconfImpl(final RestconfServer server) {
57         this.server = requireNonNull(server);
58     }
59
60     /**
61      * Delete the target data resource.
62      *
63      * @param identifier path to target
64      * @param ar {@link AsyncResponse} which needs to be completed
65      */
66     @DELETE
67     @Path("/data/{identifier:.+}")
68     @SuppressWarnings("checkstyle:abbreviationAsWordInName")
69     public void dataDELETE(@Encoded @PathParam("identifier") final String identifier,
70             @Suspended final AsyncResponse ar) {
71         server.dataDELETE(identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
72             @Override
73             Response transform(final Empty result) {
74                 return Response.noContent().build();
75             }
76         });
77     }
78
79     /**
80      * Get target data resource from data root.
81      *
82      * @param uriInfo URI info
83      * @param ar {@link AsyncResponse} which needs to be completed
84      */
85     @GET
86     @Path("/data")
87     @Produces({
88         MediaTypes.APPLICATION_YANG_DATA_JSON,
89         MediaTypes.APPLICATION_YANG_DATA_XML,
90         MediaType.APPLICATION_JSON,
91         MediaType.APPLICATION_XML,
92         MediaType.TEXT_XML
93     })
94     public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
95         final var readParams = QueryParams.newReadDataParams(uriInfo);
96         completeDataGET(server.dataGET(readParams), readParams, ar);
97     }
98
99     /**
100      * Get target data resource.
101      *
102      * @param identifier path to target
103      * @param uriInfo URI info
104      * @param ar {@link AsyncResponse} which needs to be completed
105      */
106     @GET
107     @Path("/data/{identifier:.+}")
108     @Produces({
109         MediaTypes.APPLICATION_YANG_DATA_JSON,
110         MediaTypes.APPLICATION_YANG_DATA_XML,
111         MediaType.APPLICATION_JSON,
112         MediaType.APPLICATION_XML,
113         MediaType.TEXT_XML
114     })
115     public void dataGET(@Encoded @PathParam("identifier") final String identifier,
116             @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
117         final var readParams = QueryParams.newReadDataParams(uriInfo);
118         completeDataGET(server.dataGET(identifier, readParams), readParams, ar);
119     }
120
121     private static void completeDataGET(final RestconfFuture<NormalizedNodePayload> future,
122             final ReadDataParams readParams, final AsyncResponse ar) {
123         future.addCallback(new JaxRsRestconfCallback<>(ar) {
124             @Override
125             Response transform(final NormalizedNodePayload result) {
126                 return switch (readParams.content()) {
127                     case ALL, CONFIG -> {
128                         final var type = result.data().name().getNodeType();
129                         yield Response.status(Status.OK)
130                             .entity(result)
131                             // FIXME: is this ETag okay?
132                             .header("ETag", '"' + type.getModule().getRevision().map(Revision::toString).orElse(null)
133                                 + "-" + type.getLocalName() + '"')
134                             .header("Last-Modified", FORMATTER.format(LocalDateTime.now(Clock.systemUTC())))
135                             .build();
136                     }
137                     case NONCONFIG -> Response.status(Status.OK).entity(result).build();
138                 };
139             }
140         });
141     }
142
143     /**
144      * List RPC and action operations in RFC7951 format.
145      *
146      * @return A string containing a JSON document conforming to both RFC8040 and RFC7951.
147      */
148     @GET
149     @Path("/operations")
150     @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
151     public String operationsJsonGET() {
152         return server.operationsGET().toJSON();
153     }
154
155     /**
156      * Retrieve list of operations and actions supported by the server or device in JSON format.
157      *
158      * @param operation path parameter to identify device and/or operation
159      * @return A string containing a JSON document conforming to both RFC8040 and RFC7951.
160      */
161     @GET
162     @Path("/operations/{operation:.+}")
163     @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
164     public String operationsJsonGET(@PathParam("operation") final String operation) {
165         return operationsGET(operation).toJSON();
166     }
167
168     /**
169      * List RPC and action operations in RFC8040 XML format.
170      *
171      * @return A string containing an XML document conforming to both RFC8040 section 11.3.1 and page 84.
172      */
173     @GET
174     @Path("/operations")
175     @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
176     public String operationsXmlGET() {
177         return server.operationsGET().toXML();
178     }
179
180     /**
181      * Retrieve list of operations and actions supported by the server or device in XML format.
182      *
183      * @param operation path parameter to identify device and/or operation
184      * @return A string containing an XML document conforming to both RFC8040 section 11.3.1 and page 84.
185      */
186     @GET
187     @Path("/operations/{operation:.+}")
188     @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
189     public String operationsXmlGET(@PathParam("operation") final String operation) {
190         return operationsGET(operation).toXML();
191     }
192
193     private @NonNull OperationsContent operationsGET(final String operation) {
194         final var content = server.operationsGET(operation);
195         if (content == null) {
196             throw new NotFoundException();
197         }
198         return content;
199     }
200
201     /**
202      * Invoke RPC operation.
203      *
204      * @param identifier module name and rpc identifier string for the desired operation
205      * @param body the body of the operation
206      * @param uriInfo URI info
207      * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
208      */
209     @POST
210     // FIXME: identifier is just a *single* QName
211     @Path("/operations/{identifier:.+}")
212     @Consumes({
213         MediaTypes.APPLICATION_YANG_DATA_XML,
214         MediaType.APPLICATION_XML,
215         MediaType.TEXT_XML
216     })
217     @Produces({
218         MediaTypes.APPLICATION_YANG_DATA_JSON,
219         MediaTypes.APPLICATION_YANG_DATA_XML,
220         MediaType.APPLICATION_JSON,
221         MediaType.APPLICATION_XML,
222         MediaType.TEXT_XML
223     })
224     public void operationsXmlPOST(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
225             @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
226         try (var xmlBody = new XmlOperationInputBody(body)) {
227             operationsPOST(identifier, uriInfo, ar, xmlBody);
228         }
229     }
230
231     /**
232      * Invoke RPC operation.
233      *
234      * @param identifier module name and rpc identifier string for the desired operation
235      * @param body the body of the operation
236      * @param uriInfo URI info
237      * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
238      */
239     @POST
240     // FIXME: identifier is just a *single* QName
241     @Path("/operations/{identifier:.+}")
242     @Consumes({
243         MediaTypes.APPLICATION_YANG_DATA_JSON,
244         MediaType.APPLICATION_JSON,
245     })
246     @Produces({
247         MediaTypes.APPLICATION_YANG_DATA_JSON,
248         MediaTypes.APPLICATION_YANG_DATA_XML,
249         MediaType.APPLICATION_JSON,
250         MediaType.APPLICATION_XML,
251         MediaType.TEXT_XML
252     })
253     public void operationsJsonPOST(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
254             @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
255         try (var jsonBody = new JsonOperationInputBody(body)) {
256             operationsPOST(identifier, uriInfo, ar, jsonBody);
257         }
258     }
259
260     private void operationsPOST(final String identifier, final UriInfo uriInfo, final AsyncResponse ar,
261             final OperationInputBody body) {
262         server.operationsPOST(uriInfo.getBaseUri(), identifier, body)
263             .addCallback(new JaxRsRestconfCallback<OperationOutput>(ar) {
264                 @Override
265                 Response transform(final OperationOutput result) {
266                     final var body = result.output();
267                     return body == null ? Response.noContent().build()
268                         : Response.ok().entity(new NormalizedNodePayload(result.operation(), body)).build();
269                 }
270             });
271     }
272
273     /**
274      * Get revision of IETF YANG Library module.
275      *
276      * @return {@link NormalizedNodePayload}
277      */
278     @GET
279     @Path("/yang-library-version")
280     @Produces({
281         MediaTypes.APPLICATION_YANG_DATA_JSON,
282         MediaTypes.APPLICATION_YANG_DATA_XML,
283         MediaType.APPLICATION_JSON,
284         MediaType.APPLICATION_XML,
285         MediaType.TEXT_XML
286     })
287     public NormalizedNodePayload yangLibraryVersionGET() {
288         return server.yangLibraryVersionGET();
289     }
290 }