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/pom.xml b/apps/acl/pom.xml
new file mode 100644
index 0000000..e2c1454
--- /dev/null
+++ b/apps/acl/pom.xml
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onosproject</groupId>
+        <artifactId>onos-apps</artifactId>
+        <version>1.3.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>onos-app-acl</artifactId>
+    <version>1.3.0-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <description>ONOS ACL application</description>
+    <url>http://onosproject.org</url>
+
+    <properties>
+        <onos.version>1.3.0-SNAPSHOT</onos.version>
+        <onos.app.name>org.onosproject.acl</onos.app.name>
+        <onos.app.origin>Pengfei Lu</onos.app.origin>
+        <web.context>/onos/v1/acl</web.context>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>18.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>jsr311-api</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-junit</artifactId>
+            <version>${onos.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-rest</artifactId>
+            <version>${onos.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-core-serializers</artifactId>
+            <version>${onos.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.sun.jersey.jersey-test-framework</groupId>
+            <artifactId>jersey-test-framework-core</artifactId>
+            <version>1.19</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <version>3.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.sun.jersey.jersey-test-framework</groupId>
+            <artifactId>jersey-test-framework-grizzly2</artifactId>
+            <version>1.19</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-misc</artifactId>
+            <version>${onos.version}</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <version>2.5.3</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <_wab>src/main/webapp/</_wab>
+                        <Bundle-SymbolicName>
+                            ${project.groupId}.${project.artifactId}
+                        </Bundle-SymbolicName>
+                        <Import-Package>
+                            org.slf4j,
+                            org.osgi.framework,
+                            javax.ws.rs,javax.ws.rs.core,
+                            com.fasterxml.jackson*,
+                            com.sun.jersey.api.core,
+                            com.sun.jersey.spi.container.servlet,
+                            com.sun.jersey.server.impl.container.servlet,
+                            org.onlab.packet.*,
+                            org.onlab.rest.*,
+                            org.onosproject.*,
+                            org.onlab.util.*,
+                            com.google.common.*;
+                        </Import-Package>
+                        <Web-ContextPath>${web.context}</Web-ContextPath>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>2.5.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+                <version>1.20.0</version>
+                <executions>
+                    <execution>
+                        <id>generate-scr-srcdescriptor</id>
+                        <goals>
+                            <goal>scr</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <supportedProjectTypes>
+                        <supportedProjectType>bundle</supportedProjectType>
+                        <supportedProjectType>war</supportedProjectType>
+                    </supportedProjectTypes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.onosproject</groupId>
+                <artifactId>onos-maven-plugin</artifactId>
+                <version>1.4-SNAPSHOT</version>
+                <executions>
+                    <execution>
+                        <id>cfg</id>
+                        <phase>generate-resources</phase>
+                        <goals>
+                            <goal>cfg</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>app</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>app</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/apps/acl/src/main/java/org/onos/acl/AclRule.java b/apps/acl/src/main/java/org/onos/acl/AclRule.java
new file mode 100644
index 0000000..6ca0105
--- /dev/null
+++ b/apps/acl/src/main/java/org/onos/acl/AclRule.java
@@ -0,0 +1,302 @@
+/*
+ * 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;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Prefix;
+import org.onosproject.core.IdGenerator;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * ACL rule class.
+ */
+public final class AclRule {
+
+    private final RuleId id;
+
+    private final Ip4Prefix srcIp;
+    private final Ip4Prefix dstIp;
+    private final byte ipProto;
+    private final short dstTpPort;
+    private final Action action;
+
+    private static IdGenerator idGenerator;
+
+    /**
+     * Enum type for ACL rule's action.
+     */
+    public enum Action {
+        DENY, ALLOW
+    }
+
+    /**
+     * Constructor for serializer.
+     */
+    private AclRule() {
+        this.id = null;
+        this.srcIp = null;
+        this.dstIp = null;
+        this.ipProto = 0;
+        this.dstTpPort = 0;
+        this.action = null;
+    }
+
+    /**
+     * Create a new ACL rule.
+     *
+     * @param srcIp source IP address
+     * @param dstIp destination IP address
+     * @param ipProto IP protocol
+     * @param dstTpPort destination transport layer port
+     * @param action ACL rule's action
+     */
+    private AclRule(Ip4Prefix srcIp,
+                   Ip4Prefix dstIp,
+                   byte ipProto,
+                   short dstTpPort,
+                   Action action) {
+        checkState(idGenerator != null, "Id generator is not bound.");
+        this.id = RuleId.valueOf(idGenerator.getNewId());
+        this.srcIp = srcIp;
+        this.dstIp = dstIp;
+        this.ipProto = ipProto;
+        this.dstTpPort = dstTpPort;
+        this.action = action;
+    }
+
+    /**
+     * Check if the first CIDR address is in (or the same as) the second CIDR address.
+     */
+    private boolean checkCIDRinCIDR(Ip4Prefix cidrAddr1, Ip4Prefix cidrAddr2) {
+        if (cidrAddr2 == null) {
+            return true;
+        } else if (cidrAddr1 == null) {
+            return false;
+        }
+        if (cidrAddr1.prefixLength() < cidrAddr2.prefixLength()) {
+            return false;
+        }
+        int offset = 32 - cidrAddr2.prefixLength();
+
+        int cidr1Prefix = cidrAddr1.address().toInt();
+        int cidr2Prefix = cidrAddr2.address().toInt();
+        cidr1Prefix = cidr1Prefix >> offset;
+        cidr2Prefix = cidr2Prefix >> offset;
+        cidr1Prefix = cidr1Prefix << offset;
+        cidr2Prefix = cidr2Prefix << offset;
+
+        return (cidr1Prefix == cidr2Prefix);
+    }
+
+    /**
+     * Check if this ACL rule match the given ACL rule.
+     * @param r ACL rule to check against
+     * @return true if this ACL rule matches the given ACL ruleule.
+     */
+    public boolean checkMatch(AclRule r) {
+        return (this.dstTpPort == r.dstTpPort || r.dstTpPort == 0)
+                && (this.ipProto == r.ipProto || r.ipProto == 0)
+                && (checkCIDRinCIDR(this.srcIp(), r.srcIp()))
+                && (checkCIDRinCIDR(this.dstIp(), r.dstIp()));
+    }
+
+    /**
+     * Returns a new ACL rule builder.
+     *
+     * @return ACL rule builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Builder of an ACL rule.
+     */
+    public static final class Builder {
+
+        private Ip4Prefix srcIp = null;
+        private Ip4Prefix dstIp = null;
+        private byte ipProto = 0;
+        private short dstTpPort = 0;
+        private Action action = Action.DENY;
+
+        private Builder() {
+            // Hide constructor
+        }
+
+        /**
+         * Sets the source IP address for the ACL rule that will be built.
+         *
+         * @param srcIp source IP address to use for built ACL rule
+         * @return this builder
+         */
+        public Builder srcIp(String srcIp) {
+            this.srcIp = Ip4Prefix.valueOf(srcIp);
+            return this;
+        }
+
+        /**
+         * Sets the destination IP address for the ACL rule that will be built.
+         *
+         * @param dstIp destination IP address to use for built ACL rule
+         * @return this builder
+         */
+        public Builder dstIp(String dstIp) {
+            this.dstIp = Ip4Prefix.valueOf(dstIp);
+            return this;
+        }
+
+        /**
+         * Sets the IP protocol for the ACL rule that will be built.
+         *
+         * @param ipProto IP protocol to use for built ACL rule
+         * @return this builder
+         */
+        public Builder ipProto(byte ipProto) {
+            this.ipProto = ipProto;
+            return this;
+        }
+
+        /**
+         * Sets the destination transport layer port for the ACL rule that will be built.
+         *
+         * @param dstTpPort destination transport layer port to use for built ACL rule
+         * @return this builder
+         */
+        public Builder dstTpPort(short dstTpPort) {
+            if ((ipProto == IPv4.PROTOCOL_TCP || ipProto == IPv4.PROTOCOL_UDP)) {
+                this.dstTpPort = dstTpPort;
+            }
+            return this;
+        }
+
+        /**
+         * Sets the action for the ACL rule that will be built.
+         *
+         * @param action action to use for built ACL rule
+         * @return this builder
+         */
+        public Builder action(Action action) {
+            this.action = action;
+            return this;
+        }
+
+        /**
+         * Builds an ACL rule from the accumulated parameters.
+         * @return ACL rule instance
+         */
+        public AclRule build() {
+            checkState(srcIp != null && dstIp != null, "Either srcIp or dstIp must be assigned.");
+            checkState(ipProto == 0 || ipProto == IPv4.PROTOCOL_ICMP
+                               || ipProto == IPv4.PROTOCOL_TCP || ipProto == IPv4.PROTOCOL_UDP,
+                       "ipProto must be assigned to TCP, UDP, or ICMP.");
+            return new AclRule(
+                    srcIp,
+                    dstIp,
+                    ipProto,
+                    dstTpPort,
+                    action
+            );
+        }
+
+    }
+
+    /**
+     * Binds an id generator for unique ACL rule id generation.
+     *
+     * Note: A generator cannot be bound if there is already a generator bound.
+     *
+     * @param newIdGenerator id generator
+     */
+    public static void bindIdGenerator(IdGenerator newIdGenerator) {
+        checkState(idGenerator == null, "Id generator is already bound.");
+        idGenerator = checkNotNull(newIdGenerator);
+    }
+
+    public RuleId id() {
+        return id;
+    }
+
+    public Ip4Prefix srcIp() {
+        return srcIp;
+    }
+
+    public Ip4Prefix dstIp() {
+        return this.dstIp;
+    }
+
+    public byte ipProto() {
+        return ipProto;
+    }
+
+    public short dstTpPort() {
+        return dstTpPort;
+    }
+
+    public Action action() {
+        return action;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(action,
+                            id.fingerprint(),
+                            ipProto,
+                            srcIp,
+                            dstIp,
+                            dstTpPort);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof AclRule) {
+            AclRule that = (AclRule) obj;
+            return Objects.equals(id, that.id) &&
+                    Objects.equals(srcIp, that.srcIp) &&
+                    Objects.equals(dstIp, that.dstIp) &&
+                    Objects.equals(ipProto, that.ipProto) &&
+                    Objects.equals(dstTpPort, that.dstTpPort) &&
+                    Objects.equals(action, that.action);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .omitNullValues()
+                .add("id", id)
+                .add("srcIp", srcIp)
+                .add("dstIp", dstIp)
+                .add("ipProto", ipProto)
+                .add("dstTpPort", dstTpPort)
+                .add("action", action)
+                .toString();
+    }
+
+}
diff --git a/apps/acl/src/main/java/org/onos/acl/AclService.java b/apps/acl/src/main/java/org/onos/acl/AclService.java
new file mode 100644
index 0000000..0c2b734
--- /dev/null
+++ b/apps/acl/src/main/java/org/onos/acl/AclService.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import java.util.List;
+
+/**
+ * Service interface exported by ACL application.
+ */
+public interface AclService {
+
+    /**
+     * Gets a list containing all ACL rules.
+     * @return a list containing all ACL rules
+     */
+    List<AclRule> getAclRules();
+
+    /**
+     * Adds a new ACL rule.
+     * @param rule ACL rule
+     * @return true if successfully added, otherwise false
+     */
+    boolean addAclRule(AclRule rule);
+
+    /**
+     * Removes an exsiting ACL rule by rule id.
+     * @param ruleId ACL rule identifier
+     */
+    void removeAclRule(RuleId ruleId);
+
+    /**
+     * Clears ACL and resets all.
+     */
+    void clearAcl();
+
+}
\ No newline at end of file
diff --git a/apps/acl/src/main/java/org/onos/acl/AclStore.java b/apps/acl/src/main/java/org/onos/acl/AclStore.java
new file mode 100644
index 0000000..47531b2
--- /dev/null
+++ b/apps/acl/src/main/java/org/onos/acl/AclStore.java
@@ -0,0 +1,132 @@
+/*
+ * 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;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.store.Store;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Service interface exported by ACL distributed store.
+ */
+public interface AclStore extends Store {
+
+    /**
+     * Gets a list containing all ACL rules.
+     * @return a list containing all ACL rules
+     */
+    List<AclRule> getAclRules();
+
+    /**
+     * Adds a new ACL rule.
+     * @param rule new ACL rule
+     */
+    void addAclRule(AclRule rule);
+
+    /**
+     * Gets an existing ACL rule.
+     * @param ruleId ACL rule id
+     * @return ACL rule with the given id
+     */
+    AclRule getAclRule(RuleId ruleId);
+
+    /**
+     * Removes an existing ACL rule by rule id.
+     * @param ruleId ACL rule id
+     */
+    void removeAclRule(RuleId ruleId);
+
+    /**
+     * Clears ACL and reset all.
+     */
+    void clearAcl();
+
+    /**
+     * Gets the current priority for new ACL flow rule by device id.
+     * @param deviceId device id
+     * @return new ACL flow rule's priority in the given device
+     */
+    int getPriorityByDevice(DeviceId deviceId);
+
+    /**
+     * Gets a set containing all ACL flow rules belonging to a given ACL rule.
+     * @param ruleId ACL rule id
+     * @return a set containing all ACL flow rules belonging to the given ACL rule
+     */
+    Set<FlowRule> getFlowByRule(RuleId ruleId);
+
+    /**
+     * Adds a new mapping from ACL rule to ACL flow rule.
+     * @param ruleId ACL rule id
+     * @param flowRule ACL flow rule
+     */
+    void addRuleToFlowMapping(RuleId ruleId, FlowRule flowRule);
+
+    /**
+     * Removes an existing mapping from ACL rule to ACL flow rule.
+     * @param ruleId ACL rule id
+     */
+    void removeRuleToFlowMapping(RuleId ruleId);
+
+    /**
+     * Gets a list containing all allowing ACL rules matching a given denying ACL rule.
+     * @param denyingRuleId denying ACL rule id
+     * @return a list containing all allowing ACL rules matching the given denying ACL rule
+     */
+    List<RuleId> getAllowingRuleByDenyingRule(RuleId denyingRuleId);
+
+    /**
+     * Adds a new mapping from denying ACL rule to allowing ACL rule.
+     * @param denyingRuleId denying ACL rule id
+     * @param allowingRuleId allowing ACL rule id
+     */
+    void addDenyToAllowMapping(RuleId denyingRuleId, RuleId allowingRuleId);
+
+    /**
+     * Removes an exsiting mapping from denying ACL rule to allowing ACL rule.
+     * @param denyingRuleId denying ACL rule id
+     */
+    void removeDenyToAllowMapping(RuleId denyingRuleId);
+
+    /**
+     * Checks if an existing ACL rule already works in a given device.
+     * @param ruleId ACL rule id
+     * @param deviceId devide id
+     * @return true if the given ACL rule works in the given device
+     */
+    boolean checkIfRuleWorksInDevice(RuleId ruleId, DeviceId deviceId);
+
+    /**
+     * Adds a new mapping from ACL rule to device.
+     * @param ruleId ACL rule id
+     * @param deviceId device id
+     */
+    void addRuleToDeviceMapping(RuleId ruleId, DeviceId deviceId);
+
+    /**
+     * Removes an existing mapping from ACL rule to device.
+     * @param ruleId ACL rule id
+     */
+    void removeRuleToDeviceMapping(RuleId ruleId);
+
+}
diff --git a/apps/acl/src/main/java/org/onos/acl/AclWebResource.java b/apps/acl/src/main/java/org/onos/acl/AclWebResource.java
new file mode 100644
index 0000000..2ef8041
--- /dev/null
+++ b/apps/acl/src/main/java/org/onos/acl/AclWebResource.java
@@ -0,0 +1,215 @@
+/*
+ * 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;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.IPv4;
+import org.onosproject.rest.AbstractWebResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * REST resource for interacting with ACL application.
+ */
+@Path("")
+public class AclWebResource extends AbstractWebResource {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    /**
+     * Processes user's GET HTTP request for querying ACL rules.
+     * @return response to the request
+     */
+    @GET
+    public Response queryAclRule() {
+        List<AclRule> rules = get(AclService.class).getAclRules();
+        ObjectMapper mapper = new ObjectMapper();
+        ObjectNode root = mapper.createObjectNode();
+        ArrayNode arrayNode = mapper.createArrayNode();
+        for (AclRule rule : rules) {
+            ObjectNode node = mapper.createObjectNode();
+            node.put("id", rule.id().toString());
+            if (rule.srcIp() != null) {
+                node.put("srcIp", rule.srcIp().toString());
+            }
+            if (rule.dstIp() != null) {
+                node.put("dstIp", rule.dstIp().toString());
+            }
+            if (rule.ipProto() != 0) {
+                switch (rule.ipProto()) {
+                    case IPv4.PROTOCOL_ICMP:
+                        node.put("ipProto", "ICMP");
+                        break;
+                    case IPv4.PROTOCOL_TCP:
+                        node.put("ipProto", "TCP");
+                        break;
+                    case IPv4.PROTOCOL_UDP:
+                        node.put("ipProto", "UDP");
+                        break;
+                    default:
+                        break;
+                }
+            }
+            if (rule.dstTpPort() != 0) {
+                node.put("dstTpPort", rule.dstTpPort());
+            }
+            node.put("action", rule.action().toString());
+            arrayNode.add(node);
+        }
+        root.set("ACL rules", arrayNode);
+        return Response.ok(root.toString(), MediaType.APPLICATION_JSON_TYPE).build();
+    }
+
+    /**
+     * Processes user's POST HTTP request for add ACL rules.
+     * @param stream input stream
+     * @return response to the request
+     */
+    @POST
+    @Path("add")
+    public Response addAclRule(InputStream stream) {
+        AclRule newRule;
+        try {
+            newRule = jsonToRule(stream);
+        } catch (Exception e) {
+            return Response.ok("{\"status\" : \"Failed! " + e.getMessage() + "\"}").build();
+        }
+
+        String status;
+        if (get(AclService.class).addAclRule(newRule)) {
+            status = "Success! New ACL rule is added.";
+        } else {
+            status = "Failed! New ACL rule matches an existing rule.";
+        }
+        return Response.ok("{\"status\" : \"" + status + "\"}").build();
+    }
+
+    /**
+     * Processes user's GET HTTP request for removing ACL rule.
+     * @param id ACL rule id (in hex string format)
+     * @return response to the request
+     */
+    @GET
+    @Path("remove/{id}")
+    public Response removeAclRule(@PathParam("id") String id) {
+        String status;
+        RuleId ruleId = new RuleId(Long.parseLong(id.substring(2), 16));
+        if (get(AclStore.class).getAclRule(ruleId) == null) {
+            status = "Failed! There is no ACL rule with this id.";
+        } else {
+            get(AclService.class).removeAclRule(ruleId);
+            status = "Success! ACL rule(id:" + id + ") is removed.";
+        }
+        return Response.ok("{\"status\" : \"" + status + "\"}").build();
+    }
+
+    /**
+     * Processes user's GET HTTP request for clearing ACL.
+     * @return response to the request
+     */
+    @GET
+    @Path("clear")
+    public Response clearACL() {
+        get(AclService.class).clearAcl();
+        return Response.ok("{\"status\" : \"ACL is cleared.\"}").build();
+    }
+
+    /**
+     * Exception class for parsing a invalid ACL rule.
+     */
+    private class AclRuleParseException extends Exception {
+        public AclRuleParseException(String message) {
+            super(message);
+        }
+    }
+
+    /**
+     * Turns a JSON string into an ACL rule instance.
+     */
+    private AclRule jsonToRule(InputStream stream) throws AclRuleParseException, IOException {
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode jsonNode = mapper.readTree(stream);
+        JsonParser jp = jsonNode.traverse();
+        AclRule.Builder rule = AclRule.builder();
+        jp.nextToken();
+        if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
+            throw new AclRuleParseException("Expected START_OBJECT");
+        }
+
+        while (jp.nextToken() != JsonToken.END_OBJECT) {
+            if (jp.getCurrentToken() != JsonToken.FIELD_NAME) {
+                throw new AclRuleParseException("Expected FIELD_NAME");
+            }
+
+            String key = jp.getCurrentName();
+            jp.nextToken();
+            String value = jp.getText();
+            if ("".equals(value)) {
+                continue;
+            }
+
+            if ("srcIp".equals(key)) {
+                rule.srcIp(value);
+            } else if ("dstIp".equals(key)) {
+                rule.dstIp(value);
+            } else if ("ipProto".equals(key)) {
+                if ("TCP".equalsIgnoreCase(value)) {
+                    rule.ipProto(IPv4.PROTOCOL_TCP);
+                } else if ("UDP".equalsIgnoreCase(value)) {
+                    rule.ipProto(IPv4.PROTOCOL_UDP);
+                } else if ("ICMP".equalsIgnoreCase(value)) {
+                    rule.ipProto(IPv4.PROTOCOL_ICMP);
+                } else {
+                    throw new AclRuleParseException("ipProto must be assigned to TCP, UDP, or ICMP.");
+                }
+            } else if ("dstTpPort".equals(key)) {
+                try {
+                    rule.dstTpPort(Short.parseShort(value));
+                } catch (NumberFormatException e) {
+                    throw new AclRuleParseException("dstTpPort must be assigned to a numerical value.");
+                }
+            } else if ("action".equals(key)) {
+                if (!"allow".equalsIgnoreCase(value) && !"deny".equalsIgnoreCase(value)) {
+                    throw new AclRuleParseException("action must be assigned to ALLOW or DENY.");
+                }
+                if ("allow".equalsIgnoreCase(value)) {
+                    rule.action(AclRule.Action.ALLOW);
+                }
+            }
+        }
+        return rule.build();
+    }
+
+}
\ No newline at end of file
diff --git a/apps/acl/src/main/java/org/onos/acl/RuleId.java b/apps/acl/src/main/java/org/onos/acl/RuleId.java
new file mode 100644
index 0000000..754a643
--- /dev/null
+++ b/apps/acl/src/main/java/org/onos/acl/RuleId.java
@@ -0,0 +1,85 @@
+/*
+ * 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;
+
+/**
+ * ACL rule identifier suitable as an external key.
+ * <p>This class is immutable.</p>
+ */
+public final class RuleId {
+    private final long value;
+
+    /**
+     * Creates an ACL rule identifier from the specified long value.
+     *
+     * @param value long value
+     * @return ACL rule identifier
+     */
+    public static RuleId valueOf(long value) {
+        return new RuleId(value);
+    }
+
+    /**
+     * Constructor for serializer.
+     */
+    RuleId() {
+        this.value = 0;
+    }
+
+    /**
+     * Constructs the ID corresponding to a given long value.
+     *
+     * @param value the underlying value of this ID
+     */
+    RuleId(long value) {
+        this.value = value;
+    }
+
+    /**
+     * Returns the backing value.
+     *
+     * @return the value
+     */
+    public long fingerprint() {
+        return value;
+    }
+
+    @Override
+    public int hashCode() {
+        return Long.hashCode(value);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof RuleId)) {
+            return false;
+        }
+        RuleId that = (RuleId) obj;
+        return this.value == that.value;
+    }
+
+    @Override
+    public String toString() {
+        return "0x" + Long.toHexString(value);
+    }
+}
diff --git a/apps/acl/src/main/java/org/onos/acl/impl/AclManager.java b/apps/acl/src/main/java/org/onos/acl/impl/AclManager.java
new file mode 100644
index 0000000..dd8355e
--- /dev/null
+++ b/apps/acl/src/main/java/org/onos/acl/impl/AclManager.java
@@ -0,0 +1,336 @@
+/*
+ * 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.impl;
+
+import org.onos.acl.AclRule;
+import org.onos.acl.AclService;
+import org.onos.acl.AclStore;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpAddress;
+import org.onos.acl.RuleId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.slf4j.Logger;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the ACL service.
+ */
+@Component(immediate = true)
+@Service
+public class AclManager implements AclService {
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected AclStore aclStore;
+
+    private final Logger log = getLogger(getClass());
+    private ApplicationId appId;
+    private final HostListener hostListener = new InternalHostListener();
+    private IdGenerator idGenerator;
+
+    /**
+     * Checks if the given IP address is in the given CIDR address.
+     */
+    private boolean checkIpInCIDR(Ip4Address ip, Ip4Prefix cidr) {
+        int offset = 32 - cidr.prefixLength();
+        int cidrPrefix = cidr.address().toInt();
+        int ipIntValue = ip.toInt();
+        cidrPrefix = cidrPrefix >> offset;
+        ipIntValue = ipIntValue >> offset;
+        cidrPrefix = cidrPrefix << offset;
+        ipIntValue = ipIntValue << offset;
+
+        return (cidrPrefix == ipIntValue);
+    }
+
+    private class InternalHostListener implements HostListener {
+
+        /**
+         * Generate new ACL flow rules for new host following the given ACL rule.
+         */
+        private void processHostAddedEvent(HostEvent event, AclRule rule) {
+            DeviceId deviceId = event.subject().location().deviceId();
+            for (IpAddress address : event.subject().ipAddresses()) {
+                if ((rule.srcIp() != null) ?
+                        (checkIpInCIDR(address.getIp4Address(), rule.srcIp())) :
+                        (checkIpInCIDR(address.getIp4Address(), rule.dstIp()))) {
+                    if (!aclStore.checkIfRuleWorksInDevice(rule.id(), deviceId)) {
+                        List<RuleId> allowingRuleList = aclStore
+                                .getAllowingRuleByDenyingRule(rule.id());
+                        if (allowingRuleList != null) {
+                            for (RuleId allowingRuleId : allowingRuleList) {
+                                generateACLFlow(aclStore.getAclRule(allowingRuleId), deviceId);
+                            }
+                        }
+                        generateACLFlow(rule, deviceId);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void event(HostEvent event) {
+            // if a new host appears and an existing rule denies
+            // its traffic, a new ACL flow rule is generated.
+            if (event.type() == HostEvent.Type.HOST_ADDED) {
+                DeviceId deviceId = event.subject().location().deviceId();
+                if (mastershipService.getLocalRole(deviceId) == MastershipRole.MASTER) {
+                    for (AclRule rule : aclStore.getAclRules()) {
+                        if (rule.action() != AclRule.Action.ALLOW) {
+                            processHostAddedEvent(event, rule);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Activate
+    public void activate() {
+        appId = coreService.registerApplication("org.onos.acl");
+        hostService.addListener(hostListener);
+        idGenerator = coreService.getIdGenerator("acl-ids");
+        AclRule.bindIdGenerator(idGenerator);
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        hostService.removeListener(hostListener);
+        flowRuleService.removeFlowRulesById(appId);
+        aclStore.clearAcl();
+        log.info("Stopped");
+    }
+
+    @Override
+    public List<AclRule> getAclRules() {
+        return aclStore.getAclRules();
+    }
+
+    /**
+     * Checks if the new ACL rule matches an existing rule.
+     * If existing allowing rules matches the new denying rule, store the mappings.
+     * @return true if the new ACL rule matches an existing rule, false otherwise
+     */
+    private boolean matchCheck(AclRule newRule) {
+        for (AclRule existingRule : aclStore.getAclRules()) {
+            if (newRule.checkMatch(existingRule)) {
+                return true;
+            }
+
+            if (existingRule.action() == AclRule.Action.ALLOW
+                    && newRule.action() == AclRule.Action.DENY) {
+                if (existingRule.checkMatch(newRule)) {
+                    aclStore.addDenyToAllowMapping(newRule.id(), existingRule.id());
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean addAclRule(AclRule rule) {
+        if (matchCheck(rule)) {
+            return false;
+        }
+        aclStore.addAclRule(rule);
+        log.info("ACL rule(id:{}) is added.", rule.id());
+        if (rule.action() != AclRule.Action.ALLOW) {
+            enforceRuleAdding(rule);
+        }
+        return true;
+    }
+
+    /**
+     * Gets a set containing all devices connecting with the hosts
+     * whose IP address is in the given CIDR IP address.
+     */
+    private Set<DeviceId> getDeviceIdSet(Ip4Prefix cidrAddr) {
+        Set<DeviceId> deviceIdSet = new HashSet<>();
+        final Iterable<Host> hosts = hostService.getHosts();
+
+        if (cidrAddr.prefixLength() != 32) {
+            for (Host h : hosts) {
+                for (IpAddress a : h.ipAddresses()) {
+                    if (checkIpInCIDR(a.getIp4Address(), cidrAddr)) {
+                        deviceIdSet.add(h.location().deviceId());
+                    }
+                }
+            }
+        } else {
+            for (Host h : hosts) {
+                for (IpAddress a : h.ipAddresses()) {
+                    if (checkIpInCIDR(a.getIp4Address(), cidrAddr)) {
+                        deviceIdSet.add(h.location().deviceId());
+                        return deviceIdSet;
+                    }
+                }
+            }
+        }
+        return deviceIdSet;
+    }
+
+    /**
+     * Enforces denying ACL rule by ACL flow rules.
+     */
+    private void enforceRuleAdding(AclRule rule) {
+        Set<DeviceId> dpidSet;
+        if (rule.srcIp() != null) {
+            dpidSet = getDeviceIdSet(rule.srcIp());
+        } else {
+            dpidSet = getDeviceIdSet(rule.dstIp());
+        }
+
+        for (DeviceId deviceId : dpidSet) {
+            List<RuleId> allowingRuleList = aclStore.getAllowingRuleByDenyingRule(rule.id());
+            if (allowingRuleList != null) {
+                for (RuleId allowingRuleId : allowingRuleList) {
+                    generateACLFlow(aclStore.getAclRule(allowingRuleId), deviceId);
+                }
+            }
+            generateACLFlow(rule, deviceId);
+        }
+    }
+
+    /**
+     * Generates ACL flow rule according to ACL rule
+     * and install it into related device.
+     */
+    private void generateACLFlow(AclRule rule, DeviceId deviceId) {
+        if (rule == null || aclStore.checkIfRuleWorksInDevice(rule.id(), deviceId)) {
+            return;
+        }
+
+        TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        FlowEntry.Builder flowEntry = DefaultFlowEntry.builder();
+
+        selectorBuilder.matchEthType(Ethernet.TYPE_IPV4);
+        if (rule.srcIp() != null) {
+            selectorBuilder.matchIPSrc(rule.srcIp());
+            if (rule.dstIp() != null) {
+                selectorBuilder.matchIPDst(rule.dstIp());
+            }
+        } else {
+            selectorBuilder.matchIPDst(rule.dstIp());
+        }
+        if (rule.ipProto() != 0) {
+            selectorBuilder.matchIPProtocol(Integer.valueOf(rule.ipProto()).byteValue());
+        }
+        if (rule.dstTpPort() != 0) {
+            switch (rule.ipProto()) {
+                case IPv4.PROTOCOL_TCP:
+                    selectorBuilder.matchTcpDst(rule.dstTpPort());
+                    break;
+                case IPv4.PROTOCOL_UDP:
+                    selectorBuilder.matchUdpDst(rule.dstTpPort());
+                    break;
+                default:
+                    break;
+            }
+        }
+        if (rule.action() == AclRule.Action.ALLOW) {
+            treatment.add(Instructions.createOutput(PortNumber.CONTROLLER));
+        }
+        flowEntry.forDevice(deviceId);
+        flowEntry.withPriority(aclStore.getPriorityByDevice(deviceId));
+        flowEntry.withSelector(selectorBuilder.build());
+        flowEntry.withTreatment(treatment.build());
+        flowEntry.fromApp(appId);
+        flowEntry.makePermanent();
+        // install flow rule
+        flowRuleService.applyFlowRules(flowEntry.build());
+        log.debug("ACL flow rule {} is installed in {}.", flowEntry.build(), deviceId);
+        aclStore.addRuleToFlowMapping(rule.id(), flowEntry.build());
+        aclStore.addRuleToDeviceMapping(rule.id(), deviceId);
+    }
+
+    @Override
+    public void removeAclRule(RuleId ruleId) {
+        aclStore.removeAclRule(ruleId);
+        log.info("ACL rule(id:{}) is removed.", ruleId);
+        enforceRuleRemoving(ruleId);
+    }
+
+    /**
+     * Enforces removing an existing ACL rule.
+     */
+    private void enforceRuleRemoving(RuleId ruleId) {
+        Set<FlowRule> flowSet = aclStore.getFlowByRule(ruleId);
+        if (flowSet != null) {
+            for (FlowRule flowRule : flowSet) {
+                flowRuleService.removeFlowRules(flowRule);
+                log.debug("ACL flow rule {} is removed from {}.", flowRule.toString(), flowRule.deviceId().toString());
+            }
+        }
+        aclStore.removeRuleToFlowMapping(ruleId);
+        aclStore.removeRuleToDeviceMapping(ruleId);
+        aclStore.removeDenyToAllowMapping(ruleId);
+    }
+
+    @Override
+    public void clearAcl() {
+        aclStore.clearAcl();
+        flowRuleService.removeFlowRulesById(appId);
+        log.info("ACL is cleared.");
+    }
+
+}
diff --git a/apps/acl/src/main/java/org/onos/acl/impl/DistributedAclStore.java b/apps/acl/src/main/java/org/onos/acl/impl/DistributedAclStore.java
new file mode 100644
index 0000000..656b7c7
--- /dev/null
+++ b/apps/acl/src/main/java/org/onos/acl/impl/DistributedAclStore.java
@@ -0,0 +1,251 @@
+/*
+ * 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.impl;
+
+import com.google.common.collect.Collections2;
+import org.onos.acl.AclRule;
+import org.onos.acl.AclStore;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onos.acl.RuleId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the ACL store service.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedAclStore extends AbstractStore implements AclStore {
+
+    private final Logger log = getLogger(getClass());
+    private final int defaultFlowMaxPriority = 30000;
+
+    private ConsistentMap<RuleId, AclRule> ruleSet;
+    private ConsistentMap<DeviceId, Integer> deviceToPriority;
+    private ConsistentMap<RuleId, Set<DeviceId>> ruleToDevice;
+    private ConsistentMap<RuleId, Set<FlowRule>> ruleToFlow;
+    private ConsistentMap<RuleId, List<RuleId>> denyRuleToAllowRule;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Activate
+    public void activate() {
+        ApplicationId appId = coreService.getAppId("org.onosproject.acl");
+
+        KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
+                .register(KryoNamespaces.API)
+                .register(AclRule.class)
+                .register(AclRule.Action.class)
+                .register(RuleId.class);
+
+        ruleSet = storageService.<RuleId, AclRule>consistentMapBuilder()
+                .withSerializer(Serializer.using(serializer.build()))
+                .withName("acl-rule-set")
+                .withApplicationId(appId)
+                .withPurgeOnUninstall()
+                .build();
+
+        deviceToPriority = storageService.<DeviceId, Integer>consistentMapBuilder()
+                .withSerializer(Serializer.using(serializer.build()))
+                .withName("device-to-priority")
+                .withApplicationId(appId)
+                .withPurgeOnUninstall()
+                .build();
+
+        ruleToFlow = storageService.<RuleId, Set<FlowRule>>consistentMapBuilder()
+                .withSerializer(Serializer.using(serializer.build()))
+                .withName("rule-to-flow")
+                .withApplicationId(appId)
+                .withPurgeOnUninstall()
+                .build();
+
+        denyRuleToAllowRule = storageService.<RuleId, List<RuleId>>consistentMapBuilder()
+                .withSerializer(Serializer.using(serializer.build()))
+                .withName("deny-to-allow")
+                .withApplicationId(appId)
+                .withPurgeOnUninstall()
+                .build();
+
+        ruleToDevice = storageService.<RuleId, Set<DeviceId>>consistentMapBuilder()
+                .withSerializer(Serializer.using(serializer.build()))
+                .withName("rule-to-device")
+                .withApplicationId(appId)
+                .withPurgeOnUninstall()
+                .build();
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactive() {
+        log.info("Stopped");
+    }
+
+    @Override
+    public List<AclRule> getAclRules() {
+        List<AclRule> aclRules = new ArrayList<>();
+        aclRules.addAll(Collections2.transform(ruleSet.values(), Versioned::value));
+        return aclRules;
+    }
+
+    @Override
+    public void addAclRule(AclRule rule) {
+        ruleSet.putIfAbsent(rule.id(), rule);
+    }
+
+    @Override
+    public AclRule getAclRule(RuleId ruleId) {
+        Versioned<AclRule> rule = ruleSet.get(ruleId);
+        if (rule != null) {
+            return rule.value();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void removeAclRule(RuleId ruleId) {
+        ruleSet.remove(ruleId);
+    }
+
+    @Override
+    public void clearAcl() {
+        ruleSet.clear();
+        deviceToPriority.clear();
+        ruleToFlow.clear();
+        denyRuleToAllowRule.clear();
+        ruleToDevice.clear();
+    }
+
+    @Override
+    public int getPriorityByDevice(DeviceId deviceId) {
+        return deviceToPriority.compute(deviceId,
+                                        (id, priority) -> (priority == null) ? defaultFlowMaxPriority : (priority - 1))
+                .value();
+    }
+
+    @Override
+    public Set<FlowRule> getFlowByRule(RuleId ruleId) {
+        Versioned<Set<FlowRule>> flowRuleSet = ruleToFlow.get(ruleId);
+        if (flowRuleSet != null) {
+            return flowRuleSet.value();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void addRuleToFlowMapping(RuleId ruleId, FlowRule flowRule) {
+        ruleToFlow.computeIf(ruleId,
+                             flowRuleSet -> (flowRuleSet == null || !flowRuleSet.contains(flowRule)),
+                             (id, flowRuleSet) -> {
+                                 Set<FlowRule> newSet = new HashSet<>();
+                                 if (flowRuleSet != null) {
+                                     newSet.addAll(flowRuleSet);
+                                 }
+                                 newSet.add(flowRule);
+                                 return newSet;
+                             });
+    }
+
+    @Override
+    public void removeRuleToFlowMapping(RuleId ruleId) {
+        ruleToFlow.remove(ruleId);
+    }
+
+    @Override
+    public List<RuleId> getAllowingRuleByDenyingRule(RuleId denyingRuleId) {
+        Versioned<List<RuleId>> allowRuleIdSet = denyRuleToAllowRule.get(denyingRuleId);
+        if (allowRuleIdSet != null) {
+            return allowRuleIdSet.value();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void addDenyToAllowMapping(RuleId denyingRuleId, RuleId allowingRuleId) {
+        denyRuleToAllowRule.computeIf(denyingRuleId,
+                                      ruleIdList -> (ruleIdList == null || !ruleIdList.contains(allowingRuleId)),
+                                      (id, ruleIdList) -> {
+                                          ArrayList<RuleId> newList = new ArrayList<>();
+                                          if (ruleIdList != null) {
+                                              newList.addAll(ruleIdList);
+                                          }
+                                          newList.add(allowingRuleId);
+                                          return newList;
+                                      });
+    }
+
+    @Override
+    public void removeDenyToAllowMapping(RuleId denyingRuleId) {
+        denyRuleToAllowRule.remove(denyingRuleId);
+    }
+
+    @Override
+    public boolean checkIfRuleWorksInDevice(RuleId ruleId, DeviceId deviceId) {
+        return ruleToDevice.containsKey(ruleId) && ruleToDevice.get(ruleId).value().contains(deviceId);
+    }
+
+    @Override
+    public void addRuleToDeviceMapping(RuleId ruleId, DeviceId deviceId) {
+        ruleToDevice.computeIf(ruleId,
+                               deviceIdSet -> (deviceIdSet == null || !deviceIdSet.contains(deviceId)),
+                               (id, deviceIdSet) -> {
+                                   Set<DeviceId> newSet = new HashSet<DeviceId>();
+                                   if (deviceIdSet != null) {
+                                       newSet.addAll(deviceIdSet);
+                                   }
+                                   newSet.add(deviceId);
+                                   return newSet;
+                               });
+    }
+
+    @Override
+    public void removeRuleToDeviceMapping(RuleId ruleId) {
+        ruleToDevice.remove(ruleId);
+    }
+
+}
\ No newline at end of file
diff --git a/apps/acl/src/main/webapp/WEB-INF/web.xml b/apps/acl/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..2c2d5cf
--- /dev/null
+++ b/apps/acl/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         id="ONOS" version="2.5">
+    <display-name>ACL application</display-name>
+
+    <servlet>
+        <servlet-name>JAX-RS Service</servlet-name>
+        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
+            <param-value>com.sun.jersey.api.core.ClassNamesResourceConfig</param-value>
+        </init-param>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.classnames</param-name>
+            <param-value>org.onos.acl.AclWebResource</param-value>
+        </init-param>
+        <load-on-startup>10</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>JAX-RS Service</servlet-name>
+        <url-pattern>/*</url-pattern>
+    </servlet-mapping>
+
+</web-app>
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."));
+    }
+}
diff --git a/apps/acl/src/test/java/org/onos/acl/web/ResourceTest.java b/apps/acl/src/test/java/org/onos/acl/web/ResourceTest.java
new file mode 100644
index 0000000..04cd10b
--- /dev/null
+++ b/apps/acl/src/test/java/org/onos/acl/web/ResourceTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.test.framework.AppDescriptor;
+import com.sun.jersey.test.framework.JerseyTest;
+import com.sun.jersey.test.framework.WebAppDescriptor;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+
+/**
+ * Base class for REST API tests.  Performs common configuration operations.
+ */
+public class ResourceTest extends JerseyTest {
+
+    /**
+     * Assigns an available port for the test.
+     *
+     * @param defaultPort If a port cannot be determined, this one is used.
+     * @return free port
+     */
+    @Override
+    public int getPort(int defaultPort) {
+        try {
+            ServerSocket socket = new ServerSocket(0);
+            socket.setReuseAddress(true);
+            int port = socket.getLocalPort();
+            socket.close();
+            return port;
+        } catch (IOException ioe) {
+            return defaultPort;
+        }
+    }
+
+    @Override
+    public AppDescriptor configure() {
+        return new WebAppDescriptor.Builder("org.onos.acl").build();
+    }
+
+}
diff --git a/apps/acl/src/test/java/org/onos/acl/web/TestServiceDirectory.java b/apps/acl/src/test/java/org/onos/acl/web/TestServiceDirectory.java
new file mode 100644
index 0000000..6dbd302
--- /dev/null
+++ b/apps/acl/src/test/java/org/onos/acl/web/TestServiceDirectory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.google.common.collect.ClassToInstanceMap;
+import com.google.common.collect.MutableClassToInstanceMap;
+import org.onlab.osgi.ServiceDirectory;
+
+/**
+ * Service directory implementation suitable for testing.
+ */
+public class TestServiceDirectory implements ServiceDirectory {
+
+
+    private ClassToInstanceMap<Object> services = MutableClassToInstanceMap.create();
+
+    @Override
+    public <T> T get(Class<T> serviceClass) {
+        return services.getInstance(serviceClass);
+    }
+
+    /**
+     * Adds a new service to the directory.
+     *
+     * @param serviceClass service class
+     * @param service service instance
+     * @return self
+     */
+    public TestServiceDirectory add(Class serviceClass, Object service) {
+        services.putInstance(serviceClass, service);
+        return this;
+    }
+
+}
diff --git a/apps/pom.xml b/apps/pom.xml
index de25b9d..e08f306 100644
--- a/apps/pom.xml
+++ b/apps/pom.xml
@@ -33,6 +33,7 @@
 
     <modules>
         <module>aaa</module>
+        <module>acl</module>
         <module>fwd</module>
         <module>mobility</module>
         <module>proxyarp</module>