blob: ba11421c1ad383e2ef4a18a6241c753c69518aa1 [file] [log] [blame]
Henry Yue20926e2016-08-25 22:58:02 -04001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Henry Yue20926e2016-08-25 22:58:02 -04003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.protocol.restconf.server.rpp;
18
19import com.fasterxml.jackson.core.JsonProcessingException;
20import com.fasterxml.jackson.databind.node.ObjectNode;
21import org.glassfish.jersey.server.ChunkedOutput;
Henry Yue20926e2016-08-25 22:58:02 -040022import org.onosproject.rest.AbstractWebResource;
Henry Yu3a91b132017-04-26 16:00:55 -040023import org.onosproject.restconf.api.RestconfException;
Henry Yuc10f7fc2017-07-26 13:42:08 -040024import org.onosproject.restconf.api.RestconfRpcOutput;
Henry Yu3a91b132017-04-26 16:00:55 -040025import org.onosproject.restconf.api.RestconfService;
Henry Yue20926e2016-08-25 22:58:02 -040026import org.slf4j.Logger;
27
Henry Yuc10f7fc2017-07-26 13:42:08 -040028import javax.servlet.http.HttpServletRequest;
Henry Yue20926e2016-08-25 22:58:02 -040029import javax.ws.rs.Consumes;
30import javax.ws.rs.DELETE;
31import javax.ws.rs.GET;
Dimitrios Mavrommatis89e60d72017-12-02 18:45:35 -080032import javax.ws.rs.PATCH;
Henry Yue20926e2016-08-25 22:58:02 -040033import javax.ws.rs.POST;
34import javax.ws.rs.PUT;
35import javax.ws.rs.Path;
36import javax.ws.rs.PathParam;
37import javax.ws.rs.Produces;
38import javax.ws.rs.core.Context;
39import javax.ws.rs.core.MediaType;
40import javax.ws.rs.core.Response;
41import javax.ws.rs.core.UriInfo;
42import java.io.IOException;
43import java.io.InputStream;
Henry Yuc10f7fc2017-07-26 13:42:08 -040044import java.net.URI;
45import java.util.concurrent.CompletableFuture;
Henry Yue20926e2016-08-25 22:58:02 -040046
chengfanc58d4be2016-09-20 10:33:12 +080047import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
48import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
Henry Yuc10f7fc2017-07-26 13:42:08 -040049import static javax.ws.rs.core.Response.Status.NOT_FOUND;
50import static javax.ws.rs.core.Response.Status.OK;
Henry Yue20926e2016-08-25 22:58:02 -040051import static org.slf4j.LoggerFactory.getLogger;
52
chengfanc58d4be2016-09-20 10:33:12 +080053
Henry Yue20926e2016-08-25 22:58:02 -040054/*
55 * This class is the main implementation of the RESTCONF Protocol
56 * Proxy module. Currently it only handles some basic operations
57 * on data resource nodes. However, the design intention is to
58 * create a code structure that allows new methods/functionality
59 * to be easily added in future releases.
60 */
61
62/**
63 * Implementation of the RESTCONF Protocol Proxy module.
64 */
65@Path("/")
66public class RestconfWebResource extends AbstractWebResource {
67
68 @Context
69 UriInfo uriInfo;
70
Henry Yuc10f7fc2017-07-26 13:42:08 -040071 private static final String NOT_EXIST = "Requested data resource does not exist";
72
Henry Yu3a91b132017-04-26 16:00:55 -040073 private final RestconfService service = get(RestconfService.class);
Henry Yue20926e2016-08-25 22:58:02 -040074 private final Logger log = getLogger(getClass());
75
76 /**
77 * Handles a RESTCONF GET operation against a target data resource. If the
78 * operation is successful, the JSON presentation of the resource plus HTTP
79 * status code "200 OK" is returned. Otherwise, HTTP error status code
80 * "400 Bad Request" is returned.
81 *
82 * @param uriString URI of the data resource.
83 * @return HTTP response
84 */
85 @GET
Henry Yuc10f7fc2017-07-26 13:42:08 -040086 @Consumes(MediaType.APPLICATION_JSON)
Henry Yue20926e2016-08-25 22:58:02 -040087 @Produces(MediaType.APPLICATION_JSON)
88 @Path("data/{identifier : .+}")
89 public Response handleGetRequest(@PathParam("identifier") String uriString) {
Henry Yue20926e2016-08-25 22:58:02 -040090 log.debug("handleGetRequest: {}", uriString);
91
Henry Yuc10f7fc2017-07-26 13:42:08 -040092 URI uri = uriInfo.getRequestUri();
93
Henry Yue20926e2016-08-25 22:58:02 -040094 try {
Henry Yuc10f7fc2017-07-26 13:42:08 -040095 ObjectNode node = service.runGetOperationOnDataResource(uri);
96 if (node == null) {
97 return Response.status(NOT_FOUND).entity(NOT_EXIST).build();
98 }
Henry Yue20926e2016-08-25 22:58:02 -040099 return ok(node).build();
100 } catch (RestconfException e) {
101 log.error("ERROR: handleGetRequest: {}", e.getMessage());
102 log.debug("Exception in handleGetRequest:", e);
103 return e.getResponse();
104 }
105 }
106
107 /**
108 * Handles the RESTCONF Event Notification Subscription request. If the
109 * subscription is successful, a ChunkedOutput stream is created and returned
110 * to the caller.
Yuta HIGUCHId1ce4bc2017-06-03 01:05:33 -0700111 * <p>
Henry Yue20926e2016-08-25 22:58:02 -0400112 * This function is not blocked on streaming the data (so that it can handle
113 * other incoming requests). Instead, a worker thread running in the background
114 * does the data streaming. If errors occur during streaming, the worker thread
115 * calls ChunkedOutput.close() to disconnect the session and terminates itself.
116 *
117 * @param streamId Event stream ID
Henry Yuc10f7fc2017-07-26 13:42:08 -0400118 * @param request RESTCONF client information from which the client IP
119 * address is retrieved
Henry Yue20926e2016-08-25 22:58:02 -0400120 * @return A string data stream over HTTP keep-alive session
121 */
122 @GET
123 @Produces(MediaType.APPLICATION_JSON)
124 @Path("streams/{streamId}")
Henry Yuc10f7fc2017-07-26 13:42:08 -0400125 public ChunkedOutput<String> handleNotificationRegistration(@PathParam("streamId") String streamId,
126 @Context HttpServletRequest request) {
Yuta HIGUCHId1ce4bc2017-06-03 01:05:33 -0700127 final ChunkedOutput<String> output = new ChunkedOutput<>(String.class);
Henry Yue20926e2016-08-25 22:58:02 -0400128 try {
Henry Yuc10f7fc2017-07-26 13:42:08 -0400129 service.subscribeEventStream(streamId, request.getRemoteAddr(), output);
Henry Yue20926e2016-08-25 22:58:02 -0400130 } catch (RestconfException e) {
131 log.error("ERROR: handleNotificationRegistration: {}", e.getMessage());
132 log.debug("Exception in handleNotificationRegistration:", e);
133 try {
134 output.close();
135 } catch (IOException ex) {
136 log.error("ERROR: handleNotificationRegistration:", ex);
137 }
138 }
139
140 return output;
141 }
142
143 /**
Henry Yuc10f7fc2017-07-26 13:42:08 -0400144 * Handles a RESTCONF POST operation against the entire data store. If the
145 * operation is successful, HTTP status code "201 Created" is returned
146 * and there is no response message-body. If the data resource already
147 * exists, then the HTTP status code "409 Conflict" is returned.
148 *
149 * @param stream Input JSON object
150 * @return HTTP response
151 */
152 @POST
153 @Consumes(MediaType.APPLICATION_JSON)
154 @Produces(MediaType.APPLICATION_JSON)
155 @Path("data")
156 public Response handlePostDatastore(InputStream stream) {
157
158 log.debug("handlePostDatastore");
159 return handlePostRequest(null, stream);
160 }
161
162 /**
Henry Yue20926e2016-08-25 22:58:02 -0400163 * Handles a RESTCONF POST operation against a data resource. If the
164 * operation is successful, HTTP status code "201 Created" is returned
165 * and there is no response message-body. If the data resource already
166 * exists, then the HTTP status code "409 Conflict" is returned.
167 *
Henry Yuc10f7fc2017-07-26 13:42:08 -0400168 * @param uriString URI of the parent data resource
Henry Yue20926e2016-08-25 22:58:02 -0400169 * @param stream Input JSON object
170 * @return HTTP response
171 */
172 @POST
173 @Consumes(MediaType.APPLICATION_JSON)
174 @Produces(MediaType.APPLICATION_JSON)
175 @Path("data/{identifier : .+}")
chengfanc58d4be2016-09-20 10:33:12 +0800176 public Response handlePostRequest(@PathParam("identifier") String uriString,
177 InputStream stream) {
Henry Yue20926e2016-08-25 22:58:02 -0400178 log.debug("handlePostRequest: {}", uriString);
179
Henry Yuc10f7fc2017-07-26 13:42:08 -0400180 URI uri = uriInfo.getRequestUri();
181
Henry Yue20926e2016-08-25 22:58:02 -0400182 try {
183 ObjectNode rootNode = (ObjectNode) mapper().readTree(stream);
184
Henry Yuc10f7fc2017-07-26 13:42:08 -0400185 service.runPostOperationOnDataResource(uri, rootNode);
Henry Yue20926e2016-08-25 22:58:02 -0400186 return Response.created(uriInfo.getRequestUri()).build();
187 } catch (JsonProcessingException e) {
188 log.error("ERROR: handlePostRequest ", e);
chengfanc58d4be2016-09-20 10:33:12 +0800189 return Response.status(BAD_REQUEST).build();
Henry Yue20926e2016-08-25 22:58:02 -0400190 } catch (RestconfException e) {
191 log.error("ERROR: handlePostRequest: {}", e.getMessage());
192 log.debug("Exception in handlePostRequest:", e);
193 return e.getResponse();
194 } catch (IOException ex) {
195 log.error("ERROR: handlePostRequest ", ex);
chengfanc58d4be2016-09-20 10:33:12 +0800196 return Response.status(INTERNAL_SERVER_ERROR).build();
Henry Yue20926e2016-08-25 22:58:02 -0400197 }
198 }
199
200 /**
Henry Yuc10f7fc2017-07-26 13:42:08 -0400201 * Handles a RESTCONF RPC request. This function executes the RPC in
202 * the target application's context and returns the results as a Future.
203 *
204 * @param rpcName Name of the RPC
205 * @param rpcInput Input parameters
206 * @param request RESTCONF client information from which the client IP
207 * address is retrieved
208 * @return RPC output
209 */
210 @POST
211 @Consumes(MediaType.APPLICATION_JSON)
212 @Produces(MediaType.APPLICATION_JSON)
213 @Path("operations/{rpc : .+}")
214 public Response handleRpcRequest(@PathParam("rpc") String rpcName,
215 InputStream rpcInput,
216 @Context HttpServletRequest request) {
217 URI uri = uriInfo.getRequestUri();
218 try {
219 ObjectNode inputNode = (ObjectNode) mapper().readTree(rpcInput);
220 CompletableFuture<RestconfRpcOutput> rpcFuture = service.runRpc(uri,
221 inputNode,
222 request.getRemoteAddr());
223 RestconfRpcOutput restconfRpcOutput;
224 restconfRpcOutput = rpcFuture.get();
225 if (restconfRpcOutput.status() != OK) {
226 return Response.status(restconfRpcOutput.status())
227 .entity(restconfRpcOutput.reason()).build();
228 }
229 ObjectNode node = restconfRpcOutput.output();
230 return ok(node).build();
231 } catch (JsonProcessingException e) {
232 log.error("ERROR: handleRpcRequest", e);
233 return Response.status(BAD_REQUEST).build();
234 } catch (RestconfException e) {
235 log.error("ERROR: handleRpcRequest: {}", e.getMessage());
236 log.debug("Exception in handleRpcRequest:", e);
237 return e.getResponse();
238 } catch (Exception e) {
239 log.error("ERROR: handleRpcRequest ", e);
240 return Response.status(INTERNAL_SERVER_ERROR).build();
241 }
242 }
243
244 /**
Henry Yue20926e2016-08-25 22:58:02 -0400245 * Handles a RESTCONF PUT operation against a data resource. If a new
246 * resource is successfully created, then the HTTP status code "201 Created"
247 * is returned. If an existing resource is modified, then the HTTP
248 * status code "204 No Content" is returned. If the input JSON payload
249 * contains errors, then "400 Bad Request" is returned. If an exception
250 * occurs during the operation, the status code enclosed in
251 * the RestconfException object, such as "500 Internal Server Error",
252 * is returned.
253 *
254 * @param uriString URI of the data resource.
255 * @param stream Input JSON object
256 * @return HTTP response
257 */
258 @PUT
259 @Consumes(MediaType.APPLICATION_JSON)
260 @Produces(MediaType.APPLICATION_JSON)
261 @Path("data/{identifier : .+}")
chengfanc58d4be2016-09-20 10:33:12 +0800262 public Response handlePutRequest(@PathParam("identifier") String uriString,
263 InputStream stream) {
Henry Yue20926e2016-08-25 22:58:02 -0400264 log.debug("handlePutRequest: {}", uriString);
265
Henry Yuc10f7fc2017-07-26 13:42:08 -0400266 URI uri = uriInfo.getRequestUri();
267
Henry Yue20926e2016-08-25 22:58:02 -0400268 try {
269 ObjectNode rootNode = (ObjectNode) mapper().readTree(stream);
270
Henry Yuc10f7fc2017-07-26 13:42:08 -0400271 service.runPutOperationOnDataResource(uri, rootNode);
Henry Yue20926e2016-08-25 22:58:02 -0400272 return Response.created(uriInfo.getRequestUri()).build();
273 } catch (JsonProcessingException e) {
274 log.error("ERROR: handlePutRequest ", e);
chengfanc58d4be2016-09-20 10:33:12 +0800275 return Response.status(BAD_REQUEST).build();
Henry Yue20926e2016-08-25 22:58:02 -0400276 } catch (RestconfException e) {
277 log.error("ERROR: handlePutRequest: {}", e.getMessage());
278 log.debug("Exception in handlePutRequest:", e);
279 return e.getResponse();
280 } catch (IOException ex) {
281 log.error("ERROR: handlePutRequest ", ex);
chengfanc58d4be2016-09-20 10:33:12 +0800282 return Response.status(INTERNAL_SERVER_ERROR).build();
Henry Yue20926e2016-08-25 22:58:02 -0400283 }
284 }
285
286 /**
287 * Handles the RESTCONF DELETION Operation against a data resource. If the
288 * resource is successfully deleted, the HTTP status code "204 No Content"
289 * is returned in the response. If an exception occurs, then the
290 * HTTP status code enclosed in the RestconfException object is
291 * returned.
292 *
293 * @param uriString URI of the data resource to be deleted.
294 * @return HTTP response
295 */
296 @DELETE
Henry Yuc10f7fc2017-07-26 13:42:08 -0400297 @Consumes(MediaType.APPLICATION_JSON)
Henry Yue20926e2016-08-25 22:58:02 -0400298 @Produces(MediaType.APPLICATION_JSON)
299 @Path("data/{identifier : .+}")
300 public Response handleDeleteRequest(@PathParam("identifier") String uriString) {
Henry Yue20926e2016-08-25 22:58:02 -0400301 log.debug("handleDeleteRequest: {}", uriString);
302
Henry Yuc10f7fc2017-07-26 13:42:08 -0400303 URI uri = uriInfo.getRequestUri();
304
Henry Yue20926e2016-08-25 22:58:02 -0400305 try {
Henry Yuc10f7fc2017-07-26 13:42:08 -0400306 service.runDeleteOperationOnDataResource(uri);
Henry Yue20926e2016-08-25 22:58:02 -0400307 return Response.ok().build();
308 } catch (RestconfException e) {
309 log.error("ERROR: handleDeleteRequest: {}", e.getMessage());
310 log.debug("Exception in handleDeleteRequest:", e);
311 return e.getResponse();
312 }
313 }
314
chengfanc58d4be2016-09-20 10:33:12 +0800315 /**
316 * Handles a RESTCONF PATCH operation against a data resource.
317 * If the PATCH request succeeds, a "200 OK" status-line is returned if
318 * there is a message-body, and "204 No Content" is returned if no
319 * response message-body is sent.
320 *
Henry Yuc10f7fc2017-07-26 13:42:08 -0400321 * @param uriString URI of the parent data resource
chengfanc58d4be2016-09-20 10:33:12 +0800322 * @param stream Input JSON object
323 * @return HTTP response
324 */
Dimitrios Mavrommatis89e60d72017-12-02 18:45:35 -0800325 @PATCH
chengfanc58d4be2016-09-20 10:33:12 +0800326 @Consumes(MediaType.APPLICATION_JSON)
327 @Produces(MediaType.APPLICATION_JSON)
328 @Path("data/{identifier : .+}")
329 public Response handlePatchRequest(@PathParam("identifier") String uriString,
330 InputStream stream) {
331
332 log.debug("handlePatchRequest: {}", uriString);
333
Henry Yuc10f7fc2017-07-26 13:42:08 -0400334 URI uri = uriInfo.getRequestUri();
335
chengfanc58d4be2016-09-20 10:33:12 +0800336 try {
337 ObjectNode rootNode = (ObjectNode) mapper().readTree(stream);
338
Henry Yuc10f7fc2017-07-26 13:42:08 -0400339 service.runPatchOperationOnDataResource(uri, rootNode);
chengfanc58d4be2016-09-20 10:33:12 +0800340 return Response.ok().build();
341 } catch (JsonProcessingException e) {
342 log.error("ERROR: handlePatchRequest ", e);
343 return Response.status(BAD_REQUEST).build();
344 } catch (RestconfException e) {
345 log.error("ERROR: handlePatchRequest: {}", e.getMessage());
346 log.debug("Exception in handlePatchRequest:", e);
347 return e.getResponse();
348 } catch (IOException ex) {
349 log.error("ERROR: handlePatchRequest ", ex);
350 return Response.status(INTERNAL_SERVER_ERROR).build();
351 }
352 }
353
Henry Yuc10f7fc2017-07-26 13:42:08 -0400354
355 /**
356 * Handles a RESTCONF PATCH operation against the entire data store.
357 * If the PATCH request succeeds, a "200 OK" status-line is returned if
358 * there is a message-body, and "204 No Content" is returned if no
359 * response message-body is sent.
360 *
361 * @param stream Input JSON object
362 * @return HTTP response
363 */
Dimitrios Mavrommatis89e60d72017-12-02 18:45:35 -0800364 @PATCH
Henry Yuc10f7fc2017-07-26 13:42:08 -0400365 @Consumes(MediaType.APPLICATION_JSON)
366 @Produces(MediaType.APPLICATION_JSON)
367 @Path("data")
368 public Response handlePatchDatastore(InputStream stream) {
369 log.debug("handlePatchDatastore");
370 return handlePatchRequest(null, stream);
371 }
Henry Yue20926e2016-08-25 22:58:02 -0400372}