blob: 36eb3a29e56e66d65346f6665228aaa52e2c692a [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;
Sean Condon13b16812018-01-25 10:31:49 +000023import org.onosproject.restconf.api.RestconfError;
Henry Yu3a91b132017-04-26 16:00:55 -040024import org.onosproject.restconf.api.RestconfException;
Henry Yuc10f7fc2017-07-26 13:42:08 -040025import org.onosproject.restconf.api.RestconfRpcOutput;
Henry Yu3a91b132017-04-26 16:00:55 -040026import org.onosproject.restconf.api.RestconfService;
Henry Yue20926e2016-08-25 22:58:02 -040027import org.slf4j.Logger;
28
Henry Yuc10f7fc2017-07-26 13:42:08 -040029import javax.servlet.http.HttpServletRequest;
Henry Yue20926e2016-08-25 22:58:02 -040030import javax.ws.rs.Consumes;
31import javax.ws.rs.DELETE;
32import javax.ws.rs.GET;
Dimitrios Mavrommatis89e60d72017-12-02 18:45:35 -080033import javax.ws.rs.PATCH;
Henry Yue20926e2016-08-25 22:58:02 -040034import javax.ws.rs.POST;
35import javax.ws.rs.PUT;
36import javax.ws.rs.Path;
37import javax.ws.rs.PathParam;
38import javax.ws.rs.Produces;
39import javax.ws.rs.core.Context;
40import javax.ws.rs.core.MediaType;
41import javax.ws.rs.core.Response;
42import javax.ws.rs.core.UriInfo;
43import java.io.IOException;
44import java.io.InputStream;
Henry Yuc10f7fc2017-07-26 13:42:08 -040045import java.net.URI;
Sean Condon13b16812018-01-25 10:31:49 +000046import java.util.Arrays;
Henry Yuc10f7fc2017-07-26 13:42:08 -040047import java.util.concurrent.CompletableFuture;
Henry Yue20926e2016-08-25 22:58:02 -040048
chengfanc58d4be2016-09-20 10:33:12 +080049import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
50import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
Henry Yuc10f7fc2017-07-26 13:42:08 -040051import static javax.ws.rs.core.Response.Status.NOT_FOUND;
52import static javax.ws.rs.core.Response.Status.OK;
Henry Yue20926e2016-08-25 22:58:02 -040053import static org.slf4j.LoggerFactory.getLogger;
54
chengfanc58d4be2016-09-20 10:33:12 +080055
Henry Yue20926e2016-08-25 22:58:02 -040056/*
57 * This class is the main implementation of the RESTCONF Protocol
58 * Proxy module. Currently it only handles some basic operations
59 * on data resource nodes. However, the design intention is to
60 * create a code structure that allows new methods/functionality
61 * to be easily added in future releases.
62 */
63
64/**
65 * Implementation of the RESTCONF Protocol Proxy module.
66 */
67@Path("/")
68public class RestconfWebResource extends AbstractWebResource {
69
70 @Context
71 UriInfo uriInfo;
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
Sean Condon13b16812018-01-25 10:31:49 +000079 * status code "200 OK" is returned. If it is not found then "404 Not Found"
80 * is returned. On internal error "500 Internal Server Error" is returned.
Henry Yue20926e2016-08-25 22:58:02 -040081 *
82 * @param uriString URI of the data resource.
Sean Condon13b16812018-01-25 10:31:49 +000083 * @return HTTP response - 200, 404 or 500
Henry Yue20926e2016-08-25 22:58:02 -040084 */
85 @GET
86 @Produces(MediaType.APPLICATION_JSON)
87 @Path("data/{identifier : .+}")
88 public Response handleGetRequest(@PathParam("identifier") String uriString) {
Henry Yue20926e2016-08-25 22:58:02 -040089 log.debug("handleGetRequest: {}", uriString);
90
Henry Yuc10f7fc2017-07-26 13:42:08 -040091 URI uri = uriInfo.getRequestUri();
92
Henry Yue20926e2016-08-25 22:58:02 -040093 try {
Henry Yuc10f7fc2017-07-26 13:42:08 -040094 ObjectNode node = service.runGetOperationOnDataResource(uri);
95 if (node == null) {
Sean Condon13b16812018-01-25 10:31:49 +000096 RestconfError error =
97 RestconfError.builder(RestconfError.ErrorType.PROTOCOL,
98 RestconfError.ErrorTag.INVALID_VALUE)
99 .errorMessage("Resource not found")
100 .errorPath(uriString)
101 .errorAppTag("handleGetRequest")
102 .build();
103 return Response.status(NOT_FOUND)
104 .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
Henry Yuc10f7fc2017-07-26 13:42:08 -0400105 }
Henry Yue20926e2016-08-25 22:58:02 -0400106 return ok(node).build();
107 } catch (RestconfException e) {
108 log.error("ERROR: handleGetRequest: {}", e.getMessage());
109 log.debug("Exception in handleGetRequest:", e);
Sean Condon13b16812018-01-25 10:31:49 +0000110 return Response.status(e.getResponse().getStatus()).entity(e.toRestconfErrorJson()).build();
111 } catch (Exception e) {
112 RestconfError error = RestconfError
113 .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED)
114 .errorMessage(e.getMessage()).errorAppTag("handlePostRequest").build();
115 return Response.status(INTERNAL_SERVER_ERROR)
116 .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
Henry Yue20926e2016-08-25 22:58:02 -0400117 }
118 }
119
120 /**
121 * Handles the RESTCONF Event Notification Subscription request. If the
122 * subscription is successful, a ChunkedOutput stream is created and returned
123 * to the caller.
Yuta HIGUCHId1ce4bc2017-06-03 01:05:33 -0700124 * <p>
Henry Yue20926e2016-08-25 22:58:02 -0400125 * This function is not blocked on streaming the data (so that it can handle
126 * other incoming requests). Instead, a worker thread running in the background
127 * does the data streaming. If errors occur during streaming, the worker thread
128 * calls ChunkedOutput.close() to disconnect the session and terminates itself.
129 *
130 * @param streamId Event stream ID
Henry Yuc10f7fc2017-07-26 13:42:08 -0400131 * @param request RESTCONF client information from which the client IP
132 * address is retrieved
Henry Yue20926e2016-08-25 22:58:02 -0400133 * @return A string data stream over HTTP keep-alive session
134 */
135 @GET
136 @Produces(MediaType.APPLICATION_JSON)
137 @Path("streams/{streamId}")
Henry Yuc10f7fc2017-07-26 13:42:08 -0400138 public ChunkedOutput<String> handleNotificationRegistration(@PathParam("streamId") String streamId,
139 @Context HttpServletRequest request) {
Yuta HIGUCHId1ce4bc2017-06-03 01:05:33 -0700140 final ChunkedOutput<String> output = new ChunkedOutput<>(String.class);
Henry Yue20926e2016-08-25 22:58:02 -0400141 try {
Henry Yuc10f7fc2017-07-26 13:42:08 -0400142 service.subscribeEventStream(streamId, request.getRemoteAddr(), output);
Henry Yue20926e2016-08-25 22:58:02 -0400143 } catch (RestconfException e) {
144 log.error("ERROR: handleNotificationRegistration: {}", e.getMessage());
145 log.debug("Exception in handleNotificationRegistration:", e);
146 try {
147 output.close();
148 } catch (IOException ex) {
149 log.error("ERROR: handleNotificationRegistration:", ex);
150 }
151 }
152
153 return output;
154 }
155
156 /**
Henry Yuc10f7fc2017-07-26 13:42:08 -0400157 * Handles a RESTCONF POST operation against the entire data store. If the
158 * operation is successful, HTTP status code "201 Created" is returned
159 * and there is no response message-body. If the data resource already
160 * exists, then the HTTP status code "409 Conflict" is returned.
161 *
162 * @param stream Input JSON object
163 * @return HTTP response
164 */
165 @POST
166 @Consumes(MediaType.APPLICATION_JSON)
167 @Produces(MediaType.APPLICATION_JSON)
168 @Path("data")
169 public Response handlePostDatastore(InputStream stream) {
170
171 log.debug("handlePostDatastore");
172 return handlePostRequest(null, stream);
173 }
174
175 /**
Henry Yue20926e2016-08-25 22:58:02 -0400176 * Handles a RESTCONF POST operation against a data resource. If the
177 * operation is successful, HTTP status code "201 Created" is returned
178 * and there is no response message-body. If the data resource already
179 * exists, then the HTTP status code "409 Conflict" is returned.
180 *
Henry Yuc10f7fc2017-07-26 13:42:08 -0400181 * @param uriString URI of the parent data resource
Henry Yue20926e2016-08-25 22:58:02 -0400182 * @param stream Input JSON object
183 * @return HTTP response
184 */
185 @POST
186 @Consumes(MediaType.APPLICATION_JSON)
187 @Produces(MediaType.APPLICATION_JSON)
188 @Path("data/{identifier : .+}")
chengfanc58d4be2016-09-20 10:33:12 +0800189 public Response handlePostRequest(@PathParam("identifier") String uriString,
190 InputStream stream) {
Henry Yue20926e2016-08-25 22:58:02 -0400191 log.debug("handlePostRequest: {}", uriString);
192
Henry Yuc10f7fc2017-07-26 13:42:08 -0400193 URI uri = uriInfo.getRequestUri();
194
Henry Yue20926e2016-08-25 22:58:02 -0400195 try {
196 ObjectNode rootNode = (ObjectNode) mapper().readTree(stream);
197
Henry Yuc10f7fc2017-07-26 13:42:08 -0400198 service.runPostOperationOnDataResource(uri, rootNode);
Henry Yue20926e2016-08-25 22:58:02 -0400199 return Response.created(uriInfo.getRequestUri()).build();
200 } catch (JsonProcessingException e) {
201 log.error("ERROR: handlePostRequest ", e);
Sean Condon13b16812018-01-25 10:31:49 +0000202 RestconfError error = RestconfError
203 .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.MALFORMED_MESSAGE)
204 .errorMessage(e.getMessage()).errorAppTag("handlePostRequest").build();
205 return Response.status(BAD_REQUEST)
206 .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
Henry Yue20926e2016-08-25 22:58:02 -0400207 } catch (RestconfException e) {
208 log.error("ERROR: handlePostRequest: {}", e.getMessage());
209 log.debug("Exception in handlePostRequest:", e);
Sean Condon13b16812018-01-25 10:31:49 +0000210 return Response.status(e.getResponse().getStatus())
211 .entity(e.toRestconfErrorJson()).build();
Henry Yue20926e2016-08-25 22:58:02 -0400212 } catch (IOException ex) {
213 log.error("ERROR: handlePostRequest ", ex);
Sean Condon13b16812018-01-25 10:31:49 +0000214 RestconfError error = RestconfError
215 .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED)
216 .errorMessage(ex.getMessage()).errorAppTag("handlePostRequest").build();
217 return Response.status(INTERNAL_SERVER_ERROR)
218 .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
Henry Yue20926e2016-08-25 22:58:02 -0400219 }
220 }
221
222 /**
Henry Yuc10f7fc2017-07-26 13:42:08 -0400223 * Handles a RESTCONF RPC request. This function executes the RPC in
224 * the target application's context and returns the results as a Future.
225 *
226 * @param rpcName Name of the RPC
227 * @param rpcInput Input parameters
228 * @param request RESTCONF client information from which the client IP
229 * address is retrieved
230 * @return RPC output
231 */
232 @POST
233 @Consumes(MediaType.APPLICATION_JSON)
234 @Produces(MediaType.APPLICATION_JSON)
235 @Path("operations/{rpc : .+}")
236 public Response handleRpcRequest(@PathParam("rpc") String rpcName,
237 InputStream rpcInput,
238 @Context HttpServletRequest request) {
239 URI uri = uriInfo.getRequestUri();
240 try {
241 ObjectNode inputNode = (ObjectNode) mapper().readTree(rpcInput);
242 CompletableFuture<RestconfRpcOutput> rpcFuture = service.runRpc(uri,
243 inputNode,
244 request.getRemoteAddr());
245 RestconfRpcOutput restconfRpcOutput;
246 restconfRpcOutput = rpcFuture.get();
247 if (restconfRpcOutput.status() != OK) {
248 return Response.status(restconfRpcOutput.status())
249 .entity(restconfRpcOutput.reason()).build();
250 }
251 ObjectNode node = restconfRpcOutput.output();
252 return ok(node).build();
253 } catch (JsonProcessingException e) {
254 log.error("ERROR: handleRpcRequest", e);
Sean Condon13b16812018-01-25 10:31:49 +0000255 RestconfError error = RestconfError
256 .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.MALFORMED_MESSAGE)
257 .errorMessage(e.getMessage()).errorAppTag("handleRpcRequest").build();
258 return Response.status(BAD_REQUEST)
259 .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
Henry Yuc10f7fc2017-07-26 13:42:08 -0400260 } catch (RestconfException e) {
261 log.error("ERROR: handleRpcRequest: {}", e.getMessage());
262 log.debug("Exception in handleRpcRequest:", e);
Sean Condon13b16812018-01-25 10:31:49 +0000263 return Response.status(e.getResponse().getStatus())
264 .entity(e.toRestconfErrorJson()).build();
Henry Yuc10f7fc2017-07-26 13:42:08 -0400265 } catch (Exception e) {
266 log.error("ERROR: handleRpcRequest ", e);
Sean Condon13b16812018-01-25 10:31:49 +0000267 RestconfError error = RestconfError
268 .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED)
269 .errorMessage(e.getMessage()).errorAppTag("handleRpcRequest").build();
270 return Response.status(INTERNAL_SERVER_ERROR)
271 .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
Henry Yuc10f7fc2017-07-26 13:42:08 -0400272 }
273 }
274
275 /**
Henry Yue20926e2016-08-25 22:58:02 -0400276 * Handles a RESTCONF PUT operation against a data resource. If a new
277 * resource is successfully created, then the HTTP status code "201 Created"
278 * is returned. If an existing resource is modified, then the HTTP
279 * status code "204 No Content" is returned. If the input JSON payload
280 * contains errors, then "400 Bad Request" is returned. If an exception
281 * occurs during the operation, the status code enclosed in
282 * the RestconfException object, such as "500 Internal Server Error",
283 * is returned.
284 *
285 * @param uriString URI of the data resource.
286 * @param stream Input JSON object
287 * @return HTTP response
288 */
289 @PUT
290 @Consumes(MediaType.APPLICATION_JSON)
291 @Produces(MediaType.APPLICATION_JSON)
292 @Path("data/{identifier : .+}")
chengfanc58d4be2016-09-20 10:33:12 +0800293 public Response handlePutRequest(@PathParam("identifier") String uriString,
294 InputStream stream) {
Henry Yue20926e2016-08-25 22:58:02 -0400295 log.debug("handlePutRequest: {}", uriString);
296
Henry Yuc10f7fc2017-07-26 13:42:08 -0400297 URI uri = uriInfo.getRequestUri();
298
Henry Yue20926e2016-08-25 22:58:02 -0400299 try {
300 ObjectNode rootNode = (ObjectNode) mapper().readTree(stream);
301
Henry Yuc10f7fc2017-07-26 13:42:08 -0400302 service.runPutOperationOnDataResource(uri, rootNode);
Henry Yue20926e2016-08-25 22:58:02 -0400303 return Response.created(uriInfo.getRequestUri()).build();
304 } catch (JsonProcessingException e) {
305 log.error("ERROR: handlePutRequest ", e);
Sean Condon13b16812018-01-25 10:31:49 +0000306 RestconfError error = RestconfError
307 .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.MALFORMED_MESSAGE)
308 .errorMessage(e.getMessage()).errorAppTag("handlePutRequest").build();
309 return Response.status(BAD_REQUEST)
310 .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
Henry Yue20926e2016-08-25 22:58:02 -0400311 } catch (RestconfException e) {
312 log.error("ERROR: handlePutRequest: {}", e.getMessage());
313 log.debug("Exception in handlePutRequest:", e);
Sean Condon13b16812018-01-25 10:31:49 +0000314 return Response.status(e.getResponse().getStatus())
315 .entity(e.toRestconfErrorJson()).build();
Henry Yue20926e2016-08-25 22:58:02 -0400316 } catch (IOException ex) {
317 log.error("ERROR: handlePutRequest ", ex);
Sean Condon13b16812018-01-25 10:31:49 +0000318 RestconfError error = RestconfError
319 .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED)
320 .errorMessage(ex.getMessage()).errorAppTag("handlePutRequest").build();
321 return Response.status(INTERNAL_SERVER_ERROR)
322 .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
Henry Yue20926e2016-08-25 22:58:02 -0400323 }
324 }
325
326 /**
327 * Handles the RESTCONF DELETION Operation against a data resource. If the
328 * resource is successfully deleted, the HTTP status code "204 No Content"
329 * is returned in the response. If an exception occurs, then the
330 * HTTP status code enclosed in the RestconfException object is
331 * returned.
332 *
333 * @param uriString URI of the data resource to be deleted.
334 * @return HTTP response
335 */
336 @DELETE
337 @Produces(MediaType.APPLICATION_JSON)
338 @Path("data/{identifier : .+}")
339 public Response handleDeleteRequest(@PathParam("identifier") String uriString) {
Henry Yue20926e2016-08-25 22:58:02 -0400340 log.debug("handleDeleteRequest: {}", uriString);
341
Henry Yuc10f7fc2017-07-26 13:42:08 -0400342 URI uri = uriInfo.getRequestUri();
343
Henry Yue20926e2016-08-25 22:58:02 -0400344 try {
Henry Yuc10f7fc2017-07-26 13:42:08 -0400345 service.runDeleteOperationOnDataResource(uri);
Henry Yue20926e2016-08-25 22:58:02 -0400346 return Response.ok().build();
347 } catch (RestconfException e) {
348 log.error("ERROR: handleDeleteRequest: {}", e.getMessage());
349 log.debug("Exception in handleDeleteRequest:", e);
Sean Condon13b16812018-01-25 10:31:49 +0000350 return Response.status(e.getResponse().getStatus())
351 .entity(e.toRestconfErrorJson()).build();
Henry Yue20926e2016-08-25 22:58:02 -0400352 }
353 }
354
chengfanc58d4be2016-09-20 10:33:12 +0800355 /**
356 * Handles a RESTCONF PATCH operation against a data resource.
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 *
Henry Yuc10f7fc2017-07-26 13:42:08 -0400361 * @param uriString URI of the parent data resource
chengfanc58d4be2016-09-20 10:33:12 +0800362 * @param stream Input JSON object
363 * @return HTTP response
364 */
Dimitrios Mavrommatis89e60d72017-12-02 18:45:35 -0800365 @PATCH
chengfanc58d4be2016-09-20 10:33:12 +0800366 @Consumes(MediaType.APPLICATION_JSON)
367 @Produces(MediaType.APPLICATION_JSON)
368 @Path("data/{identifier : .+}")
369 public Response handlePatchRequest(@PathParam("identifier") String uriString,
370 InputStream stream) {
371
372 log.debug("handlePatchRequest: {}", uriString);
373
Henry Yuc10f7fc2017-07-26 13:42:08 -0400374 URI uri = uriInfo.getRequestUri();
375
chengfanc58d4be2016-09-20 10:33:12 +0800376 try {
377 ObjectNode rootNode = (ObjectNode) mapper().readTree(stream);
378
Henry Yuc10f7fc2017-07-26 13:42:08 -0400379 service.runPatchOperationOnDataResource(uri, rootNode);
chengfanc58d4be2016-09-20 10:33:12 +0800380 return Response.ok().build();
381 } catch (JsonProcessingException e) {
382 log.error("ERROR: handlePatchRequest ", e);
Sean Condon13b16812018-01-25 10:31:49 +0000383 RestconfError error = RestconfError
384 .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.MALFORMED_MESSAGE)
385 .errorMessage(e.getMessage()).errorAppTag("handlePatchRequest").build();
386 return Response.status(BAD_REQUEST)
387 .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
chengfanc58d4be2016-09-20 10:33:12 +0800388 } catch (RestconfException e) {
389 log.error("ERROR: handlePatchRequest: {}", e.getMessage());
390 log.debug("Exception in handlePatchRequest:", e);
Sean Condon13b16812018-01-25 10:31:49 +0000391 return Response.status(e.getResponse().getStatus())
392 .entity(e.toRestconfErrorJson()).build();
chengfanc58d4be2016-09-20 10:33:12 +0800393 } catch (IOException ex) {
394 log.error("ERROR: handlePatchRequest ", ex);
Sean Condon13b16812018-01-25 10:31:49 +0000395 RestconfError error = RestconfError
396 .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED)
397 .errorMessage(ex.getMessage()).errorAppTag("handlePatchRequest").build();
398 return Response.status(INTERNAL_SERVER_ERROR)
399 .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
chengfanc58d4be2016-09-20 10:33:12 +0800400 }
401 }
402
Henry Yuc10f7fc2017-07-26 13:42:08 -0400403
404 /**
405 * Handles a RESTCONF PATCH operation against the entire data store.
406 * If the PATCH request succeeds, a "200 OK" status-line is returned if
407 * there is a message-body, and "204 No Content" is returned if no
408 * response message-body is sent.
409 *
410 * @param stream Input JSON object
411 * @return HTTP response
412 */
Dimitrios Mavrommatis89e60d72017-12-02 18:45:35 -0800413 @PATCH
Henry Yuc10f7fc2017-07-26 13:42:08 -0400414 @Consumes(MediaType.APPLICATION_JSON)
415 @Produces(MediaType.APPLICATION_JSON)
416 @Path("data")
417 public Response handlePatchDatastore(InputStream stream) {
418 log.debug("handlePatchDatastore");
419 return handlePatchRequest(null, stream);
420 }
Henry Yue20926e2016-08-25 22:58:02 -0400421}