Add an ACL application

- change both of the constructors in AclRule.class to be private
- change AclRule.class to be final
- remove useless reference

|URL|Notes|
|-|-|
|GET onos/v1/acl | Lists all existing ACL rules.|
|GET onos/v1/acl/remove/{id} | Removes an existing ACL rule by id|
|GET onos/v1/acl/clear | Clears ACL and reset all|
|POST onos/v1/acl/add | Adds a new ACL rule|

|Key|Value|Notes|
|-|-|-|
|ipProto | string | "TCP" or "UDP" or "ICMP" (ignoring case)|
|srcIp | IPv4 address[/mask] | Either src-ip or dst-ip must be specified.|
|dstIp | IPv4 address[/mask] | Either src-ip or dst-ip must be specified.|
|dstTpPort | number | Valid when nw-proto == "TCP" or "UDP".|
|action | string | "DENY" or "ALLOW" (ignoring case), set to "DENY" if not specified.|

Change-Id: I55170d5f50814eabef43b1bf2ee33af41b5987e4
diff --git a/apps/acl/src/test/java/org/onos/acl/web/AclWebResourceTest.java b/apps/acl/src/test/java/org/onos/acl/web/AclWebResourceTest.java
new file mode 100644
index 0000000..bb7d805
--- /dev/null
+++ b/apps/acl/src/test/java/org/onos/acl/web/AclWebResourceTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ * Originally created by Pengfei Lu, Network and Cloud Computing Laboratory, Dalian University of Technology, China
+ * Advisers: Keqiu Li and Heng Qi
+ * This work is supported by the State Key Program of National Natural Science of China(Grant No. 61432002)
+ * and Prospective Research Project on Future Networks in Jiangsu Future Networks Innovation Institute.
+ *
+ * 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.onos.acl.web;
+
+import com.sun.jersey.api.client.WebResource;
+import org.onos.acl.AclService;
+import org.onos.acl.AclStore;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.rest.BaseResource;
+import org.onos.acl.AclRule;
+import org.onosproject.core.IdGenerator;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.easymock.EasyMock.*;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Test class for ACL application REST resource.
+ */
+public class AclWebResourceTest extends ResourceTest {
+
+    final AclService mockAclService = createMock(AclService.class);
+    final AclStore mockAclStore = createMock(AclStore.class);
+    final List<AclRule> rules = new ArrayList<>();
+
+    @Before
+    public void setUp() {
+        expect(mockAclService.getAclRules()).andReturn(rules).anyTimes();
+        ServiceDirectory testDirectory = new TestServiceDirectory().add(AclService.class, mockAclService)
+                .add(AclStore.class, mockAclStore);
+        BaseResource.setServiceDirectory(testDirectory);
+    }
+
+    @After
+    public void tearDown() {
+        verify(mockAclService);
+    }
+
+    /**
+     * Mock id generator for testing.
+     */
+    private class MockIdGenerator implements IdGenerator {
+        private AtomicLong nextId = new AtomicLong(0);
+
+        @Override
+        public long getNewId() {
+            return nextId.getAndIncrement();
+        }
+    }
+
+    @Test
+    public void testaddRule() throws IOException {
+        WebResource rs = resource();
+        String response;
+        String json;
+        IdGenerator idGenerator = new MockIdGenerator();
+        AclRule.bindIdGenerator(idGenerator);
+
+        replay(mockAclService);
+
+        // input a invalid JSON string that contains neither nw_src and nw_dst
+        json = "{\"ipProto\":\"TCP\",\"dstTpPort\":\"80\"}";
+        response = rs.path("add").post(String.class, json);
+        assertThat(response, containsString("Failed! Either srcIp or dstIp must be assigned."));
+
+        // input a invalid JSON string that doesn't contain CIDR mask bits
+        json = "{\"ipProto\":\"TCP\",\"srcIp\":\"10.0.0.1\",\"dstTpPort\":\"80\",\"action\":\"DENY\"}";
+        response = rs.path("add").post(String.class, json);
+        assertThat(response, containsString("Malformed IPv4 prefix string: 10.0.0.1. " +
+                                                    "Address must take form \"x.x.x.x/y\""));
+
+        // input a invalid JSON string that contains a invalid IP address
+        json = "{\"ipProto\":\"TCP\",\"srcIp\":\"10.0.0.256/32\",\"dstTpPort\":\"80\",\"action\":\"DENY\"}";
+        response = rs.path("add").post(String.class, json);
+        assertThat(response, containsString("Invalid IP address string: 10.0.0.256"));
+
+        // input a invalid JSON string that contains a invalid IP address
+        json = "{\"ipProto\":\"TCP\",\"srcIp\":\"10.0.01/32\",\"dstTpPort\":\"80\",\"action\":\"DENY\"}";
+        response = rs.path("add").post(String.class, json);
+        assertThat(response, containsString("Invalid IP address string: 10.0.01"));
+
+        // input a invalid JSON string that contains a invalid CIDR mask bits
+        json = "{\"ipProto\":\"TCP\",\"srcIp\":\"10.0.0.1/a\",\"dstTpPort\":\"80\",\"action\":\"DENY\"}";
+        response = rs.path("add").post(String.class, json);
+        assertThat(response, containsString("Failed! For input string: \"a\""));
+
+        // input a invalid JSON string that contains a invalid CIDR mask bits
+        json = "{\"ipProto\":\"TCP\",\"srcIp\":\"10.0.0.1/33\",\"dstTpPort\":\"80\",\"action\":\"DENY\"}";
+        response = rs.path("add").post(String.class, json);
+        assertThat(response, containsString("Invalid prefix length 33. The value must be in the interval [0, 32]"));
+
+        // input a invalid JSON string that contains a invalid ipProto value
+        json = "{\"ipProto\":\"ARP\",\"srcIp\":\"10.0.0.1/32\",\"dstTpPort\":\"80\",\"action\":\"DENY\"}";
+        response = rs.path("add").post(String.class, json);
+        assertThat(response, containsString("ipProto must be assigned to TCP, UDP, or ICMP."));
+
+        // input a invalid JSON string that contains a invalid dstTpPort value
+        json = "{\"ipProto\":\"TCP\",\"srcIp\":\"10.0.0.1/32\",\"dstTpPort\":\"a\",\"action\":\"DENY\"}";
+        response = rs.path("add").post(String.class, json);
+        assertThat(response, containsString("dstTpPort must be assigned to a numerical value."));
+
+        // input a invalid JSON string that contains a invalid action value
+        json = "{\"ipProto\":\"TCP\",\"srcIp\":\"10.0.0.1/32\",\"dstTpPort\":\"80\",\"action\":\"PERMIT\"}";
+        response = rs.path("add").post(String.class, json);
+        assertThat(response, containsString("action must be assigned to ALLOW or DENY."));
+    }
+}