Add POST method for the Host REST API and fix minor documentation errors

Change-Id: I598ace5a94f83f010fdde1b3ad43b90f762a9d33
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/HostsWebResource.java b/web/api/src/main/java/org/onosproject/rest/resources/HostsWebResource.java
index 7b24929..95afe99 100644
--- a/web/api/src/main/java/org/onosproject/rest/resources/HostsWebResource.java
+++ b/web/api/src/main/java/org/onosproject/rest/resources/HostsWebResource.java
@@ -15,17 +15,42 @@
  */
 package org.onosproject.rest.resources;
 
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
 import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostProvider;
+import org.onosproject.net.host.HostProviderRegistry;
+import org.onosproject.net.host.HostProviderService;
 import org.onosproject.net.host.HostService;
+import org.onosproject.net.provider.ProviderId;
 import org.onosproject.rest.AbstractWebResource;
 
+import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
+import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
 
 import static org.onlab.util.Tools.nullIsNotFound;
 import static org.onosproject.net.HostId.hostId;
@@ -36,6 +61,8 @@
 @Path("hosts")
 public class HostsWebResource extends AbstractWebResource {
 
+    @Context
+    UriInfo uriInfo;
     public static final String HOST_NOT_FOUND = "Host is not found";
 
     @GET
@@ -51,7 +78,7 @@
     @Path("{id}")
     public Response getHostById(@PathParam("id") String id) {
         final Host host = nullIsNotFound(get(HostService.class).getHost(hostId(id)),
-                HOST_NOT_FOUND);
+                                         HOST_NOT_FOUND);
         final ObjectNode root = codec(Host.class).encode(host, this);
         return ok(root).build();
     }
@@ -62,9 +89,121 @@
     public Response getHostByMacAndVlan(@PathParam("mac") String mac,
                                         @PathParam("vlan") String vlan) {
         final Host host = nullIsNotFound(get(HostService.class).getHost(hostId(mac + "/" + vlan)),
-                HOST_NOT_FOUND);
+                                         HOST_NOT_FOUND);
         final ObjectNode root = codec(Host.class).encode(host, this);
         return ok(root).build();
     }
+
+    /**
+     * Creates a new host based on JSON input and adds it to the current
+     * host inventory.
+     *
+     * @param stream input JSON
+     * @return status of the request - CREATED if the JSON is correct,
+     * BAD_REQUEST if the JSON is invalid
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createAndAddHost(InputStream stream) {
+        URI location;
+        try {
+            // Parse the input stream
+            ObjectNode root = (ObjectNode) mapper().readTree(stream);
+
+            HostProviderRegistry hostProviderRegistry = get(HostProviderRegistry.class);
+            InternalHostProvider hostProvider = new InternalHostProvider(hostProviderRegistry);
+            hostProvider.register();
+            HostId hostId = hostProvider.parseHost(root);
+
+            UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                    .path("hosts")
+                    .path(hostId.mac().toString())
+                    .path(hostId.vlanId().toString());
+            location = locationBuilder.build();
+            hostProvider.unregister();
+
+        } catch (IOException ex) {
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+        }
+        return Response
+                .created(location)
+                .build();
+    }
+
+    /**
+     * Produces annotations from specified JsonNode. Copied from the ConfigProvider
+     * class for use in the POST method.
+     *
+     * @param node node to be annotated
+     * @return SparseAnnotations object with information about node
+     */
+    private SparseAnnotations annotations(JsonNode node) {
+        if (node == null) {
+            return DefaultAnnotations.EMPTY;
+        }
+
+        DefaultAnnotations.Builder builder = DefaultAnnotations.builder();
+        Iterator<String> it = node.fieldNames();
+        while (it.hasNext()) {
+            String k = it.next();
+            builder.set(k, node.get(k).asText());
+        }
+        return builder.build();
+    }
+
+    private final class InternalHostProvider implements HostProvider {
+        private final ProviderId providerId =
+                new ProviderId("host", "org.onosproject.rest", true);
+        private HostProviderRegistry hostProviderRegistry;
+        private HostProviderService hostProviderService;
+
+        public void triggerProbe(Host host) {
+            // No need to implement since we don't need to check if the host exists
+        }
+
+        private InternalHostProvider(HostProviderRegistry hostProviderRegistry) {
+            this.hostProviderRegistry = hostProviderRegistry;
+        }
+
+        private void register() {
+            this.hostProviderService = hostProviderRegistry.register(this);
+        }
+
+        private void unregister() {
+            hostProviderRegistry.unregister(this);
+        }
+
+        public ProviderId id() {
+            return providerId;
+        }
+
+        /**
+         * Creates and adds new host based on given data and returns its host ID.
+         * @param node JsonNode containing host information
+         * @return host ID of new host created
+         */
+        private HostId parseHost(JsonNode node) {
+            MacAddress mac = MacAddress.valueOf(node.get("mac").asText());
+            VlanId vlanId = VlanId.vlanId(((short) node.get("vlan").asInt((VlanId.UNTAGGED))));
+            JsonNode locationNode = node.get("location");
+            String deviceAndPort = locationNode.get("elementId").asText() + "/" +
+                    locationNode.get("port").asText();
+            HostLocation hostLocation = new HostLocation(ConnectPoint.deviceConnectPoint(deviceAndPort), 0);
+
+            Iterator<JsonNode> ipStrings = node.get("ipAddresses").elements();
+            Set<IpAddress> ips = new HashSet<>();
+            while (ipStrings.hasNext()) {
+                ips.add(IpAddress.valueOf(ipStrings.next().asText()));
+            }
+            SparseAnnotations annotations = annotations(node);
+            // Update host inventory
+
+            HostId hostId = HostId.hostId(mac, vlanId);
+            DefaultHostDescription desc = new DefaultHostDescription(mac, vlanId, hostLocation, ips, annotations);
+            hostProviderService.hostDetected(hostId, desc);
+            return hostId;
+        }
+    }
 }