REST API to create flows

Change-Id: I5d001782249c0eab249d7aa857ae465da95b5955
diff --git a/web/api/src/main/java/org/onosproject/rest/exceptions/IllegalArgumentExceptionMapper.java b/web/api/src/main/java/org/onosproject/rest/exceptions/IllegalArgumentExceptionMapper.java
new file mode 100644
index 0000000..6a381f7
--- /dev/null
+++ b/web/api/src/main/java/org/onosproject/rest/exceptions/IllegalArgumentExceptionMapper.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014-2015 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.exceptions;
+
+import javax.ws.rs.core.Response;
+
+/**
+ * Mapper for illegal argument exceptions to the BAD_REQUEST response code.
+ */
+public class IllegalArgumentExceptionMapper extends AbstractMapper<IllegalArgumentException> {
+    @Override
+    protected Response.Status responseStatus() {
+        return Response.Status.BAD_REQUEST;
+    }
+}
+
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/FlowsWebResource.java b/web/api/src/main/java/org/onosproject/rest/resources/FlowsWebResource.java
index 49840c4..9c39268 100644
--- a/web/api/src/main/java/org/onosproject/rest/resources/FlowsWebResource.java
+++ b/web/api/src/main/java/org/onosproject/rest/resources/FlowsWebResource.java
@@ -15,23 +15,30 @@
  */
 package org.onosproject.rest.resources;
 
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import org.onlab.util.ItemNotFoundException;
-import org.onosproject.net.Device;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.flow.FlowEntry;
-import org.onosproject.net.flow.FlowRuleService;
-import org.onosproject.rest.AbstractWebResource;
+import java.io.IOException;
+import java.io.InputStream;
 
+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.MediaType;
 import javax.ws.rs.core.Response;
 
+import org.onlab.util.ItemNotFoundException;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.rest.AbstractWebResource;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
 /**
  * REST resource for interacting with the inventory of flows.
  */
@@ -113,4 +120,27 @@
         }
         return ok(root).build();
     }
+
+    /**
+     * Creates a flow rule from a POST of a JSON string and attempts to apply it.
+     *
+     * @param stream input JSON
+     * @return status of the request - ACCEPTED if the JSON is correct,
+     * BAD_REQUEST if the JSON is invalid
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createFlow(InputStream stream) {
+        try {
+            FlowRuleService service = get(FlowRuleService.class);
+            ObjectNode root = (ObjectNode) mapper().readTree(stream);
+            FlowRule rule = codec(FlowRule.class).decode(root, this);
+            service.applyFlowRules(rule);
+        } catch (IOException ex) {
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+        }
+        return Response.status(Response.Status.ACCEPTED).build();
+    }
+
 }
diff --git a/web/api/src/main/webapp/WEB-INF/web.xml b/web/api/src/main/webapp/WEB-INF/web.xml
index a3e2060..d8759fe 100644
--- a/web/api/src/main/webapp/WEB-INF/web.xml
+++ b/web/api/src/main/webapp/WEB-INF/web.xml
@@ -61,6 +61,7 @@
                 org.onosproject.rest.exceptions.ServerErrorMapper,
                 org.onosproject.rest.exceptions.BadRequestMapper,
                 org.onosproject.rest.exceptions.WebApplicationExceptionMapper,
+                org.onosproject.rest.exceptions.IllegalArgumentExceptionMapper,
                 org.onosproject.rest.resources.JsonBodyWriter,
 
                 org.onosproject.rest.resources.ApplicationsWebResource,
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 0f0dae8..b18340a 100644
--- a/web/api/src/test/java/org/onosproject/rest/FlowsResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/FlowsResourceTest.java
@@ -15,10 +15,13 @@
  */
 package org.onosproject.rest;
 
+import java.net.HttpURLConnection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Set;
 
+import javax.ws.rs.core.MediaType;
+
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
 import org.junit.After;
@@ -30,11 +33,13 @@
 import org.onlab.rest.BaseResource;
 import org.onosproject.codec.CodecService;
 import org.onosproject.codec.impl.CodecManager;
+import org.onosproject.core.CoreService;
 import org.onosproject.core.DefaultGroupId;
 import org.onosproject.core.GroupId;
 import org.onosproject.net.DefaultDevice;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.NetTestTools;
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
@@ -51,12 +56,15 @@
 import com.eclipsesource.json.JsonArray;
 import com.eclipsesource.json.JsonObject;
 import com.google.common.collect.ImmutableSet;
+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.anyShort;
 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;
@@ -72,6 +80,8 @@
  */
 public class FlowsResourceTest extends ResourceTest {
     final FlowRuleService mockFlowService = createMock(FlowRuleService.class);
+    CoreService mockCoreService = createMock(CoreService.class);
+
     final HashMap<DeviceId, Set<FlowEntry>> rules = new HashMap<>();
 
     final DeviceService mockDeviceService = createMock(DeviceService.class);
@@ -245,6 +255,11 @@
         expect(mockDeviceService.getDevices())
                 .andReturn(ImmutableSet.of(device1, device2));
 
+        // Mock Core Service
+        expect(mockCoreService.getAppId(anyShort()))
+                .andReturn(NetTestTools.APP_ID).anyTimes();
+        replay(mockCoreService);
+
         // Register the services needed for the test
         final CodecManager codecService =  new CodecManager();
         codecService.activate();
@@ -252,7 +267,8 @@
                 new TestServiceDirectory()
                         .add(FlowRuleService.class, mockFlowService)
                         .add(DeviceService.class, mockDeviceService)
-                        .add(CodecService.class, codecService);
+                        .add(CodecService.class, codecService)
+                        .add(CoreService.class, mockCoreService);
 
         BaseResource.setServiceDirectory(testDirectory);
     }
@@ -263,6 +279,7 @@
     @After
     public void tearDownTest() {
         verify(mockFlowService);
+        verify(mockCoreService);
     }
 
     /**
@@ -542,4 +559,27 @@
                     containsString("returned a response status of"));
         }
     }
+
+    /**
+     * Tests creating a flow with POST.
+     */
+    @Test
+    public void testPost() {
+        String json = "{\"appId\":2,\"priority\":1,\"isPermanent\":true,"
+        + "\"deviceId\":\"of:0000000000000001\","
+        + "\"treatment\":{\"instructions\":[ {\"type\":\"OUTPUT\",\"port\":2}]},"
+        + "\"selector\":{\"criteria\":[ {\"type\":\"ETH_TYPE\",\"ethType\":2054}]}}";
+
+        mockFlowService.applyFlowRules(anyObject());
+        expectLastCall();
+        replay(mockFlowService);
+
+        WebResource rs = resource();
+
+
+        ClientResponse response = rs.path("flows/")
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, json);
+        assertThat(response.getStatus(), is(HttpURLConnection.HTTP_ACCEPTED));
+    }
 }