[ONOS-4016] Implement Region administration REST API

- Impelent Region management REST API
- Add unit test for Region management REST API
- Add swagger docs for Region management REST API
- Add SCR Component and Service annotation for RegionManager

Change-Id: I042e92ed7144d596659b779a59239afba832ca62
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/RegionsWebResource.java b/web/api/src/main/java/org/onosproject/rest/resources/RegionsWebResource.java
new file mode 100644
index 0000000..dab1995
--- /dev/null
+++ b/web/api/src/main/java/org/onosproject/rest/resources/RegionsWebResource.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.rest.resources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.Sets;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.region.Region;
+import org.onosproject.net.region.RegionAdminService;
+import org.onosproject.net.region.RegionId;
+import org.onosproject.net.region.RegionService;
+import org.onosproject.rest.AbstractWebResource;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Set;
+
+import static org.onlab.util.Tools.nullIsNotFound;
+
+/**
+ * Manages region and device membership.
+ */
+@Path("regions")
+public class RegionsWebResource extends AbstractWebResource {
+    private final RegionService regionService = get(RegionService.class);
+    private final RegionAdminService regionAdminService = get(RegionAdminService.class);
+
+    private static final String REGION_NOT_FOUND = "Region is not found for ";
+    private static final String REGION_INVALID = "Invalid regionId in region update request";
+    private static final String DEVICE_IDS_INVALID = "Invalid device identifiers";
+
+    /**
+     * Returns set of all regions.
+     *
+     * @return 200 OK
+     * @onos.rsModel Regions
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getRegions() {
+        final Iterable<Region> regions = regionService.getRegions();
+        return ok(encodeArray(Region.class, "regions", regions)).build();
+    }
+
+    /**
+     * Returns the region with the specified identifier.
+     *
+     * @param regionId region identifier
+     * @return 200 OK, 404 not found
+     * @onos.rsModel Region
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{regionId}")
+    public Response getRegionById(@PathParam("regionId") String regionId) {
+        final RegionId rid = RegionId.regionId(regionId);
+        final Region region = nullIsNotFound(regionService.getRegion(rid),
+                REGION_NOT_FOUND + rid.toString());
+        return ok(codec(Region.class).encode(region, this)).build();
+    }
+
+    /**
+     * Returns the set of devices that belong to the specified region.
+     *
+     * @param regionId region identifier
+     * @return 200 OK
+     * @onos.rsModel RegionDeviceIds
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{regionId}/devices")
+    public Response getRegionDevices(@PathParam("regionId") String regionId) {
+        final RegionId rid = RegionId.regionId(regionId);
+        final Iterable<DeviceId> deviceIds = regionService.getRegionDevices(rid);
+        final ObjectNode root = mapper().createObjectNode();
+        final ArrayNode deviceIdsNode = root.putArray("deviceIds");
+        deviceIds.forEach(did -> deviceIdsNode.add(did.toString()));
+        return ok(root).build();
+    }
+
+    /**
+     * Creates a new region using the supplied JSON input stream.
+     *
+     * @param stream region JSON stream
+     * @return status of the request - CREATED if the JSON is correct,
+     * BAD_REQUEST if the JSON is invalid
+     * @onos.rsModel RegionPost
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createRegion(InputStream stream) {
+        URI location;
+        try {
+            ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
+            final Region region = codec(Region.class).decode(jsonTree, this);
+            final Region resultRegion = regionAdminService.createRegion(region.id(),
+                                region.name(), region.type(), region.masters());
+            location = new URI(resultRegion.id().id());
+        } catch (IOException | URISyntaxException e) {
+            throw new IllegalArgumentException(e);
+        }
+
+        return Response.created(location).build();
+    }
+
+    /**
+     * Updates the specified region using the supplied JSON input stream.
+     *
+     * @param regionId region identifier
+     * @param stream region JSON stream
+     * @return status of the request - UPDATED if the JSON is correct,
+     * BAD_REQUEST if the JSON is invalid
+     * @onos.rsModel RegionPost
+     */
+    @PUT
+    @Path("{regionId}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response updateRegion(@PathParam("regionId") String regionId,
+                                 InputStream stream) {
+        try {
+            ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
+            JsonNode specifiedRegionId = jsonTree.get("id");
+
+            if (specifiedRegionId != null &&
+                    !specifiedRegionId.asText().equals(regionId)) {
+                throw new IllegalArgumentException(REGION_INVALID);
+            }
+
+            final Region region = codec(Region.class).decode(jsonTree, this);
+            regionAdminService.updateRegion(region.id(),
+                                region.name(), region.type(), region.masters());
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+
+        return Response.ok().build();
+    }
+
+    /**
+     * Removes the specified region using the given region identifier.
+     *
+     * @param regionId region identifier
+     * @return 200 OK, 404 not found
+     */
+    @DELETE
+    @Path("{regionId}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response removeRegion(@PathParam("regionId") String regionId) {
+        final RegionId rid = RegionId.regionId(regionId);
+        regionAdminService.removeRegion(rid);
+        return Response.ok().build();
+    }
+
+    /**
+     * Adds the specified collection of devices to the region.
+     *
+     * @param regionId region identifier
+     * @param stream deviceIds JSON stream
+     * @return status of the request - CREATED if the JSON is correct,
+     * BAD_REQUEST if the JSON is invalid
+     * @onos.rsModel RegionDeviceIds
+     */
+    @POST
+    @Path("{regionId}/devices")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response addDevices(@PathParam("regionId") String regionId,
+                               InputStream stream) {
+        final RegionId rid = RegionId.regionId(regionId);
+
+        URI location;
+        try {
+            regionAdminService.addDevices(rid, extractDeviceIds(stream));
+            location = new URI(rid.id());
+        } catch (IOException | URISyntaxException e) {
+            throw new IllegalArgumentException(e);
+        }
+
+        return Response.created(location).build();
+    }
+
+    /**
+     * Removes the specified collection of devices from the region.
+     *
+     * @param regionId region identifier
+     * @param stream deviceIds JSON stream
+     * @return 200 OK, 404 not found
+     * @onos.rsModel RegionDeviceIds
+     */
+    @DELETE
+    @Path("{regionId}/devices")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response removeDevices(@PathParam("regionId") String regionId,
+                                  InputStream stream) {
+        final RegionId rid = RegionId.regionId(regionId);
+
+        try {
+            regionAdminService.removeDevices(rid, extractDeviceIds(stream));
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+
+        return Response.ok().build();
+    }
+
+    /**
+     * Extracts device ids from a given JSON string.
+     *
+     * @param stream deviceIds JSON stream
+     * @return a set of device identifiers
+     * @throws IOException
+     */
+    private Set<DeviceId> extractDeviceIds(InputStream stream) throws IOException {
+        ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
+        JsonNode deviceIdsJson = jsonTree.get("deviceIds");
+
+        if (deviceIdsJson == null || deviceIdsJson.size() == 0) {
+            throw new IllegalArgumentException(DEVICE_IDS_INVALID);
+        }
+
+        Set<DeviceId> deviceIds = Sets.newHashSet();
+        deviceIdsJson.forEach(did -> deviceIds.add(DeviceId.deviceId(did.asText())));
+
+        return deviceIds;
+    }
+}