[ONOS-3603] Add getGroupByDeviceIdAndAppCookie method in group REST API

* Add a new method for getting a specific group result
* Add descriptions in swagger doc

Change-Id: I62a476bd2cd774eed157dd3954349eb5aa335db3
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/GroupsWebResource.java b/web/api/src/main/java/org/onosproject/rest/resources/GroupsWebResource.java
index 733fc41..2d917bb 100644
--- a/web/api/src/main/java/org/onosproject/rest/resources/GroupsWebResource.java
+++ b/web/api/src/main/java/org/onosproject/rest/resources/GroupsWebResource.java
@@ -43,6 +43,8 @@
 import java.net.URI;
 import java.net.URISyntaxException;
 
+import static org.onlab.util.Tools.nullIsNotFound;
+
 /**
  * Query and program group rules.
  */
@@ -50,6 +52,7 @@
 @Path("groups")
 public class GroupsWebResource extends AbstractWebResource {
     public static final String DEVICE_INVALID = "Invalid deviceId in group creation request";
+    public static final String GROUP_NOT_FOUND = "Group was not found";
 
     final GroupService groupService = get(GroupService.class);
     final ObjectNode root = mapper().createObjectNode();
@@ -57,8 +60,9 @@
 
     /**
      * Returns all groups of all devices.
-     * @onos.rsModel Groups
+     *
      * @return array of all the groups in the system
+     * @onos.rsModel Groups
      */
     @GET
     @Produces(MediaType.APPLICATION_JSON)
@@ -78,8 +82,8 @@
      * Returns all groups associated with the given device.
      *
      * @param deviceId device identifier
-     * @onos.rsModel Groups
      * @return array of all the groups in the system
+     * @onos.rsModel Groups
      */
     @GET
     @Produces(MediaType.APPLICATION_JSON)
@@ -93,14 +97,37 @@
     }
 
     /**
+     * Returns a group with the given deviceId and appCookie.
+     *
+     * @param deviceId device identifier
+     * @param appCookie group key
+     * @return a group entry in the system
+     * @onos.rsModel Group
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{deviceId}/{appCookie}")
+    public Response getGroupByDeviceIdAndAppCookie(@PathParam("deviceId") String deviceId,
+                                                   @PathParam("appCookie") String appCookie) {
+        final DeviceId deviceIdInstance = DeviceId.deviceId(deviceId);
+        final GroupKey appCookieInstance = new DefaultGroupKey(appCookie.getBytes());
+
+        Group group = nullIsNotFound(groupService.getGroup(deviceIdInstance, appCookieInstance),
+                GROUP_NOT_FOUND);
+
+        groupsNode.add(codec(Group.class).encode(group, this));
+        return ok(root).build();
+    }
+
+    /**
      * Create new group rule. Creates and installs a new group rule for the
      * specified device.
      *
      * @param deviceId device identifier
      * @param stream   group rule JSON
-     * @onos.rsModel GroupsPost
      * @return status of the request - CREATED if the JSON is correct,
      * BAD_REQUEST if the JSON is invalid
+     * @onos.rsModel GroupsPost
      */
     @POST
     @Path("{deviceId}")
diff --git a/web/api/src/main/resources/definitions/Group.json b/web/api/src/main/resources/definitions/Group.json
new file mode 100644
index 0000000..8a689a3
--- /dev/null
+++ b/web/api/src/main/resources/definitions/Group.json
@@ -0,0 +1,146 @@
+{
+  "type": "object",
+  "title": "group",
+  "required": [
+    "id",
+    "state",
+    "life",
+    "packets",
+    "bytes",
+    "referenceCount",
+    "type",
+    "deviceId",
+    "appId",
+    "appCookie",
+    "buckets"
+  ],
+  "properties": {
+    "id": {
+      "type": "string",
+      "description": "group id",
+      "example": "1"
+    },
+    "state": {
+      "type": "string",
+      "description": "state of the group object",
+      "example": "PENDING_ADD"
+    },
+    "life": {
+      "type": "integer",
+      "format": "int64",
+      "description": "number of milliseconds this group has been alive",
+      "example": 69889
+    },
+    "packets": {
+      "type": "integer",
+      "format": "int64",
+      "description": "number of packets processed by this group",
+      "example": 22546
+    },
+    "bytes": {
+      "type": "integer",
+      "format": "int64",
+      "description": "number of bytes processed by this group",
+      "example": 1826226
+    },
+    "referenceCount": {
+      "type": "integer",
+      "format": "int64",
+      "description": "number of flow rules or other groups reference this group",
+      "example": 1826226
+    },
+    "type": {
+      "type": "string",
+      "description": "types of the group",
+      "example": "ALL"
+    },
+    "deviceId": {
+      "type": "string",
+      "description": "device identifier",
+      "example": "of:0000000000000003"
+    },
+    "appId": {
+      "type": "string",
+      "description": "application identifier",
+      "example": "1"
+    },
+    "appCookie": {
+      "type": "string",
+      "description": "application cookie",
+      "example": "1"
+    },
+    "buckets": {
+      "type": "array",
+      "xml": {
+        "name": "buckets",
+        "wrapped": true
+      },
+      "items": {
+        "type": "object",
+        "title": "buckets",
+        "required": [
+          "treatment",
+          "weight",
+          "watchPort",
+          "watchGroup"
+        ],
+        "properties": {
+          "treatment": {
+            "type": "object",
+            "title": "treatment",
+            "required": [
+              "instructions",
+              "deferred"
+            ],
+            "properties": {
+              "instructions": {
+                "type": "array",
+                "title": "treatment",
+                "required": [
+                  "properties",
+                  "port"
+                ],
+                "items": {
+                  "type": "object",
+                  "title": "instructions",
+                  "required": [
+                    "type",
+                    "port"
+                  ],
+                  "properties": {
+                    "type": {
+                      "type": "string",
+                      "description": "instruction type",
+                      "example": "OUTPUT"
+                    },
+                    "port": {
+                      "type": "string",
+                      "description": "port number",
+                      "example": "2"
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "weight": {
+            "type": "integer",
+            "format": "int16",
+            "description": "weight of select group bucket",
+            "example": "1.0"
+          },
+          "watchPort": {
+            "type": "string",
+            "description": "port number used for liveness detection for a failover bucket",
+            "example": "2"
+          },
+          "watchGroup": {
+            "type": "string",
+            "description": "group identifier used for liveness detection for a failover bucket",
+            "example": "1"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/web/api/src/main/resources/definitions/Groups.json b/web/api/src/main/resources/definitions/Groups.json
index 517c564..3f9acca 100644
--- a/web/api/src/main/resources/definitions/Groups.json
+++ b/web/api/src/main/resources/definitions/Groups.json
@@ -23,45 +23,65 @@
           "referenceCount",
           "type",
           "deviceId",
+          "appId",
+          "appCookie",
           "buckets"
         ],
         "properties": {
           "id": {
             "type": "string",
+            "description": "group id",
             "example": "1"
           },
           "state": {
             "type": "string",
+            "description": "state of the group object",
             "example": "PENDING_ADD"
           },
           "life": {
             "type": "integer",
             "format": "int64",
+            "description": "number of milliseconds this group has been alive",
             "example": 69889
           },
           "packets": {
             "type": "integer",
             "format": "int64",
+            "description": "number of packets processed by this group",
             "example": 22546
           },
           "bytes": {
             "type": "integer",
             "format": "int64",
+            "description": "number of bytes processed by this group",
             "example": 1826226
           },
           "referenceCount": {
             "type": "integer",
             "format": "int64",
+            "description": "number of flow rules or other groups reference this group",
             "example": 1826226
           },
           "type": {
             "type": "string",
+            "description": "types of the group",
             "example": "ALL"
           },
           "deviceId": {
             "type": "string",
+            "description": "device identifier",
             "example": "of:0000000000000003"
           },
+          "appId": {
+            "type": "string",
+            "description": "application identifier",
+            "example": "1"
+          },
+          "appCookie": {
+            "type": "string",
+            "description": "application cookie",
+            "example": "1"
+          },
           "buckets": {
             "type": "array",
             "xml": {
@@ -103,16 +123,34 @@
                         "properties": {
                           "type": {
                             "type": "string",
+                            "description": "instruction type",
                             "example": "OUTPUT"
                           },
                           "port": {
                             "type": "string",
+                            "description": "port number",
                             "example": "2"
                           }
                         }
                       }
                     }
                   }
+                },
+                "weight": {
+                  "type": "integer",
+                  "format": "int16",
+                  "description": "weight of select group bucket",
+                  "example": "1.0"
+                },
+                "watchPort": {
+                  "type": "string",
+                  "description": "port number used for liveness detection for a failover bucket",
+                  "example": "2"
+                },
+                "watchGroup": {
+                  "type": "string",
+                  "description": "group identifier used for liveness detection for a failover bucket",
+                  "example": "1"
                 }
               }
             }
diff --git a/web/api/src/test/java/org/onosproject/rest/GroupsResourceTest.java b/web/api/src/test/java/org/onosproject/rest/GroupsResourceTest.java
index 78c9d8f..db322e3 100644
--- a/web/api/src/test/java/org/onosproject/rest/GroupsResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/GroupsResourceTest.java
@@ -49,6 +49,7 @@
 import org.onosproject.net.group.GroupDescription;
 import org.onosproject.net.group.GroupKey;
 import org.onosproject.net.group.GroupService;
+import org.onosproject.rest.resources.CoreWebApplication;
 
 import javax.ws.rs.core.MediaType;
 import java.io.InputStream;
@@ -70,6 +71,7 @@
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.onosproject.net.NetTestTools.APP_ID;
 
 /**
@@ -100,6 +102,10 @@
     final MockGroup group5 = new MockGroup(deviceId3, 5, "555", 5);
     final MockGroup group6 = new MockGroup(deviceId3, 6, "666", 6);
 
+    public GroupsResourceTest() {
+        super(CoreWebApplication.class);
+    }
+
     /**
      * Mock class for a group.
      */
@@ -444,6 +450,42 @@
     }
 
     /**
+     * Test the result of a rest api GET with specifying device id and appcookie.
+     */
+    @Test
+    public void testGroupByDeviceIdAndAppCookie() {
+        setupMockGroups();
+        expect(mockGroupService.getGroup(anyObject(), anyObject()))
+                .andReturn(group5).anyTimes();
+        replay(mockGroupService);
+        final WebResource rs = resource();
+        final String response = rs.path("groups/" + deviceId3 + "/" + "111").get(String.class);
+        final JsonObject result = JsonObject.readFrom(response);
+        assertThat(result, notNullValue());
+
+        assertThat(result.names(), hasSize(1));
+        assertThat(result.names().get(0), is("groups"));
+        final JsonArray jsonFlows = result.get("groups").asArray();
+        assertThat(jsonFlows, notNullValue());
+        assertThat(jsonFlows, hasGroup(group5));
+    }
+
+    /**
+     * Test whether the REST API returns 404 if no entry has been found.
+     */
+    @Test
+    public void testGroupByDeviceIdAndAppCookieNull() {
+        setupMockGroups();
+        expect(mockGroupService.getGroup(anyObject(), anyObject()))
+                .andReturn(null).anyTimes();
+        replay(mockGroupService);
+        final WebResource rs = resource();
+        final ClientResponse response = rs.path("groups/" + deviceId3 + "/" + "222").get(ClientResponse.class);
+
+        assertEquals(404, response.getStatus());
+    }
+
+    /**
      * Tests creating a group with POST.
      */
     @Test