[ONOS-4530] Allow to specify appId when insert FlowRule through REST

- Augment FlowRuleCodec to encode FlowRule
- Add unit test for encode method of FlowRuleCodec
- Add getFlowByAppId and removeFlowByAppId methods in FlowsWebResource
- Add more unit tests for FlowWebResource
- Add FlowRules.json swagger doc
- Rename Flows.json to FlowEntries.json, correct FlowEntries.json

Change-Id: Ic3ec390c13a349e51ae4208adbc478564b6724ba
diff --git a/web/api/src/test/java/org/onosproject/rest/resources/FlowsResourceTest.java b/web/api/src/test/java/org/onosproject/rest/resources/FlowsResourceTest.java
index 4aede15..4687e44 100644
--- a/web/api/src/test/java/org/onosproject/rest/resources/FlowsResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/resources/FlowsResourceTest.java
@@ -19,6 +19,7 @@
 import com.eclipsesource.json.JsonArray;
 import com.eclipsesource.json.JsonObject;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import org.hamcrest.Description;
 import org.hamcrest.Matchers;
 import org.hamcrest.TypeSafeMatcher;
@@ -29,6 +30,7 @@
 import org.onlab.osgi.TestServiceDirectory;
 import org.onlab.packet.MacAddress;
 import org.onlab.rest.BaseResource;
+import org.onosproject.app.ApplicationService;
 import org.onosproject.codec.CodecService;
 import org.onosproject.codec.impl.CodecManager;
 import org.onosproject.codec.impl.FlowRuleCodec;
@@ -65,6 +67,7 @@
 
 import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.anyShort;
+import static org.easymock.EasyMock.anyString;
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.expectLastCall;
@@ -98,6 +101,8 @@
     final Device device2 = new DefaultDevice(null, deviceId2, Device.Type.OTHER,
             "", "", "", "", null);
 
+    final ApplicationService mockApplicationService = createMock(ApplicationService.class);
+
     final MockFlowEntry flow1 = new MockFlowEntry(deviceId1, 1);
     final MockFlowEntry flow2 = new MockFlowEntry(deviceId1, 2);
 
@@ -107,6 +112,14 @@
     final MockFlowEntry flow5 = new MockFlowEntry(deviceId2, 5);
     final MockFlowEntry flow6 = new MockFlowEntry(deviceId2, 6);
 
+    final MockFlowRule flowRule1 = new MockFlowRule(deviceId1, 1);
+    final MockFlowRule flowRule2 = new MockFlowRule(deviceId1, 2);
+
+    final MockFlowRule flowRule3 = new MockFlowRule(deviceId2, 3);
+    final MockFlowRule flowRule4 = new MockFlowRule(deviceId2, 4);
+
+    final Set<FlowRule> flowRules = Sets.newHashSet();
+
     /**
      * Mock class for a flow entry.
      */
@@ -219,6 +232,83 @@
     }
 
     /**
+     * Mock class for a flow rule.
+     */
+    private static class MockFlowRule implements FlowRule {
+
+        final DeviceId deviceId;
+        final long baseValue;
+        TrafficTreatment treatment;
+        TrafficSelector selector;
+
+        public MockFlowRule(DeviceId deviceId, long id) {
+            this.deviceId = deviceId;
+            this.baseValue = id * 100;
+        }
+
+        @Override
+        public FlowId id() {
+            final long id = baseValue + 55;
+            return FlowId.valueOf(id);
+        }
+
+        @Override
+        public short appId() {
+            return 4;
+        }
+
+        @Override
+        public GroupId groupId() {
+            return new DefaultGroupId(3);
+        }
+
+        @Override
+        public int priority() {
+            return 0;
+        }
+
+        @Override
+        public DeviceId deviceId() {
+            return deviceId;
+        }
+
+        @Override
+        public TrafficSelector selector() {
+            return selector;
+        }
+
+        @Override
+        public TrafficTreatment treatment() {
+            return treatment;
+        }
+
+        @Override
+        public int timeout() {
+            return (int) (baseValue + 77);
+        }
+
+        @Override
+        public boolean isPermanent() {
+            return false;
+        }
+
+        @Override
+        public int tableId() {
+            return 0;
+        }
+
+        @Override
+        public boolean exactMatch(FlowRule rule) {
+            return false;
+        }
+
+        @Override
+        public FlowRuleExtPayLoad payLoad() {
+            return null;
+        }
+    }
+
+    /**
      * Populates some flows used as testing data.
      */
     private void setupMockFlows() {
@@ -249,6 +339,26 @@
     }
 
     /**
+     * Populates some flow rules used as testing data.
+     */
+    private void setupMockFlowRules() {
+        flowRule2.treatment = DefaultTrafficTreatment.builder()
+                .setEthDst(MacAddress.BROADCAST)
+                .build();
+        flowRule2.selector = DefaultTrafficSelector.builder()
+                .matchEthType((short) 3)
+                .matchIPProtocol((byte) 9)
+                .build();
+        flowRule4.treatment = DefaultTrafficTreatment.builder()
+                .build();
+
+        flowRules.add(flowRule1);
+        flowRules.add(flowRule2);
+        flowRules.add(flowRule3);
+        flowRules.add(flowRule4);
+    }
+
+    /**
      * Sets up the global values for all the tests.
      */
     @Before
@@ -264,6 +374,8 @@
         // Mock Core Service
         expect(mockCoreService.getAppId(anyShort()))
                 .andReturn(NetTestTools.APP_ID).anyTimes();
+        expect(mockCoreService.getAppId(anyString()))
+                .andReturn(NetTestTools.APP_ID).anyTimes();
         expect(mockCoreService.registerApplication(FlowRuleCodec.REST_APP_ID))
                 .andReturn(APP_ID).anyTimes();
         replay(mockCoreService);
@@ -276,7 +388,8 @@
                         .add(FlowRuleService.class, mockFlowService)
                         .add(DeviceService.class, mockDeviceService)
                         .add(CodecService.class, codecService)
-                        .add(CoreService.class, mockCoreService);
+                        .add(CoreService.class, mockCoreService)
+                        .add(ApplicationService.class, mockApplicationService);
 
         BaseResource.setServiceDirectory(testDirectory);
     }
@@ -294,12 +407,12 @@
      * Hamcrest matcher to check that a flow representation in JSON matches
      * the actual flow entry.
      */
-    public static class FlowJsonMatcher extends TypeSafeMatcher<JsonObject> {
+    public static class FlowEntryJsonMatcher extends TypeSafeMatcher<JsonObject> {
         private final FlowEntry flow;
         private final String expectedAppId;
         private String reason = "";
 
-        public FlowJsonMatcher(FlowEntry flowValue, String expectedAppIdValue) {
+        public FlowEntryJsonMatcher(FlowEntry flowValue, String expectedAppIdValue) {
             flow = flowValue;
             expectedAppId = expectedAppIdValue;
         }
@@ -398,19 +511,19 @@
      * @param flow flow object we are looking for
      * @return matcher
      */
-    private static FlowJsonMatcher matchesFlow(FlowEntry flow, String expectedAppName) {
-        return new FlowJsonMatcher(flow, expectedAppName);
+    private static FlowEntryJsonMatcher matchesFlow(FlowEntry flow, String expectedAppName) {
+        return new FlowEntryJsonMatcher(flow, expectedAppName);
     }
 
     /**
      * Hamcrest matcher to check that a flow is represented properly in a JSON
      * array of flows.
      */
-    public static class FlowJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
+    public static class FlowEntryJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
         private final FlowEntry flow;
         private String reason = "";
 
-        public FlowJsonArrayMatcher(FlowEntry flowValue) {
+        public FlowEntryJsonArrayMatcher(FlowEntry flowValue) {
             flow = flowValue;
         }
 
@@ -452,8 +565,174 @@
      * @param flow flow object we are looking for
      * @return matcher
      */
-    private static FlowJsonArrayMatcher hasFlow(FlowEntry flow) {
-        return new FlowJsonArrayMatcher(flow);
+    private static FlowEntryJsonArrayMatcher hasFlow(FlowEntry flow) {
+        return new FlowEntryJsonArrayMatcher(flow);
+    }
+
+    /**
+     * Hamcrest matcher to check that a flow representation in JSON matches
+     * the actual flow rule.
+     */
+    public static class FlowRuleJsonMatcher extends TypeSafeMatcher<JsonObject> {
+        private final FlowRule flow;
+        private final String expectedAppId;
+        private String reason = "";
+
+        public FlowRuleJsonMatcher(FlowRule flowValue, String expectedAppIdValue) {
+            flow = flowValue;
+            expectedAppId = expectedAppIdValue;
+        }
+
+        @Override
+        public boolean matchesSafely(JsonObject jsonFlow) {
+            // check id
+            final String jsonId = jsonFlow.get("id").asString();
+            final String flowId = Long.toString(flow.id().value());
+            if (!jsonId.equals(flowId)) {
+                reason = "id " + flow.id().toString();
+                return false;
+            }
+
+            // check application id
+            final String jsonAppId = jsonFlow.get("appId").asString();
+            if (!jsonAppId.equals(expectedAppId)) {
+                reason = "appId " + Short.toString(flow.appId());
+                return false;
+            }
+
+            // check device id
+            final String jsonDeviceId = jsonFlow.get("deviceId").asString();
+            if (!jsonDeviceId.equals(flow.deviceId().toString())) {
+                reason = "deviceId " + flow.deviceId();
+                return false;
+            }
+
+            // check treatment and instructions array
+            if (flow.treatment() != null) {
+                final JsonObject jsonTreatment = jsonFlow.get("treatment").asObject();
+                final JsonArray jsonInstructions = jsonTreatment.get("instructions").asArray();
+                if (flow.treatment().immediate().size() != jsonInstructions.size()) {
+                    reason = "instructions array size of " +
+                            Integer.toString(flow.treatment().immediate().size());
+                    return false;
+                }
+                for (final Instruction instruction : flow.treatment().immediate()) {
+                    boolean instructionFound = false;
+                    for (int instructionIndex = 0; instructionIndex < jsonInstructions.size(); instructionIndex++) {
+                        final String jsonType =
+                                jsonInstructions.get(instructionIndex)
+                                        .asObject().get("type").asString();
+                        final String instructionType = instruction.type().name();
+                        if (jsonType.equals(instructionType)) {
+                            instructionFound = true;
+                        }
+                    }
+                    if (!instructionFound) {
+                        reason = "instruction " + instruction.toString();
+                        return false;
+                    }
+                }
+            }
+
+            // check selector and criteria array
+            if (flow.selector() != null) {
+                final JsonObject jsonTreatment = jsonFlow.get("selector").asObject();
+                final JsonArray jsonCriteria = jsonTreatment.get("criteria").asArray();
+                if (flow.selector().criteria().size() != jsonCriteria.size()) {
+                    reason = "criteria array size of " +
+                            Integer.toString(flow.selector().criteria().size());
+                    return false;
+                }
+                for (final Criterion criterion : flow.selector().criteria()) {
+                    boolean criterionFound = false;
+
+                    for (int criterionIndex = 0; criterionIndex < jsonCriteria.size(); criterionIndex++) {
+                        final String jsonType =
+                                jsonCriteria.get(criterionIndex)
+                                        .asObject().get("type").asString();
+                        final String criterionType = criterion.type().name();
+                        if (jsonType.equals(criterionType)) {
+                            criterionFound = true;
+                        }
+                    }
+                    if (!criterionFound) {
+                        reason = "criterion " + criterion.toString();
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(reason);
+        }
+    }
+
+    /**
+     * Factory to allocate a flow matcher.
+     *
+     * @param flow flow rule object we are looking for
+     * @return matcher
+     */
+    private static FlowRuleJsonMatcher matchesFlowRule(FlowRule flow, String expectedAppName) {
+        return new FlowRuleJsonMatcher(flow, expectedAppName);
+    }
+
+    /**
+     * Hamcrest matcher to check that a flow is represented properly in a JSON
+     * array of flow rules.
+     */
+    public static class FlowRuleJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
+        private final FlowRule flow;
+        private String reason = "";
+
+        public FlowRuleJsonArrayMatcher(FlowRule flowValue) {
+            flow = flowValue;
+        }
+
+        @Override
+        public boolean matchesSafely(JsonArray json) {
+            boolean flowFound = false;
+
+            for (int jsonFlowIndex = 0; jsonFlowIndex < json.size();
+                 jsonFlowIndex++) {
+
+                final JsonObject jsonFlow = json.get(jsonFlowIndex).asObject();
+
+                final String flowId = Long.toString(flow.id().value());
+                final String jsonFlowId = jsonFlow.get("id").asString();
+                if (jsonFlowId.equals(flowId)) {
+                    flowFound = true;
+
+                    //  We found the correct flow, check attribute values
+                    assertThat(jsonFlow, matchesFlowRule(flow, APP_ID.name()));
+                }
+            }
+            if (!flowFound) {
+                reason = "Flow with id " + flow.id().toString() + " not found";
+                return false;
+            } else {
+                return true;
+            }
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(reason);
+        }
+    }
+
+    /**
+     * Factory to allocate a flow array matcher.
+     *
+     * @param flow flow rule object we are looking for
+     * @return matcher
+     */
+    private static FlowRuleJsonArrayMatcher hasFlowRule(FlowRule flow) {
+        return new FlowRuleJsonArrayMatcher(flow);
     }
 
     /**
@@ -572,7 +851,7 @@
      * Tests creating a flow with POST.
      */
     @Test
-    public void testPost() {
+    public void testPostWithoutAppId() {
         mockFlowService.applyFlowRules(anyObject());
         expectLastCall();
         replay(mockFlowService);
@@ -590,6 +869,28 @@
     }
 
     /**
+     * Tests creating a flow with POST while specifying application identifier.
+     */
+    @Test
+    public void testPostWithAppId() {
+        mockFlowService.applyFlowRules(anyObject());
+        expectLastCall();
+        replay(mockFlowService);
+
+        WebTarget wt = target();
+        InputStream jsonStream = FlowsResourceTest.class
+                .getResourceAsStream("post-flow.json");
+
+        Response response = wt.path("flows/of:0000000000000001")
+                .queryParam("appId", "org.onosproject.rest")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .post(Entity.json(jsonStream));
+        assertThat(response.getStatus(), is(HttpURLConnection.HTTP_CREATED));
+        String location = response.getLocation().getPath();
+        assertThat(location, Matchers.startsWith("/flows/of:0000000000000001/"));
+    }
+
+    /**
      * Tests deleting a flow.
      */
     @Test
@@ -609,4 +910,55 @@
         assertThat(deleteResponse.getStatus(),
                 is(HttpURLConnection.HTTP_NO_CONTENT));
     }
+
+    /**
+     * Tests the result of a rest api GET for an application.
+     */
+    @Test
+    public void testGetFlowByAppId() {
+        setupMockFlowRules();
+
+        expect(mockApplicationService.getId(anyObject())).andReturn(APP_ID).anyTimes();
+        replay(mockApplicationService);
+
+        expect(mockFlowService.getFlowRulesById(APP_ID)).andReturn(flowRules).anyTimes();
+        replay(mockFlowService);
+
+        final WebTarget wt = target();
+        final String response = wt.path("flows/application/1").request().get(String.class);
+        final JsonObject result = Json.parse(response).asObject();
+        assertThat(result, notNullValue());
+
+        assertThat(result.names(), hasSize(1));
+        assertThat(result.names().get(0), is("flows"));
+        final JsonArray jsonFlows = result.get("flows").asArray();
+        assertThat(jsonFlows, notNullValue());
+        assertThat(jsonFlows, hasFlowRule(flowRule1));
+        assertThat(jsonFlows, hasFlowRule(flowRule2));
+        assertThat(jsonFlows, hasFlowRule(flowRule3));
+        assertThat(jsonFlows, hasFlowRule(flowRule4));
+    }
+
+    /**
+     * Tests the result of a rest api DELETE for an application.
+     */
+    @Test
+    public void testRemoveFlowByAppId() {
+        expect(mockApplicationService.getId(anyObject())).andReturn(APP_ID).anyTimes();
+        replay(mockApplicationService);
+
+        mockFlowService.removeFlowRulesById(APP_ID);
+        expectLastCall();
+        replay(mockFlowService);
+
+        WebTarget wt = target();
+
+        String location = "/flows/application/1";
+
+        Response deleteResponse = wt.path(location)
+                .request()
+                .delete();
+        assertThat(deleteResponse.getStatus(),
+                is(HttpURLConnection.HTTP_NO_CONTENT));
+    }
 }