Implementation of REST POST API for creating intents
- codec for constraint decode
- codec for intent decode
- POST method for intents
- unit tests for codecs and POST method

Change-Id: Ibc0ef8f99a0c0664710a733985424c77010c49b5
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/IntentsWebResource.java b/web/api/src/main/java/org/onosproject/rest/resources/IntentsWebResource.java
index f49e6be..967d477 100644
--- a/web/api/src/main/java/org/onosproject/rest/resources/IntentsWebResource.java
+++ b/web/api/src/main/java/org/onosproject/rest/resources/IntentsWebResource.java
@@ -15,12 +15,18 @@
  */
 package org.onosproject.rest.resources;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
 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;
@@ -177,4 +183,31 @@
         }
     }
 
+    /**
+     * Creates an intent from a POST of a JSON string and attempts to apply it.
+     *
+     * @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 createIntent(InputStream stream) {
+        URI location;
+        try {
+            IntentService service = get(IntentService.class);
+            ObjectNode root = (ObjectNode) mapper().readTree(stream);
+            Intent intent = codec(Intent.class).decode(root, this);
+            service.submit(intent);
+            location = new URI(Short.toString(intent.appId().id()) + "/"
+                    + Long.toString(intent.id().fingerprint()));
+        } catch (IOException | URISyntaxException ex) {
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+        }
+        return Response
+                .created(location)
+                .build();
+    }
+
 }
diff --git a/web/api/src/test/java/org/onosproject/rest/FlowsResourceTest.java b/web/api/src/test/java/org/onosproject/rest/FlowsResourceTest.java
index 63e9644..192c417 100644
--- a/web/api/src/test/java/org/onosproject/rest/FlowsResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/FlowsResourceTest.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.rest;
 
+import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -107,10 +108,6 @@
     final MockFlowEntry flow5 = new MockFlowEntry(deviceId2, 5);
     final MockFlowEntry flow6 = new MockFlowEntry(deviceId2, 6);
 
-    private static final String FLOW_JSON = "{\"priority\":1,\"isPermanent\":true,"
-            + "\"treatment\":{\"instructions\":[ {\"type\":\"OUTPUT\",\"port\":2}]},"
-            + "\"selector\":{\"criteria\":[ {\"type\":\"ETH_TYPE\",\"ethType\":2054}]}}";
-
     /**
      * Mock class for a flow entry.
      */
@@ -582,11 +579,12 @@
         replay(mockFlowService);
 
         WebResource rs = resource();
-
+        InputStream jsonStream = IntentsResourceTest.class
+                .getResourceAsStream("post-flow.json");
 
         ClientResponse response = rs.path("flows/of:0000000000000001")
                 .type(MediaType.APPLICATION_JSON_TYPE)
-                .post(ClientResponse.class, FLOW_JSON);
+                .post(ClientResponse.class, jsonStream);
         assertThat(response.getStatus(), is(HttpURLConnection.HTTP_CREATED));
         String location = response.getLocation().getPath();
         assertThat(location, Matchers.startsWith("/flows/of:0000000000000001/"));
diff --git a/web/api/src/test/java/org/onosproject/rest/IntentsResourceTest.java b/web/api/src/test/java/org/onosproject/rest/IntentsResourceTest.java
index 08a6ce5..6572b68 100644
--- a/web/api/src/test/java/org/onosproject/rest/IntentsResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/IntentsResourceTest.java
@@ -15,10 +15,15 @@
  */
 package org.onosproject.rest;
 
+import java.io.InputStream;
+import java.net.HttpURLConnection;
 import java.util.Collections;
 import java.util.HashSet;
 
+import javax.ws.rs.core.MediaType;
+
 import org.hamcrest.Description;
+import org.hamcrest.Matchers;
 import org.hamcrest.TypeSafeMatcher;
 import org.junit.After;
 import org.junit.Before;
@@ -42,12 +47,14 @@
 import com.eclipsesource.json.JsonArray;
 import com.eclipsesource.json.JsonObject;
 import com.eclipsesource.json.JsonValue;
+import com.sun.jersey.api.client.ClientResponse;
 import com.sun.jersey.api.client.UniformInterfaceException;
 import com.sun.jersey.api.client.WebResource;
 
 import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.verify;
 import static org.hamcrest.Matchers.containsString;
@@ -358,4 +365,29 @@
                     containsString("returned a response status of"));
         }
     }
+
+    /**
+     * Tests creating an intent with POST.
+     */
+    @Test
+    public void testPost() {
+        expect(mockCoreService.getAppId((short) 2))
+                .andReturn(new DefaultApplicationId(2, "app"));
+        replay(mockCoreService);
+
+        mockIntentService.submit(anyObject());
+        expectLastCall();
+        replay(mockIntentService);
+
+        InputStream jsonStream = IntentsResourceTest.class
+                .getResourceAsStream("post-intent.json");
+        WebResource rs = resource();
+
+        ClientResponse response = rs.path("intents")
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, jsonStream);
+        assertThat(response.getStatus(), is(HttpURLConnection.HTTP_CREATED));
+        String location = response.getLocation().getPath();
+        assertThat(location, Matchers.startsWith("/intents/2/"));
+    }
 }
diff --git a/web/api/src/test/resources/org/onosproject/rest/post-flow.json b/web/api/src/test/resources/org/onosproject/rest/post-flow.json
new file mode 100644
index 0000000..939b447
--- /dev/null
+++ b/web/api/src/test/resources/org/onosproject/rest/post-flow.json
@@ -0,0 +1,20 @@
+{
+  "priority": 1,
+  "isPermanent": true,
+  "treatment": {
+    "instructions": [
+      {
+        "type": "OUTPUT",
+        "port": 2
+      }
+    ]
+  },
+  "selector": {
+    "criteria": [
+      {
+        "type": "ETH_TYPE",
+        "ethType": 2054
+      }
+    ]
+  }
+}
diff --git a/web/api/src/test/resources/org/onosproject/rest/post-intent.json b/web/api/src/test/resources/org/onosproject/rest/post-intent.json
new file mode 100644
index 0000000..4c6c4b8
--- /dev/null
+++ b/web/api/src/test/resources/org/onosproject/rest/post-intent.json
@@ -0,0 +1,38 @@
+{
+  "type": "PointToPointIntent",
+  "appId": 2,
+  "selector": {
+    "criteria": [
+      {
+        "type": "ETH_DST",
+        "mac": "11:22:33:44:55:66"
+      }
+    ]
+  },
+  "treatment": {
+    "instructions": [
+      {
+        "type": "L2MODIFICATION",
+        "subtype": "ETH_SRC",
+        "mac": "22:33:44:55:66:77"
+      }
+    ],
+    "deferred": []
+  },
+  "priority": 55,
+  "constraints": [
+    {
+      "inclusive": false,
+      "types": ["OPTICAL"],
+      "type": "LinkTypeConstraint"
+    }
+  ],
+  "ingressPoint": {
+    "port": "1",
+    "device": "of:0000000000000001"
+  },
+  "egressPoint": {
+    "port": "2",
+    "device": "of:0000000000000007"
+  }
+}