[ONOS-6760][ONOS-6761] RESTCONF changes and fixes

* bug fixes for POST and PUT
* added support for PATCH
* added support for RPC

Change-Id: Ib03dd1e92d4e7231851340063eb23e4b3ad582cf
diff --git a/protocols/restconf/server/rpp/BUCK b/protocols/restconf/server/rpp/BUCK
index 6531f88..7eaf628 100644
--- a/protocols/restconf/server/rpp/BUCK
+++ b/protocols/restconf/server/rpp/BUCK
@@ -3,6 +3,7 @@
     '//lib:jersey-client',
     '//lib:jersey-server',
     '//lib:javax.ws.rs-api',
+    '//lib:servlet-api',
     '//utils/rest:onlab-rest',
     '//apps/restconf/api:onos-apps-restconf-api',
 ]
diff --git a/protocols/restconf/server/rpp/src/main/java/org/onosproject/protocol/restconf/server/rpp/RestconfWebResource.java b/protocols/restconf/server/rpp/src/main/java/org/onosproject/protocol/restconf/server/rpp/RestconfWebResource.java
index 4bd2168..1037567 100644
--- a/protocols/restconf/server/rpp/src/main/java/org/onosproject/protocol/restconf/server/rpp/RestconfWebResource.java
+++ b/protocols/restconf/server/rpp/src/main/java/org/onosproject/protocol/restconf/server/rpp/RestconfWebResource.java
@@ -22,9 +22,11 @@
 import org.onosproject.rest.AbstractWebResource;
 import org.onosproject.restconf.api.Patch;
 import org.onosproject.restconf.api.RestconfException;
+import org.onosproject.restconf.api.RestconfRpcOutput;
 import org.onosproject.restconf.api.RestconfService;
 import org.slf4j.Logger;
 
+import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
@@ -39,9 +41,13 @@
 import javax.ws.rs.core.UriInfo;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URI;
+import java.util.concurrent.CompletableFuture;
 
 import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
 import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
+import static javax.ws.rs.core.Response.Status.NOT_FOUND;
+import static javax.ws.rs.core.Response.Status.OK;
 import static org.slf4j.LoggerFactory.getLogger;
 
 
@@ -62,6 +68,8 @@
     @Context
     UriInfo uriInfo;
 
+    private static final String NOT_EXIST = "Requested data resource does not exist";
+
     private final RestconfService service = get(RestconfService.class);
     private final Logger log = getLogger(getClass());
 
@@ -75,14 +83,19 @@
      * @return HTTP response
      */
     @GET
+    @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
     @Path("data/{identifier : .+}")
     public Response handleGetRequest(@PathParam("identifier") String uriString) {
-
         log.debug("handleGetRequest: {}", uriString);
 
+        URI uri = uriInfo.getRequestUri();
+
         try {
-            ObjectNode node = service.runGetOperationOnDataResource(uriString);
+            ObjectNode node = service.runGetOperationOnDataResource(uri);
+            if (node == null) {
+                return Response.status(NOT_FOUND).entity(NOT_EXIST).build();
+            }
             return ok(node).build();
         } catch (RestconfException e) {
             log.error("ERROR: handleGetRequest: {}", e.getMessage());
@@ -102,15 +115,18 @@
      * calls ChunkedOutput.close() to disconnect the session and terminates itself.
      *
      * @param streamId Event stream ID
+     * @param request  RESTCONF client information from which the client IP
+     *                 address is retrieved
      * @return A string data stream over HTTP keep-alive session
      */
     @GET
     @Produces(MediaType.APPLICATION_JSON)
     @Path("streams/{streamId}")
-    public ChunkedOutput<String> handleNotificationRegistration(@PathParam("streamId") String streamId) {
+    public ChunkedOutput<String> handleNotificationRegistration(@PathParam("streamId") String streamId,
+                                                                @Context HttpServletRequest request) {
         final ChunkedOutput<String> output = new ChunkedOutput<>(String.class);
         try {
-            service.subscribeEventStream(streamId, output);
+            service.subscribeEventStream(streamId, request.getRemoteAddr(), output);
         } catch (RestconfException e) {
             log.error("ERROR: handleNotificationRegistration: {}", e.getMessage());
             log.debug("Exception in handleNotificationRegistration:", e);
@@ -125,12 +141,31 @@
     }
 
     /**
+     * Handles a RESTCONF POST operation against the entire data store. If the
+     * operation is successful, HTTP status code "201 Created" is returned
+     * and there is no response message-body. If the data resource already
+     * exists, then the HTTP status code "409 Conflict" is returned.
+     *
+     * @param stream Input JSON object
+     * @return HTTP response
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("data")
+    public Response handlePostDatastore(InputStream stream) {
+
+        log.debug("handlePostDatastore");
+        return handlePostRequest(null, stream);
+    }
+
+    /**
      * Handles a RESTCONF POST operation against a data resource. If the
      * operation is successful, HTTP status code "201 Created" is returned
      * and there is no response message-body. If the data resource already
      * exists, then the HTTP status code "409 Conflict" is returned.
      *
-     * @param uriString URI of the data resource
+     * @param uriString URI of the parent data resource
      * @param stream    Input JSON object
      * @return HTTP response
      */
@@ -140,13 +175,14 @@
     @Path("data/{identifier : .+}")
     public Response handlePostRequest(@PathParam("identifier") String uriString,
                                       InputStream stream) {
-
         log.debug("handlePostRequest: {}", uriString);
 
+        URI uri = uriInfo.getRequestUri();
+
         try {
             ObjectNode rootNode = (ObjectNode) mapper().readTree(stream);
 
-            service.runPostOperationOnDataResource(uriString, rootNode);
+            service.runPostOperationOnDataResource(uri, rootNode);
             return Response.created(uriInfo.getRequestUri()).build();
         } catch (JsonProcessingException e) {
             log.error("ERROR: handlePostRequest ", e);
@@ -162,6 +198,50 @@
     }
 
     /**
+     * Handles a RESTCONF RPC request. This function executes the RPC in
+     * the target application's context and returns the results as a Future.
+     *
+     * @param rpcName  Name of the RPC
+     * @param rpcInput Input parameters
+     * @param request  RESTCONF client information from which the client IP
+     *                 address is retrieved
+     * @return RPC output
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("operations/{rpc : .+}")
+    public Response handleRpcRequest(@PathParam("rpc") String rpcName,
+                                     InputStream rpcInput,
+                                     @Context HttpServletRequest request) {
+        URI uri = uriInfo.getRequestUri();
+        try {
+            ObjectNode inputNode = (ObjectNode) mapper().readTree(rpcInput);
+            CompletableFuture<RestconfRpcOutput> rpcFuture = service.runRpc(uri,
+                                                                            inputNode,
+                                                                            request.getRemoteAddr());
+            RestconfRpcOutput restconfRpcOutput;
+            restconfRpcOutput = rpcFuture.get();
+            if (restconfRpcOutput.status() != OK) {
+                return Response.status(restconfRpcOutput.status())
+                        .entity(restconfRpcOutput.reason()).build();
+            }
+            ObjectNode node = restconfRpcOutput.output();
+            return ok(node).build();
+        } catch (JsonProcessingException e) {
+            log.error("ERROR:  handleRpcRequest", e);
+            return Response.status(BAD_REQUEST).build();
+        } catch (RestconfException e) {
+            log.error("ERROR: handleRpcRequest: {}", e.getMessage());
+            log.debug("Exception in handleRpcRequest:", e);
+            return e.getResponse();
+        } catch (Exception e) {
+            log.error("ERROR: handleRpcRequest ", e);
+            return Response.status(INTERNAL_SERVER_ERROR).build();
+        }
+    }
+
+    /**
      * Handles a RESTCONF PUT operation against a data resource. If a new
      * resource is successfully created, then the HTTP status code "201 Created"
      * is returned. If an existing resource is modified, then the HTTP
@@ -181,13 +261,14 @@
     @Path("data/{identifier : .+}")
     public Response handlePutRequest(@PathParam("identifier") String uriString,
                                      InputStream stream) {
-
         log.debug("handlePutRequest: {}", uriString);
 
+        URI uri = uriInfo.getRequestUri();
+
         try {
             ObjectNode rootNode = (ObjectNode) mapper().readTree(stream);
 
-            service.runPutOperationOnDataResource(uriString, rootNode);
+            service.runPutOperationOnDataResource(uri, rootNode);
             return Response.created(uriInfo.getRequestUri()).build();
         } catch (JsonProcessingException e) {
             log.error("ERROR: handlePutRequest ", e);
@@ -213,14 +294,16 @@
      * @return HTTP response
      */
     @DELETE
+    @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
     @Path("data/{identifier : .+}")
     public Response handleDeleteRequest(@PathParam("identifier") String uriString) {
-
         log.debug("handleDeleteRequest: {}", uriString);
 
+        URI uri = uriInfo.getRequestUri();
+
         try {
-            service.runDeleteOperationOnDataResource(uriString);
+            service.runDeleteOperationOnDataResource(uri);
             return Response.ok().build();
         } catch (RestconfException e) {
             log.error("ERROR: handleDeleteRequest: {}", e.getMessage());
@@ -235,7 +318,7 @@
      * there is a message-body, and "204 No Content" is returned if no
      * response message-body is sent.
      *
-     * @param uriString URI of the data resource
+     * @param uriString URI of the parent data resource
      * @param stream    Input JSON object
      * @return HTTP response
      */
@@ -248,10 +331,12 @@
 
         log.debug("handlePatchRequest: {}", uriString);
 
+        URI uri = uriInfo.getRequestUri();
+
         try {
             ObjectNode rootNode = (ObjectNode) mapper().readTree(stream);
 
-            service.runPatchOperationOnDataResource(uriString, rootNode);
+            service.runPatchOperationOnDataResource(uri, rootNode);
             return Response.ok().build();
         } catch (JsonProcessingException e) {
             log.error("ERROR: handlePatchRequest ", e);
@@ -266,4 +351,22 @@
         }
     }
 
+
+    /**
+     * Handles a RESTCONF PATCH operation against the entire data store.
+     * If the PATCH request succeeds, a "200 OK" status-line is returned if
+     * there is a message-body, and "204 No Content" is returned if no
+     * response message-body is sent.
+     *
+     * @param stream Input JSON object
+     * @return HTTP response
+     */
+    @Patch
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("data")
+    public Response handlePatchDatastore(InputStream stream) {
+        log.debug("handlePatchDatastore");
+        return handlePatchRequest(null, stream);
+    }
 }