Initial contribution from https://git.io/vbHnC
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7224dcf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+/build
+/bin
+/target
+.classpath
+.project
+.settings
+/test-output/
+.DS_Store
+.springBeans
+data
+.idea
+*.ipr
+*.iml
+.acs-service-copy.jar
+api/target/
+samples/spring-mvc-api/target/
+service/pom.xml.versionsBackup
+pom.xml.versionsBackup
+model/pom.xml.versionsBackup
+commons/pom.xml.versionsBackup
+**/surefire~
+**/gotty-client*
+.acs.*
+xmlstarlet*
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5a68a72
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+## Access Control Service (ACS)
+
+For more information about Access Control Services, please read the following documentation:
+https://www.predix.io/docs#IGyNp2eM
+
+### LICENSE
+This project is licensed under Apache v2.
+
+### How to build ACS and run unit tests
+To build ACS without support for hierarchical attributes and run unit tests
+
+```
+mvn clean install [-P "public-titan"] [-s <maven_settings_file>]
+```
+Specify "public-titan" profile to build ACS with support for hierarchical attributes.
+
+### How to run ACS locally
+To run the service locally without support for hierarchical attributes
+
+```
+./service/start-acs-public.sh
+```
+To run the service locally with support for hierarchical attributes
+
+```
+./service/start-acs-public-titan.sh
+```
+
+The ACS service requires a UAA (User Account and Authentication: https://github.com/cloudfoundry/uaa) service to manage OAuth clients and users used in conjunction with ACS.
+When running ACS locally, by default the service is configured to trust the local UAA. You can modify the environment variable `ACS_DEFAULT_ISSUER_ID` and `UAA_CHECK_HEALTH_URL` to correspond to your existing UAA.
+
+### How to run UAA locally
+
+Clone the UAA repository from the following url
+
+```
+git clone https://github.com/cloudfoundry/uaa.git
+cd uaa
+./gradlew assemble -x javadoc
+./gradlew run -x javadoc --info
+```
+
+### How to run ACS integration tests
+
+The script below starts UAA and ACS, runs the tests, then stops the ACS and UAA services.
+
+```
+./run-integration-tests.sh [-t -s <maven_settings_file>]
+```
+
+Specify -t option to run integration tests against ACS that supports hierarchical attributes.
diff --git a/VERSIONING.md b/VERSIONING.md
new file mode 100644
index 0000000..dc7c06c
--- /dev/null
+++ b/VERSIONING.md
@@ -0,0 +1,21 @@
+## POM Versioning and Release Steps
+### Scenario:  
+* develop is at 2.3.0-SNAPSHOT
+* master is at 2.2.0
+* Next release version is 2.3.0
+
+### Steps
+* Create a branch off master, say release230
+* Merge develop to release230. Resolve any conflicts and commit.
+* Update POM version of release230 branch, to 2.3.0  (from 2.3.0-SNAPSHOT)
+  *  ```./versioning-acs.sh 2.3.0```
+  * Update the acs .jar path with new version in service/start-acs.sh and service/manifest.yml  
+  * Review diffs, run unit and integration tests
+  * Commit to release230
+  * Create a pull request from release230 to master
+  * ACS Pipeline changes - update *_VERSION variables in "shell build step" in acs-integration-master and  acs-release-master acs-spring-sec-integ-sample-master build plans.
+  * After review,  merge release230 to master. This will trigger the deployment to 'release' space.
+  * Create a lightweight tag for v2.3.0 in master. 
+* Update POM version in develop to 2.4.0-SNAPSHOT using steps similar to what were used for master, above.
+  * Update APP_VERSION variable in acs-integration-develop and acs-rc-develop jenkins plans. 
+ 
diff --git a/acs-integration-tests/.checkstyle b/acs-integration-tests/.checkstyle
new file mode 100644
index 0000000..f913c79
--- /dev/null
+++ b/acs-integration-tests/.checkstyle
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false">
+  <fileset name="all" enabled="true" check-config-name="Guardian's Checkstyle" local="false">
+    <file-match-pattern match-pattern="." include-pattern="true"/>
+  </fileset>
+</fileset-config>
diff --git a/acs-integration-tests/.gitignore b/acs-integration-tests/.gitignore
new file mode 100644
index 0000000..2f0a1ea
--- /dev/null
+++ b/acs-integration-tests/.gitignore
@@ -0,0 +1,14 @@
+/build
+/bin
+/target
+.classpath
+.project
+.settings
+/test-output/
+.DS_Store
+.springBeans
+.idea
+*.ipr
+*.iml
+/target/
+/failsafe-reports*/
diff --git a/acs-integration-tests/pom.xml b/acs-integration-tests/pom.xml
new file mode 100644
index 0000000..5040c0c
--- /dev/null
+++ b/acs-integration-tests/pom.xml
@@ -0,0 +1,345 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+<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/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>acs-integration-tests</artifactId>
+    <name>Predix Access Control Service Integration Tests</name>
+    <packaging>jar</packaging>
+
+    <parent>
+        <groupId>com.ge.predix</groupId>
+        <artifactId>acs</artifactId>
+        <version>5.0.3-SNAPSHOT</version>
+        <relativePath>../</relativePath>
+    </parent>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>${maven-surefire.version}</version>
+                <configuration>
+                    <!-- All tests in this project are run using failsafe. 
+                        Skip test phase -->
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <version>2.17</version>
+                <dependencies>
+                    <dependency>
+                        <groupId>com.puppycrawl.tools</groupId>
+                        <artifactId>checkstyle</artifactId>
+                        <version>7.5.1</version>
+                    </dependency>
+                </dependencies>
+                <executions>
+                    <execution>
+                        <id>validate</id>
+                        <phase>validate</phase>
+                        <configuration>
+                            <configLocation>../checkstyle-config/gog-sun-checks-eclipse.xml</configLocation>
+                            <encoding>UTF-8</encoding>
+                            <consoleOutput>true</consoleOutput>
+                            <failsOnError>true</failsOnError>
+                            <logViolationsToConsole>true</logViolationsToConsole>
+                            <linkXRef>false</linkXRef>
+                        </configuration>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <!-- run UAA -->
+            <plugin>
+                <groupId>org.codehaus.cargo</groupId>
+                <artifactId>cargo-maven2-plugin</artifactId>
+                <version>1.4.19</version>
+                <configuration>
+                    <skip>${maven.test.skip}</skip>
+                    <configuration>
+                        <home>${project.build.directory}/tomcat8x/container</home>
+                        <properties>
+                            <cargo.port.offset>${PORT_OFFSET}</cargo.port.offset>
+                        </properties>
+                    </configuration>
+                    <container>
+                        <containerId>tomcat8x</containerId>
+                        <systemProperties>
+                            <CLOUD_FOUNDRY_CONFIG_PATH>${project.basedir}/uaa/config</CLOUD_FOUNDRY_CONFIG_PATH>
+                        </systemProperties>
+                        <zipUrlInstaller>
+                            <url>http://archive.apache.org/dist/tomcat/tomcat-8/v8.5.23/bin/apache-tomcat-8.5.23.tar.gz</url>
+                            <downloadDir>${project.build.directory}/downloads</downloadDir>
+                            <extractDir>${project.build.directory}/extracts</extractDir>
+                        </zipUrlInstaller>
+                    </container>
+                    <deployables>
+                        <deployable>
+                            <groupId>org.cloudfoundry.identity</groupId>
+                            <artifactId>cloudfoundry-identity-uaa</artifactId>
+                            <type>war</type>
+                            <properties>
+                                <context>uaa</context>
+                            </properties>
+                        </deployable>
+                    </deployables>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>start-container</id>
+                        <phase>pre-integration-test</phase>
+                        <goals>
+                            <goal>start</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>stop-container</id>
+                        <phase>post-integration-test</phase>
+                        <goals>
+                            <goal>stop</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <version>${maven-failsafe.version}</version>
+                <configuration>
+                    <skipTests>false</skipTests>
+                    <parallel>false</parallel>
+                    <argLine>${custom-argline}</argLine>
+                    <properties>
+                        <property>
+                            <name>listener</name>
+                            <value>com.ge.predix.test.TestNameLogger</value>
+                        </property>
+                    </properties>
+                    <environmentVariables>
+                        <management.health.redis.enabled>false</management.health.redis.enabled>
+                        <uaaCheckHealthUrl>${ACS_UAA_URL}/healthz</uaaCheckHealthUrl>
+                        <cors.xhr.allowed.headers>Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method, Access-Control-Request-Headers</cors.xhr.allowed.headers>
+                        <cors.xhr.allowed.origins>^.*\.grc-apps\.svc\.ge\.com$,^.*\.predix\.io$</cors.xhr.allowed.origins>
+                        <cors.xhr.allowed.uris>^/v2/api-docs$</cors.xhr.allowed.uris>
+                        <cors.xhr.controlmaxage>1728000</cors.xhr.controlmaxage>
+                        <cors.xhr.allowed.methods>GET</cors.xhr.allowed.methods>
+                    </environmentVariables>
+                    <systemPropertyVariables>
+                        <ACS_SERVICE_ID>predix-acs</ACS_SERVICE_ID>
+                        <ACS_URL>${ACS_URL}</ACS_URL>
+                        <ACS_DEFAULT_ISSUER_ID>${ACS_UAA_URL}/oauth/token</ACS_DEFAULT_ISSUER_ID>
+
+                        <!-- proxy settings -->
+                        <http.nonProxyHosts>${NON_PROXY_HOSTS}</http.nonProxyHosts>
+                        <https.proxyHost>${HTTPS_PROXY_HOST}</https.proxyHost>
+                        <https.proxyPort>${HTTPS_PROXY_PORT}</https.proxyPort>
+                    </systemPropertyVariables>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>integ-test</id>
+                        <goals>
+                            <goal>integration-test</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>verify</id>
+                        <goals>
+                            <goal>verify</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.ge.predix</groupId>
+            <artifactId>acs-commons</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>io.springfox</groupId>
+                    <artifactId>springfox-swagger-ui</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>io.springfox</groupId>
+                    <artifactId>springfox-swagger2</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>io.swagger</groupId>
+                    <artifactId>swagger-annotations</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.ge.predix</groupId>
+            <artifactId>acs-model</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>io.springfox</groupId>
+                    <artifactId>springfox-swagger-ui</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>io.springfox</groupId>
+                    <artifactId>springfox-swagger2</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>io.swagger</groupId>
+                    <artifactId>swagger-annotations</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.ge.predix</groupId>
+            <artifactId>acs-service</artifactId>
+            <version>5.0.3-SNAPSHOT</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>io.netty</groupId>
+                    <artifactId>netty-all</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security.oauth</groupId>
+            <artifactId>spring-security-oauth2</artifactId>
+            <!-- These transitive dependencies are incompatible with spring 
+                core dependencies pulled in by spring boot -->
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-beans</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-context</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>info.cukes</groupId>
+            <artifactId>cucumber-java</artifactId>
+            <version>${cucumber.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>info.cukes</groupId>
+            <artifactId>cucumber-testng</artifactId>
+            <version>${cucumber.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>info.cukes</groupId>
+            <artifactId>cucumber-spring</artifactId>
+            <version>${cucumber.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-integration</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>ch.qos.logback</groupId>
+                    <artifactId>logback-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>log4j-over-slf4j</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.cloudfoundry.identity</groupId>
+            <artifactId>cloudfoundry-identity-uaa</artifactId>
+            <version>4.6.1</version>
+            <type>war</type>
+        </dependency>
+    </dependencies>
+
+    <repositories>
+        <repository>
+            <id>central</id>
+            <name>Maven Repository Switchboard</name>
+            <url>http://repo1.maven.org/maven2</url>
+        </repository>
+    </repositories>
+
+    <profiles>
+        <profile>
+            <id>public</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-failsafe-plugin</artifactId>
+                        <version>${maven-failsafe.version}</version>
+                        <configuration>
+                            <environmentVariables>
+                                <SPRING_PROFILES_ACTIVE>h2,public,simple-cache,httpValidation</SPRING_PROFILES_ACTIVE>
+                            </environmentVariables>
+                            <reportsDirectory>${basedir}/failsafe-reports-public</reportsDirectory>
+                            <summaryFile>${basedir}/failsafe-reports-public/failsafe-summary.xml</summaryFile>
+                            <suiteXmlFiles>
+                                <suiteXmlFile>testng-public.xml</suiteXmlFile>
+                            </suiteXmlFiles>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <profile>
+            <id>public-titan</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-failsafe-plugin</artifactId>
+                        <version>${maven-failsafe.version}</version>
+                        <configuration>
+                            <environmentVariables>
+                                <SPRING_PROFILES_ACTIVE>h2,public,simple-cache,titan,httpValidation</SPRING_PROFILES_ACTIVE>
+                            </environmentVariables>
+                            <reportsDirectory>${basedir}/failsafe-reports-public-titan</reportsDirectory>
+                            <summaryFile>${basedir}/failsafe-reports-public-titan/failsafe-summary.xml</summaryFile>
+                            <suiteXmlFiles>
+                                <suiteXmlFile>testng-public-titan.xml</suiteXmlFile>
+                            </suiteXmlFiles>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
+</project>
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/ACSAcceptanceIT.java b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/ACSAcceptanceIT.java
new file mode 100644
index 0000000..c3a114d
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/ACSAcceptanceIT.java
@@ -0,0 +1,212 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acceptance.test;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.util.Arrays;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.Environment;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestTemplate;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.commons.web.AcsApiUriTemplates;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+import com.ge.predix.test.utils.ACSITSetUpFactory;
+import com.ge.predix.test.utils.PolicyHelper;
+import com.ge.predix.test.utils.PrivilegeHelper;
+
+/**
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings({ "nls" })
+@ContextConfiguration("classpath:acceptance-test-spring-context.xml")
+public class ACSAcceptanceIT extends AbstractTestNGSpringContextTests {
+
+    private String acsBaseUrl;
+
+    @Autowired
+    private Environment environment;
+
+    @Autowired
+    private PrivilegeHelper privilegeHelper;
+
+    @Autowired
+    private PolicyHelper policyHelper;
+
+    @Autowired
+    private ACSITSetUpFactory acsitSetUpFactory;
+
+    private OAuth2RestTemplate acsZoneRestTemplate;
+
+    private HttpHeaders headersWithZoneSubdomain;
+
+    @BeforeClass
+    public void setup() throws IOException {
+        this.acsitSetUpFactory.setUp();
+        this.headersWithZoneSubdomain = this.acsitSetUpFactory.getZone1Headers();
+        this.acsZoneRestTemplate = this.acsitSetUpFactory.getAcsZoneAdminRestTemplate();
+        this.acsBaseUrl = this.acsitSetUpFactory.getAcsUrl();
+
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Test
+    public void testAcsHealth() {
+
+        RestTemplate restTemplate = new RestTemplate();
+        try {
+            ResponseEntity<String> heartbeatResponse = restTemplate.exchange(
+                    this.acsBaseUrl + AcsApiUriTemplates.HEARTBEAT_URL, HttpMethod.GET,
+                    new HttpEntity<>(this.headersWithZoneSubdomain), String.class);
+            Assert.assertEquals(heartbeatResponse.getBody(), "alive", "ACS Heartbeat Check Failed");
+        } catch (Exception e) {
+            Assert.fail("Could not perform ACS Heartbeat Check: " + e.getMessage());
+        }
+
+        try {
+            ResponseEntity<Map> healthStatus = restTemplate.exchange(this.acsBaseUrl + "/health", HttpMethod.GET,
+                    new HttpEntity<>(this.headersWithZoneSubdomain), Map.class);
+            Assert.assertNotNull(healthStatus);
+            Assert.assertEquals(healthStatus.getBody().size(), 1);
+            String acsStatus = (String) healthStatus.getBody().get("status");
+            Assert.assertEquals(acsStatus, "UP", "ACS Health Check Failed: " + acsStatus);
+        } catch (Exception e) {
+            Assert.fail("Could not perform ACS Health Check: " + e.getMessage());
+        }
+
+    }
+
+    @Test(dataProvider = "endpointProvider")
+    public void testCompleteACSFlow(final String endpoint, final HttpHeaders headers,
+            final PolicyEvaluationRequestV1 policyEvalRequest, final String subjectIdentifier) throws Exception {
+
+        String testPolicyName = null;
+        BaseSubject marissa = null;
+        BaseResource testResource = null;
+        try {
+            testPolicyName = this.policyHelper.setTestPolicy(this.acsZoneRestTemplate, headers, endpoint,
+                    "src/test/resources/testCompleteACSFlow.json");
+            BaseSubject subject = new BaseSubject(subjectIdentifier);
+            Attribute site = new Attribute();
+            site.setIssuer("issuerId1");
+            site.setName("site");
+            site.setValue("sanramon");
+
+            marissa = this.privilegeHelper.putSubject(this.acsZoneRestTemplate, subject, endpoint, headers, site);
+
+            Attribute region = new Attribute();
+            region.setIssuer("issuerId1");
+            region.setName("region");
+            region.setValue("testregion"); // test policy asserts on this value
+
+            BaseResource resource = new BaseResource();
+            resource.setResourceIdentifier("/alarms/sites/sanramon");
+
+            testResource = this.privilegeHelper.putResource(this.acsZoneRestTemplate, resource, endpoint, headers,
+                    region);
+
+            ResponseEntity<PolicyEvaluationResult> evalResponse = this.acsZoneRestTemplate.postForEntity(
+                    endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH, new HttpEntity<>(policyEvalRequest, headers),
+                    PolicyEvaluationResult.class);
+
+            Assert.assertEquals(evalResponse.getStatusCode(), HttpStatus.OK);
+            PolicyEvaluationResult responseBody = evalResponse.getBody();
+            Assert.assertEquals(responseBody.getEffect(), Effect.PERMIT);
+        } finally {
+            // delete policy
+            if (null != testPolicyName) {
+                this.acsZoneRestTemplate.exchange(endpoint + PolicyHelper.ACS_POLICY_SET_API_PATH + testPolicyName,
+                        HttpMethod.DELETE, new HttpEntity<>(headers), String.class);
+            }
+
+            // delete attributes
+            if (null != marissa) {
+                this.acsZoneRestTemplate.exchange(
+                        endpoint + PrivilegeHelper.ACS_SUBJECT_API_PATH + marissa.getSubjectIdentifier(),
+                        HttpMethod.DELETE, new HttpEntity<>(headers), String.class);
+            }
+            if (null != testResource) {
+                String encodedResource = URLEncoder.encode(testResource.getResourceIdentifier(), "UTF-8");
+                URI uri = new URI(endpoint + PrivilegeHelper.ACS_RESOURCE_API_PATH + encodedResource);
+                this.acsZoneRestTemplate.exchange(uri, HttpMethod.DELETE, new HttpEntity<>(headers), String.class);
+            }
+        }
+    }
+
+    @DataProvider(name = "endpointProvider")
+    public Object[][] getAcsEndpoint() throws Exception {
+        PolicyEvaluationRequestV1 policyEvalForBob = this.policyHelper.createEvalRequest("GET", "bob",
+                "/alarms/sites/sanramon", null);
+
+        return new Object[][] { { this.acsBaseUrl, this.headersWithZoneSubdomain, policyEvalForBob, "bob" } };
+    }
+
+    private ResponseEntity<String> getMonitoringApiResponse(final HttpHeaders headers) {
+        return new RestTemplate().exchange(URI.create(this.acsBaseUrl + AcsApiUriTemplates.HEARTBEAT_URL),
+                HttpMethod.GET, new HttpEntity<>(headers), String.class);
+    }
+
+    // TODO: Remove this test when the "httpValidation" Spring profile is removed
+    @Test
+    public void testHttpValidationBasedOnActiveSpringProfile() throws Exception {
+        HttpHeaders headers = new HttpHeaders();
+        headers.add(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE);
+
+        if (!Arrays.asList(this.environment.getActiveProfiles()).contains("httpValidation")) {
+            Assert.assertEquals(this.getMonitoringApiResponse(headers).getStatusCode(), HttpStatus.OK);
+            return;
+        }
+
+        try {
+            this.getMonitoringApiResponse(headers);
+            Assert.fail("Expected an HttpMediaTypeNotAcceptableException exception to be thrown");
+        } catch (final HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.NOT_ACCEPTABLE);
+        }
+    }
+
+    @AfterClass
+    public void tearDown() {
+        this.acsitSetUpFactory.destroy();
+    }
+
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/policy/admin/PolicyCreationCucumberTest.java b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/policy/admin/PolicyCreationCucumberTest.java
new file mode 100644
index 0000000..71d60e5
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/policy/admin/PolicyCreationCucumberTest.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acceptance.test.policy.admin;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ge.predix.test.TestConfig;
+
+import cucumber.api.CucumberOptions;
+import cucumber.api.testng.AbstractTestNGCucumberTests;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@Test
+@CucumberOptions(tags = "~@ignore")
+public class PolicyCreationCucumberTest extends AbstractTestNGCucumberTests {
+    // Used as the entry point for PolicyEvaluation StepsDefinitions
+
+    @BeforeClass
+    public void setup() throws Exception {
+        TestConfig.setupForEclipse(); // Starts ACS when running the test in eclipse.
+    }
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/policy/admin/PolicyCreationStepsDefinitions.java b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/policy/admin/PolicyCreationStepsDefinitions.java
new file mode 100644
index 0000000..12458a4
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/policy/admin/PolicyCreationStepsDefinitions.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acceptance.test.policy.admin;
+
+import static com.ge.predix.test.utils.PrivilegeHelper.DEFAULT_SUBJECT_ID;
+
+import java.io.IOException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.Environment;
+import org.testng.Assert;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.ge.predix.test.utils.ACSITSetUpFactory;
+import com.ge.predix.test.utils.CreatePolicyStatus;
+import com.ge.predix.test.utils.PolicyHelper;
+import com.ge.predix.test.utils.PrivilegeHelper;
+
+import cucumber.api.java.After;
+import cucumber.api.java.Before;
+import cucumber.api.java.en.Given;
+import cucumber.api.java.en.Then;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+// CHECKSTYLE:OFF
+// Turning checkstyle off because the way these cucumber tests are named do not conform to the checkstyle rules.
+@SuppressWarnings({ "nls" })
+public class PolicyCreationStepsDefinitions {
+
+    @Autowired
+    private PolicyHelper policyHelper;
+
+    @Autowired
+    private PrivilegeHelper privilegeHelper;
+
+    @Autowired
+    Environment env;
+
+    @Autowired
+    private ACSITSetUpFactory acsitSetUpFactory;
+
+    private String testPolicyName;
+
+    CreatePolicyStatus status;
+
+    @Before
+    public void setup() throws JsonParseException, JsonMappingException, IOException {
+        this.acsitSetUpFactory.setUp();
+    }
+
+    @Given("^A policy with no action defined$")
+    public void a_policy_with_no_action_defined() throws Throwable {
+        this.testPolicyName = "no-defined-action-policy-set";
+        this.status = this.policyHelper.createPolicySet("src/test/resources/no-defined-action-policy-set.json",
+                this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(), this.acsitSetUpFactory.getZone1Headers());
+    }
+
+    @Given("^A policy with single valid action defined$")
+    public void a_policy_with_single_valid_action_defined() throws Throwable {
+        this.testPolicyName = "single-action-defined-policy-set";
+        this.status = this.policyHelper.createPolicySet("src/test/resources/single-action-defined-policy-set.json",
+                this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(), this.acsitSetUpFactory.getZone1Headers());
+    }
+
+    @Given("^A policy with multiple valid actions defined$")
+    public void a_policy_with_multiple_valid_actions_defined() throws Throwable {
+        this.testPolicyName = "multiple-actions-defined-policy-set";
+        this.status = this.policyHelper.createPolicySet("src/test/resources/multiple-actions-defined-policy-set.json",
+                this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(), this.acsitSetUpFactory.getZone1Headers());
+    }
+
+    @Then("^the policy creation returns (.*)$")
+    public void the_policy_creation_returns(final String effect) throws Throwable {
+        Assert.assertEquals(this.status.toString(), effect);
+    }
+
+    @Given("^A policy with single invalid action defined")
+    public void policy_with_single_invalid_action_defined() throws Throwable {
+        this.testPolicyName = "single-invalid-action-defined-policy-set";
+        this.status = this.policyHelper.createPolicySet(
+                "src/test/resources/single-invalid-action-defined-policy-set.json",
+                this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(), this.acsitSetUpFactory.getZone1Headers());
+    }
+
+    @Given("^A policy with multiple actions containing one invalid action defined")
+    public void policy_with_multiple_actions_containing_one_invalid_action_defined() throws Throwable {
+        this.testPolicyName = "multiple-actions-with-single-invalid-action-defined-policy-set";
+        this.status = this.policyHelper.createPolicySet(
+                "src/test/resources/multiple-actions-with-single-invalid-action-defined-policy-set.json",
+                this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(), this.acsitSetUpFactory.getZone1Headers());
+    }
+
+    @After
+    public void cleanAfterScenario() throws Exception {
+        String acsBaseUrl = this.acsitSetUpFactory.getAcsUrl();
+        this.policyHelper.deletePolicySet(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(), acsBaseUrl,
+                this.testPolicyName, this.acsitSetUpFactory.getZone1Headers());
+        this.privilegeHelper.deleteSubject(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(), acsBaseUrl,
+                DEFAULT_SUBJECT_ID, this.acsitSetUpFactory.getZone1Headers());
+        this.acsitSetUpFactory.destroy();
+    }
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/policy/evaluation/PolicyEvaluationCucumberTest.java b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/policy/evaluation/PolicyEvaluationCucumberTest.java
new file mode 100644
index 0000000..269067e
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/policy/evaluation/PolicyEvaluationCucumberTest.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acceptance.test.policy.evaluation;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ge.predix.test.TestConfig;
+
+import cucumber.api.CucumberOptions;
+import cucumber.api.testng.AbstractTestNGCucumberTests;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@Test
+@CucumberOptions
+public class PolicyEvaluationCucumberTest extends AbstractTestNGCucumberTests {
+    // Used as the entry point for PolicyEvaluation StepsDefinitions
+
+    @BeforeClass
+    public void setup() {
+        TestConfig.setupForEclipse(); // Starts ACS when running the test in eclipse.
+    }
+
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/policy/evaluation/PolicyEvaluationStepsDefinitions.java b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/policy/evaluation/PolicyEvaluationStepsDefinitions.java
new file mode 100644
index 0000000..8224b3c
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/policy/evaluation/PolicyEvaluationStepsDefinitions.java
@@ -0,0 +1,269 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acceptance.test.policy.evaluation;
+
+import static com.ge.predix.test.utils.PolicyHelper.DEFAULT_ACTION;
+import static com.ge.predix.test.utils.PolicyHelper.NOT_MATCHING_ACTION;
+import static com.ge.predix.test.utils.PrivilegeHelper.DEFAULT_ATTRIBUTE_ISSUER;
+import static com.ge.predix.test.utils.PrivilegeHelper.DEFAULT_RESOURCE_IDENTIFIER;
+import static com.ge.predix.test.utils.PrivilegeHelper.DEFAULT_SUBJECT_ID;
+import static com.ge.predix.test.utils.PrivilegeHelper.DEFAULT_SUBJECT_IDENTIFIER;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.Environment;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.testng.Assert;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+import com.ge.predix.test.utils.ACSITSetUpFactory;
+import com.ge.predix.test.utils.PolicyHelper;
+import com.ge.predix.test.utils.PrivilegeHelper;
+
+import cucumber.api.java.After;
+import cucumber.api.java.Before;
+import cucumber.api.java.en.Given;
+import cucumber.api.java.en.Then;
+import cucumber.api.java.en.When;
+
+/**
+ * BDD tests for Policy Evaluation service.
+ *
+ * @author acs-engineers@ge.com
+ */
+// CHECKSTYLE:OFF
+// Turning checkstyle off because the way these cucumber tests are named do not conform to the checkstyle rules.
+@SuppressWarnings({ "nls" })
+public class PolicyEvaluationStepsDefinitions extends AbstractTestNGSpringContextTests {
+
+    @Autowired
+    private PolicyHelper policyHelper;
+
+    @Autowired
+    private PrivilegeHelper privilegeHelper;
+
+    @Autowired
+    Environment env;
+    
+    @Autowired
+    private ACSITSetUpFactory acsitSetUpFactory;
+
+    private String testPolicyName;
+    private ResponseEntity<PolicyEvaluationResult> policyEvaluationResponse;
+    private String acsUrl;
+    private HttpHeaders zone1Headers;
+    private OAuth2RestTemplate acsAdminRestTemplate;
+
+
+    @Before
+    public void setup() throws JsonParseException, JsonMappingException, IOException {
+        this.acsitSetUpFactory.setUp();
+        this.acsUrl = this.acsitSetUpFactory.getAcsUrl();
+        this.zone1Headers =this.acsitSetUpFactory.getZone1Headers();
+        this.acsAdminRestTemplate=this.acsitSetUpFactory.getAcsZoneAdminRestTemplate();      
+    }
+
+    /*
+     * Scenario: policy evaluation request which returns permit Given I have a Given A policy set that allows access
+     * to all When Any evaluation request Then policy evaluation returns PERMIT
+     */
+    @Given("^A policy set that allows access to all$")
+    public void policyDefinitionAllowsAccessToAll() throws Throwable {
+        this.testPolicyName = this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.zone1Headers, this.acsUrl,
+                "src/test/resources/permit-all.json");
+    }
+
+    /*
+     * Scenario: policy evaluation request which returns deny Given A policy set that allows access to none When Any
+     * evaluation request Then policy evaluation returns DENY
+     */
+    @Given("^A policy set that allows access to none$")
+    public void policyDefinitionAllowsAccessToNone() throws Throwable {
+        this.testPolicyName = this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.zone1Headers, this.acsUrl,
+                "src/test/resources/deny-all.json");
+    }
+
+    @When("^Any evaluation request$")
+    public void anyEvaluationRequest() throws Throwable {
+        this.policyEvaluationResponse = this.policyHelper.sendEvaluationRequest(this.acsAdminRestTemplate,
+                this.zone1Headers, this.policyHelper.createRandomEvalRequest());
+    }
+
+    @Then("^policy evaluation returns (.*)$")
+    public void policyEvaluationReturns(final String effect) throws Throwable {
+        Assert.assertEquals(this.policyEvaluationResponse.getStatusCode(), HttpStatus.OK);
+        PolicyEvaluationResult responseBody = this.policyEvaluationResponse.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.valueOf(effect));
+    }
+
+    @Then("^policy evaluation response includes subject attribute (.*) with the value (.*)$")
+    public void policyEvaluationReturns(final String attributeName, final String attributeValue) throws Throwable {
+        Assert.assertEquals(this.policyEvaluationResponse.getStatusCode(), HttpStatus.OK);
+        Set<Attribute> subjectAttributes = new HashSet<Attribute>(
+                this.policyEvaluationResponse.getBody().getSubjectAttributes());
+        Assert.assertTrue(
+                subjectAttributes.contains(new Attribute(DEFAULT_ATTRIBUTE_ISSUER, attributeName, attributeValue)),
+                String.format("Subject Attributes expected to include attribute = (%s, %s, %s)",
+                        DEFAULT_ATTRIBUTE_ISSUER, attributeName, attributeValue));
+    }
+
+    @After
+    public void cleanAfterScenario() throws Exception {
+        this.policyHelper.deletePolicySet(this.acsAdminRestTemplate, this.acsUrl, this.testPolicyName,
+                this.zone1Headers);
+        this.privilegeHelper.deleteSubject(this.acsAdminRestTemplate, this.acsUrl, DEFAULT_SUBJECT_ID,
+                this.zone1Headers);
+        this.acsitSetUpFactory.destroy();
+    }
+
+    /*
+     * Scenario: policy evaluation request which returns deny Given A policy set that allows access only to subject
+     * with role administrator When Evaluation request which has the subject attribute role with the value
+     * administrator Then policy evaluation returns PERMIT
+     */
+    @Given("^A policy set that allows access only to subject with role (.*) using condition$")
+    public void policyDefinitionAllowsAccessToOnlyAdminsUsingCondition(final String subjectAttribute) throws Throwable {
+        this.testPolicyName = this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.zone1Headers, this.acsUrl,
+                "src/test/resources/permit-admin-only-using-condition-policy-set.json");
+    }
+
+    @When("^Evaluation request which has the subject attribute role with the value (.*)$")
+    public void evaluationRequestWithSubjectAttribute(final String subjectAttribute) throws Throwable {
+
+        Set<Attribute> subjectAttributes = new HashSet<Attribute>();
+        subjectAttributes.add(new Attribute(DEFAULT_ATTRIBUTE_ISSUER, "role", subjectAttribute));
+        this.policyEvaluationResponse = this.policyHelper.sendEvaluationRequest(this.acsAdminRestTemplate,
+                this.zone1Headers, this.policyHelper.createEvalRequest(DEFAULT_ACTION, DEFAULT_SUBJECT_ID,
+                        DEFAULT_RESOURCE_IDENTIFIER, subjectAttributes));
+    }
+
+    @When("^Evaluation request for resource (.*) which has the subject attribute (.*) with the value (.*)$")
+    public void evaluationRequestForResourceWithSubjectAttribute(final String resourceIdentifier,
+            final String attributeName, final String attributeValue) throws Throwable {
+
+        Set<Attribute> subjectAttributes = new HashSet<Attribute>();
+        subjectAttributes.add(new Attribute(DEFAULT_ATTRIBUTE_ISSUER, attributeName, attributeValue));
+        this.policyEvaluationResponse = this.policyHelper.sendEvaluationRequest(this.acsAdminRestTemplate,
+                this.zone1Headers, this.policyHelper.createEvalRequest(DEFAULT_ACTION, DEFAULT_SUBJECT_ID,
+                        resourceIdentifier, subjectAttributes));
+    }
+
+    /*
+     * Given A policy set that allows access only to subject with role administrator using matcher*
+     */
+    @Given("^A policy set that allows access only to subject with role .* using matcher$")
+    public void policyDefinitionAllowsAccessToOnlyAdminsUsingMatcher() throws Throwable {
+        this.testPolicyName = this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.zone1Headers, this.acsUrl,
+                "src/test/resources/permit-admin-only-using-matcher-policy-set.json");
+    }
+
+    @Given("^ACS has subject attribute (.*) with value (.*) for the subject$")
+    public void acs_has_subject_attribute(final String attributeName, final String value) throws Throwable {
+        this.privilegeHelper.putSubject(this.acsAdminRestTemplate, DEFAULT_SUBJECT_IDENTIFIER, this.zone1Headers,
+                new Attribute(DEFAULT_ATTRIBUTE_ISSUER, attributeName, value));
+    }
+
+    @When("^Evaluation request which has no subject attribute$")
+    public void evaluation_request_which_has_no_subject_attribute() throws Throwable {
+        Set<Attribute> subjectAttributes = Collections.emptySet();
+        this.policyEvaluationResponse = this.policyHelper.sendEvaluationRequest(this.acsAdminRestTemplate,
+                this.zone1Headers, this.policyHelper.createEvalRequest(DEFAULT_ACTION, DEFAULT_SUBJECT_ID,
+                        DEFAULT_RESOURCE_IDENTIFIER, subjectAttributes));
+    }
+
+    @Given("^A policy set that allows access only to subject with role administrator and site sanramon$")
+    public void a_policy_set_that_allows_access_only_to_subject_with_role_administrator_and_site_sanramon()
+            throws Throwable {
+        this.testPolicyName = this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.zone1Headers, this.acsUrl,
+                "src/test/resources/permit-admin-with-site-access-matcher-and-condition.json");
+    }
+
+    @Given("^an existing policy with no defined action$")
+    public void an_existing_policy_with_no_defined_action() throws Throwable {
+        this.testPolicyName = this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.zone1Headers, this.acsUrl,
+                "src/test/resources/no-defined-action-policy-set.json");
+    }
+
+    @Given("^an existing policy set stored in ACS with multiple actions$")
+    public void an_existing_policy_set_stored_in_ACS_with_multiple_actions() throws Throwable {
+        this.testPolicyName = this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.zone1Headers, this.acsUrl,
+                "src/test/resources/multiple-actions-defined-policy-set.json");
+    }
+
+    @Given("^an existing policy set stored in ACS with a single action$")
+    public void an_existing_policy_set_stored_in_ACS_with_a_single_action() throws Throwable {
+        this.testPolicyName = this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.zone1Headers, this.acsUrl,
+                "src/test/resources/single-action-defined-policy-set.json");
+    }
+
+    @Given("^an existing policy with empty defined action$")
+    public void an_existing_policy_with_empty_defined_action() throws Throwable {
+        this.testPolicyName = this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.zone1Headers, this.acsUrl,
+                "src/test/resources/single-empty-action-defined-policy-set.json");
+    }
+
+    @Given("^an existing policy with null defined action$")
+    public void an_existing_policy_with_null_defined_action() throws Throwable {
+        this.testPolicyName = this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.zone1Headers, this.acsUrl,
+                "src/test/resources/single-null-action-defined-policy-set.json");
+    }
+
+    @When("^A policy evaluation is requested with any HTTP action$")
+    public void a_policy_evaluation_is_requested_with_any_HTTP_action() throws Throwable {
+        Set<Attribute> subjectAttributes = Collections.emptySet();
+        this.policyEvaluationResponse = this.policyHelper.sendEvaluationRequest(this.acsAdminRestTemplate,
+                this.zone1Headers, this.policyHelper.createEvalRequest(DEFAULT_ACTION, DEFAULT_SUBJECT_ID,
+                        DEFAULT_RESOURCE_IDENTIFIER, subjectAttributes));
+    }
+
+    @When("^A policy evaluation is requested with an HTTP action matching .*$")
+    public void a_policy_evaluation_is_requested_with_an_HTTP_action_matching() throws Throwable {
+        Set<Attribute> subjectAttributes = Collections.emptySet();
+        this.policyEvaluationResponse = this.policyHelper.sendEvaluationRequest(this.acsAdminRestTemplate,
+                this.zone1Headers, this.policyHelper.createEvalRequest(DEFAULT_ACTION, DEFAULT_SUBJECT_ID,
+                        DEFAULT_RESOURCE_IDENTIFIER, subjectAttributes));
+    }
+
+    @When("^A policy evaluation is requested with an HTTP action not matching .*$")
+    public void a_policy_evaluation_is_requested_with_an_HTTP_action_not_matching() throws Throwable {
+        Set<Attribute> subjectAttributes = Collections.emptySet();
+        this.policyEvaluationResponse = this.policyHelper.sendEvaluationRequest(this.acsAdminRestTemplate,
+                this.zone1Headers, this.policyHelper.createEvalRequest(NOT_MATCHING_ACTION, DEFAULT_SUBJECT_ID,
+                        DEFAULT_RESOURCE_IDENTIFIER, subjectAttributes));
+    }
+
+    @When("^Evaluation request which has subject attribute which are null$")
+    public void evaluation_request_which_has_subject_attribute_which_are_null() throws Throwable {
+        this.policyEvaluationResponse = this.policyHelper.sendEvaluationRequest(this.acsAdminRestTemplate,
+                this.zone1Headers, this.policyHelper.createEvalRequest(NOT_MATCHING_ACTION, DEFAULT_SUBJECT_ID,
+                        DEFAULT_RESOURCE_IDENTIFIER, null));
+    }
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/zone/admin/DefaultZoneAuthorizationIT.java b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/zone/admin/DefaultZoneAuthorizationIT.java
new file mode 100644
index 0000000..15b2980
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/zone/admin/DefaultZoneAuthorizationIT.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acceptance.test.zone.admin;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.web.client.HttpClientErrorException;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.test.utils.ACSITSetUpFactory;
+import com.ge.predix.test.utils.ACSTestUtil;
+import com.ge.predix.test.utils.PolicyHelper;
+import com.ge.predix.test.utils.PrivilegeHelper;
+
+@Test
+@ContextConfiguration("classpath:integration-test-spring-context.xml")
+public class DefaultZoneAuthorizationIT extends AbstractTestNGSpringContextTests {
+
+    @Autowired
+    private PrivilegeHelper privilegeHelper;
+    @Autowired
+    private ACSITSetUpFactory acsitSetUpFactory;
+
+    private String zone2Name;
+
+    @BeforeClass
+    public void setup() throws Exception {
+        this.acsitSetUpFactory.setUp();
+        this.zone2Name = this.acsitSetUpFactory.getZone2().getName();
+    }
+
+    @AfterClass
+    public void cleanup() {
+        this.acsitSetUpFactory.destroy();
+        
+    }
+
+    /**
+     * 1. Create a token from zone issuer with scopes for accessing: a. zone specific resources, AND b.
+     * acs.zones.admin
+     *
+     * 2. Try to access a zone specific resource . This should work 3. Try to access /v1/zone - THIS SHOULD FAIL
+     *
+     * @throws Exception
+     */
+    public void testAccessGlobalResourceWithZoneIssuer() throws Exception {
+        OAuth2RestTemplate zone2AcsTemplate = this.acsitSetUpFactory.getAcsZone2AdminRestTemplate();
+
+        HttpHeaders zoneTwoHeaders = ACSTestUtil.httpHeaders();
+        zoneTwoHeaders.set(PolicyHelper.PREDIX_ZONE_ID, this.zone2Name);
+
+        // Write a resource to zone2. This should work
+        ResponseEntity<Object> responseEntity = this.privilegeHelper.postResources(zone2AcsTemplate,
+                this.acsitSetUpFactory.getAcsUrl(), zoneTwoHeaders, new BaseResource("/sites/sanramon"));
+        Assert.assertEquals(responseEntity.getStatusCode(), HttpStatus.NO_CONTENT);
+
+        // Try to get global resource from global/baseUrl. This should FAIL
+        try {
+            zone2AcsTemplate.exchange(this.acsitSetUpFactory.getAcsUrl() + "/v1/zone/" + this.zone2Name, HttpMethod.GET,
+                    null, Zone.class);
+            Assert.fail("Able to access non-zone specific resource with a zone specific issuer token!");
+        } catch (HttpClientErrorException e) {
+            // expected
+        }
+
+        // Try to get global resource from zone2Url. This should FAIL
+        try {
+            zone2AcsTemplate.exchange(this.acsitSetUpFactory.getAcsUrl() + "/v1/zone/" + this.zone2Name, HttpMethod.GET,
+                    new HttpEntity<>(zoneTwoHeaders), Zone.class);
+            Assert.fail("Able to access non-zone specific resource from a zone specific URL, "
+                    + "with a zone specific issuer token!");
+        } catch (InvalidRequestException e) {
+            // expected
+        }
+
+    }
+
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/zone/admin/ZoneEnforcementCucumberTest.java b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/zone/admin/ZoneEnforcementCucumberTest.java
new file mode 100644
index 0000000..42d9270
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/zone/admin/ZoneEnforcementCucumberTest.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acceptance.test.zone.admin;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ge.predix.test.TestConfig;
+
+import cucumber.api.CucumberOptions;
+import cucumber.api.testng.AbstractTestNGCucumberTests;
+
+@Test
+@CucumberOptions(tags = "~@ignore")
+public class ZoneEnforcementCucumberTest extends AbstractTestNGCucumberTests {
+    // Used as the entry point for ZoneCreation StepsDefinitions
+
+    @BeforeClass
+    public void setup() {
+        TestConfig.setupForEclipse(); // Starts ACS when running the test in eclipse.
+    }
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/zone/admin/ZoneEnforcementStepsDefinitions.java b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/zone/admin/ZoneEnforcementStepsDefinitions.java
new file mode 100644
index 0000000..2d20edc
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/acceptance/test/zone/admin/ZoneEnforcementStepsDefinitions.java
@@ -0,0 +1,302 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acceptance.test.zone.admin;
+
+import static com.ge.predix.test.utils.ACSTestUtil.ACS_VERSION;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URLEncoder;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.Environment;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.security.oauth2.client.http.AccessTokenRequiredException;
+import org.springframework.web.client.HttpClientErrorException;
+import org.testng.Assert;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.test.utils.ACSITSetUpFactory;
+import com.ge.predix.test.utils.ACSTestUtil;
+import com.ge.predix.test.utils.CreatePolicyStatus;
+import com.ge.predix.test.utils.PolicyHelper;
+import com.ge.predix.test.utils.PrivilegeHelper;
+
+import cucumber.api.java.After;
+import cucumber.api.java.Before;
+import cucumber.api.java.en.Given;
+import cucumber.api.java.en.Then;
+import cucumber.api.java.en.When;
+
+//CHECKSTYLE:OFF
+//Turning checkstyle off because the way these cucumber tests are named do not conform to the checkstyle rules.
+public class ZoneEnforcementStepsDefinitions {
+
+    private String zone1Name;
+    private String zone2Name;
+
+    @Autowired
+    private PolicyHelper policyHelper;
+
+    @Autowired
+    private PrivilegeHelper privilegeHelper;
+
+    @Autowired
+    private ACSITSetUpFactory acsitSetUpFactory;
+
+    @Autowired
+    Environment env;
+
+    private String acsUrl;
+
+    private HttpHeaders zone1Headers;
+
+    private final BaseSubject subject = new BaseSubject("subject_id_1");
+
+    private final BaseResource resource = new BaseResource("resource_id_1");
+
+    private ResponseEntity<BaseSubject> responseEntity = null;
+
+    private ResponseEntity<BaseResource> responseEntityForResource = null;
+
+    private int status;
+
+    private String testPolicyName;
+
+    private ResponseEntity<PolicySet> policyset;
+    private OAuth2RestTemplate acsZone1Template;
+    private OAuth2RestTemplate acsZone2Template;
+
+    @Before
+    public void setup() throws JsonParseException, JsonMappingException, IOException {
+        this.acsitSetUpFactory.setUp();
+        this.acsUrl = this.acsitSetUpFactory.getAcsUrl();
+        this.zone1Headers = this.acsitSetUpFactory.getZone1Headers();
+        this.acsZone1Template = this.acsitSetUpFactory.getAcsZoneAdminRestTemplate();
+        this.acsZone2Template = this.acsitSetUpFactory.getAcsZone2AdminRestTemplate();
+        this.zone1Name = this.acsitSetUpFactory.getZone1().getName();
+        this.zone2Name = this.acsitSetUpFactory.getZone2().getName();
+    }
+
+    @Given("^zone 1 and zone (.*?)")
+    public void given_zone_1_and_zone(final String subdomainSuffix) throws Throwable {
+        // just checking zones are created ok
+        Assert.assertNotNull(this.acsitSetUpFactory.getZone1().getName());
+        Assert.assertNotNull(this.acsitSetUpFactory.getZone2().getName());
+    }
+
+    @When("^client_two does a PUT on (.*?) with (.*?) in zone (.*?)$")
+    public void client_two_does_a_PUT_on_subject_with_subject_id__in_zone(final String api, final String identifier,
+            final String subdomainSuffix) throws Throwable {
+        HttpHeaders zoneHeaders = ACSTestUtil.httpHeaders();
+        OAuth2RestTemplate acsTemplate = this.acsZone2Template;
+        zoneHeaders.set(PolicyHelper.PREDIX_ZONE_ID, getZoneName(subdomainSuffix));
+
+        try {
+            switch (api) {
+            case "subject":
+                this.privilegeHelper.putSubject(acsTemplate, this.subject, this.acsUrl, zoneHeaders,
+                        this.privilegeHelper.getDefaultAttribute());
+                break;
+            case "resource":
+                this.privilegeHelper.putResource(acsTemplate, this.resource, this.acsUrl, zoneHeaders,
+                        this.privilegeHelper.getDefaultAttribute());
+                break;
+            case "policy-set":
+                this.testPolicyName = "single-action-defined-policy-set";
+                CreatePolicyStatus s = this.policyHelper.createPolicySet(
+                        "src/test/resources/single-action-defined-policy-set.json", acsTemplate, zoneHeaders);
+                Assert.assertEquals(s, CreatePolicyStatus.SUCCESS);
+                break;
+            default:
+                Assert.fail("Api " + api + " does not match/is not yet implemented for this test code.");
+            }
+
+        } catch (HttpClientErrorException e) {
+            Assert.fail("Unable to PUT identifier: " + identifier + " for api: " + api, e);
+        }
+    }
+
+    @When("^client_two does a GET on (.*?) with (.*?) in zone (.*?)$")
+    public void client_two_does_a_GET_on_subject_with_subject_id__in_zone(final String api, final String identifier,
+            final String subdomainSuffix) throws Throwable {
+
+        OAuth2RestTemplate acsTemplate = this.acsZone2Template;
+        String encodedIdentifier = URLEncoder.encode(identifier, "UTF-8");
+        HttpHeaders zoneHeaders = ACSTestUtil.httpHeaders();
+        // differentiate between zone 1 and zone 2, which will have slightly different uris
+        zoneHeaders.set(PolicyHelper.PREDIX_ZONE_ID, getZoneName(subdomainSuffix));
+
+        URI uri = URI.create(this.acsUrl + ACS_VERSION + "/" + api + "/" + encodedIdentifier);
+        try {
+            switch (api) {
+            case "subject":
+                this.responseEntity = acsTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<>(zoneHeaders),
+                        BaseSubject.class);
+                this.status = this.responseEntity.getStatusCode().value();
+                break;
+            case "resource":
+                this.responseEntityForResource = acsTemplate.exchange(uri, HttpMethod.GET,
+                        new HttpEntity<>(zoneHeaders), BaseResource.class);
+                this.status = this.responseEntityForResource.getStatusCode().value();
+                break;
+            case "policy-set":
+                this.policyset = acsTemplate.exchange(
+                        this.acsUrl + PolicyHelper.ACS_POLICY_SET_API_PATH + this.testPolicyName, HttpMethod.GET,
+                        new HttpEntity<>(zoneHeaders), PolicySet.class);
+                this.status = this.policyset.getStatusCode().value();
+                break;
+            default:
+                Assert.fail("Api " + api + " does not match/is not yet implemented for this test code.");
+            }
+        } catch (AccessTokenRequiredException | HttpClientErrorException e) {
+            this.status = HttpStatus.FORBIDDEN.value();
+        }
+
+    }
+
+    @When("^client_one does a PUT on (.*?) with (.*?) in zone 1$")
+    public void client_one_does_a_PUT_on_identifier_in_test_zone(final String api, final String identifier)
+            throws Throwable {
+        OAuth2RestTemplate acsTemplate = this.acsZone1Template;
+        try {
+            switch (api) {
+            case "subject":
+                this.privilegeHelper.putSubject(acsTemplate, this.subject, this.acsUrl, this.zone1Headers,
+                        this.privilegeHelper.getDefaultAttribute());
+                break;
+            case "resource":
+                this.privilegeHelper.putResource(acsTemplate, this.resource, this.acsUrl, this.zone1Headers,
+                        this.privilegeHelper.getDefaultAttribute());
+                break;
+            case "policy-set":
+                this.testPolicyName = "single-action-defined-policy-set";
+                this.policyHelper.createPolicySet("src/test/resources/single-action-defined-policy-set.json",
+                        acsTemplate, this.zone1Headers);
+                break;
+            default:
+                Assert.fail("Api " + api + " does not match/is not yet implemented for this test code.");
+            }
+        } catch (HttpClientErrorException e) {
+            Assert.fail("Unable to PUT identifier: " + identifier + " for api: " + api);
+        }
+    }
+
+    @When("^client_one does a GET on (.*?) with (.*?) in zone 1$")
+    public void client_one_does_a_GET_on_api_with_identifier_in_test_zone_dev(final String api, final String identifier)
+            throws Throwable {
+        OAuth2RestTemplate acsTemplate = this.acsZone1Template;
+        String encodedIdentifier = URLEncoder.encode(identifier, "UTF-8");
+        URI uri = URI.create(this.acsUrl + ACS_VERSION + "/" + api + "/" + encodedIdentifier);
+
+        try {
+            switch (api) {
+            case "subject":
+                this.responseEntity = acsTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<>(this.zone1Headers),
+                        BaseSubject.class);
+                this.status = this.responseEntity.getStatusCode().value();
+                break;
+            case "resource":
+                this.responseEntityForResource = acsTemplate.exchange(uri, HttpMethod.GET,
+                        new HttpEntity<>(this.zone1Headers), BaseResource.class);
+                this.status = this.responseEntityForResource.getStatusCode().value();
+                break;
+            case "policy-set":
+                this.policyset = acsTemplate.exchange(
+                        this.acsUrl + PolicyHelper.ACS_POLICY_SET_API_PATH + this.testPolicyName, HttpMethod.GET,
+                        new HttpEntity<>(this.zone1Headers), PolicySet.class);
+                this.status = this.policyset.getStatusCode().value();
+                break;
+            default:
+                Assert.fail("Api " + api + " does not match/is not yet implemented for this test code.");
+            }
+        } catch (HttpClientErrorException e) {
+            e.printStackTrace();
+            Assert.fail("Unable to GET identifier: " + identifier + " for api: " + api);
+        }
+    }
+
+    @When("^client_one does a DELETE on (.*?) with (.*?) in zone 1$")
+    public void client_one_does_a_DELETE_on_api_with_identifier_in_test_zone_dev(final String api,
+            final String identifier) throws Throwable {
+        String encodedIdentifier = URLEncoder.encode(identifier, "UTF-8");
+        URI uri = URI.create(this.acsUrl + ACS_VERSION + "/" + api + "/" + encodedIdentifier);
+        try {
+            this.status = this.acsZone1Template
+                    .exchange(uri, HttpMethod.DELETE, new HttpEntity<>(this.zone1Headers), ResponseEntity.class)
+                    .getStatusCode().value();
+        } catch (HttpClientErrorException e) {
+            Assert.fail("Unable to DELETE identifier: " + identifier + " for api: " + api);
+        }
+    }
+
+    @When("^client_two does a DELETE on (.*?) with (.*?) in zone (.*?)$")
+    public void client_two_does_a_DELETE_on_api_with_identifier_in_test_zone_dev(final String api,
+            final String identifier, final String zone) throws Throwable {
+
+        OAuth2RestTemplate acsTemplate = this.acsZone2Template;
+        String zoneName = getZoneName(zone);
+
+        HttpHeaders zoneHeaders = ACSTestUtil.httpHeaders();
+        zoneHeaders.set(PolicyHelper.PREDIX_ZONE_ID, zoneName);
+
+        String encodedIdentifier = URLEncoder.encode(identifier, "UTF-8");
+        URI uri = URI.create(this.acsUrl + ACS_VERSION + "/" + api + "/" + encodedIdentifier);
+        try {
+            this.status = acsTemplate
+                    .exchange(uri, HttpMethod.DELETE, new HttpEntity<>(zoneHeaders), ResponseEntity.class)
+                    .getStatusCode().value();
+        } catch (HttpClientErrorException e) {
+            Assert.fail("Unable to DELETE identifier: " + identifier + " for api: " + api);
+        }
+    }
+
+    private String getZoneName(final String subdomain) {
+        String zoneName;
+        if (subdomain.equals("1")) {
+            zoneName = this.zone1Name;
+        } else if (subdomain.equals("2")) {
+            zoneName = this.zone2Name;
+        } else {
+            throw new IllegalArgumentException("Unexpected zone id from feature file");
+        }
+        return zoneName;
+    }
+
+    @Then("^the request has status code (\\d+)$")
+    public void the_request_has_status_code(final int statusCode) throws Throwable {
+        // Asserts are done in when statements because global status variable
+        // gets reset before this check is done
+        Assert.assertEquals(this.status, statusCode);
+    }
+
+    @After
+    public void cleanAfterScenario() {
+        this.acsitSetUpFactory.destroy();
+    }
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/integration/test/ACSCorsFilterIT.java b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/ACSCorsFilterIT.java
new file mode 100644
index 0000000..ee03b21
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/ACSCorsFilterIT.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.integration.test;
+
+import java.io.IOException;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+
+@ContextConfiguration("classpath:integration-test-spring-context.xml")
+@Test
+public class ACSCorsFilterIT extends AbstractTestNGSpringContextTests {
+
+    private static final String SWAGGER_API = "/v2/api-docs?group=acs";
+
+    @Value("${ACS_URL}")
+    private String acsBaseUrl;
+
+    private HttpClient client;
+
+    @BeforeClass
+    public void setup() throws JsonParseException, JsonMappingException, IOException {
+        client = HttpClientBuilder.create().useSystemProperties().build();
+    }
+
+    @Test
+    public void testCorsXHRRequestFromAllowedOriginForSwaggerUIApi() throws Exception {
+        HttpGet request = new HttpGet(this.acsBaseUrl + SWAGGER_API);
+        request.setHeader(HttpHeaders.ORIGIN, "http://someone.predix.io");
+        request.setHeader("X-Requested-With", "true");
+        HttpResponse response = client.execute(request);
+        System.out.println("Response Code : " + response.getStatusLine().getStatusCode());
+
+        System.out.println(
+                "Access-Control-Allow-Origin : " + response.getHeaders("Access-Control-Allow-Origin")[0].getValue());
+
+        Assert.assertEquals(response.getStatusLine().getStatusCode(), 200);
+
+        Assert.assertTrue(response.containsHeader("Access-Control-Allow-Origin"));
+    }
+
+    @Test
+    public void testCorsXHRRequestFromNotWhitelistedOriginForSwaggerUIApi() throws Exception {
+        HttpGet request = new HttpGet(this.acsBaseUrl + SWAGGER_API);
+        request.setHeader(HttpHeaders.ORIGIN, "Origin: http://someone.predix.nert");
+        request.setHeader("X-Requested-With", "true");
+        HttpResponse response = client.execute(request);
+        System.out.println("Response Code : " + response.getStatusLine().getStatusCode());
+
+        System.out
+                .println("Access-Control-Allow-Origin : " + response.getHeaders("Access-Control-Allow-Origin").length);
+
+        Assert.assertEquals(response.getStatusLine().getStatusCode(), 403);
+        Assert.assertFalse(response.containsHeader("Access-Control-Allow-Origin"));
+    }
+
+    @Test
+    public void testCorsXHRRequestFromWhitelistedOriginForNonSwaggerUIApi() throws Exception {
+        HttpGet request = new HttpGet(this.acsBaseUrl + "/acs");
+        request.setHeader(HttpHeaders.ORIGIN, "http://someone.predix.io");
+        request.setHeader("X-Requested-With", "true");
+        HttpResponse response = client.execute(request);
+        System.out.println("Response Code : " + response.getStatusLine().getStatusCode());
+
+        System.out
+                .println("Access-Control-Allow-Origin : " + response.getHeaders("Access-Control-Allow-Origin").length);
+
+        Assert.assertEquals(response.getStatusLine().getStatusCode(), 403);
+        Assert.assertFalse(response.containsHeader("Access-Control-Allow-Origin"));
+    }
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/integration/test/AccessControlServiceIT.java b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/AccessControlServiceIT.java
new file mode 100644
index 0000000..4333e49
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/AccessControlServiceIT.java
@@ -0,0 +1,443 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.integration.test;
+
+import static com.ge.predix.integration.test.SubjectResourceFixture.JLO_V1;
+import static com.ge.predix.integration.test.SubjectResourceFixture.JOE_V1;
+import static com.ge.predix.integration.test.SubjectResourceFixture.MARISSA_V1;
+import static com.ge.predix.integration.test.SubjectResourceFixture.PETE_V1;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestTemplate;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+import com.ge.predix.test.utils.ACSITSetUpFactory;
+import com.ge.predix.test.utils.ACSTestUtil;
+import com.ge.predix.test.utils.PolicyHelper;
+import com.ge.predix.test.utils.PrivilegeHelper;
+
+@SuppressWarnings({ "nls" })
+@ContextConfiguration("classpath:integration-test-spring-context.xml")
+@Test
+public class AccessControlServiceIT extends AbstractTestNGSpringContextTests {
+
+    @Autowired
+    private ACSITSetUpFactory acsitSetUpFactory;
+
+    @Autowired
+    private ACSTestUtil acsTestUtil;
+
+    @Autowired
+    private PolicyHelper policyHelper;
+
+    @Autowired
+    private PrivilegeHelper privilegeHelper;
+
+    @BeforeClass
+    public void setup() throws JsonParseException, JsonMappingException, IOException {
+        this.acsitSetUpFactory.setUp();
+    }
+
+    @Test(dataProvider = "subjectProvider")
+    public void testPolicyEvalWithFirstMatchDeny(final BaseSubject subject,
+            final PolicyEvaluationRequestV1 policyEvaluationRequest, final String endpoint) throws Exception {
+
+        this.privilegeHelper.putSubject(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(), subject, endpoint,
+                this.acsitSetUpFactory.getZone1Headers(), this.privilegeHelper.getDefaultAttribute());
+
+        String policyFile = "src/test/resources/multiple-site-based-policy-set.json";
+        String testPolicyName = this.policyHelper.setTestPolicy(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                this.acsitSetUpFactory.getZone1Headers(), endpoint, policyFile);
+        ResponseEntity<PolicyEvaluationResult> postForEntity = this.acsitSetUpFactory.getAcsZoneAdminRestTemplate()
+                .postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                        new HttpEntity<>(policyEvaluationRequest, this.acsitSetUpFactory.getZone1Headers()),
+                        PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        PolicyEvaluationResult responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.DENY);
+
+        this.policyHelper.deletePolicySet(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                this.acsitSetUpFactory.getAcsUrl(), testPolicyName, this.acsitSetUpFactory.getZone1Headers());
+        this.privilegeHelper.deleteSubject(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                this.acsitSetUpFactory.getAcsUrl(), subject.getSubjectIdentifier(),
+                this.acsitSetUpFactory.getZone1Headers());
+    }
+
+    @Test(dataProvider = "subjectProvider")
+    public void testPolicyEvalPermit(final BaseSubject subject, final PolicyEvaluationRequestV1 policyEvaluationRequest,
+            final String endpoint) throws Exception {
+        String testPolicyName = null;
+        try {
+
+            this.privilegeHelper.putSubject(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(), subject, endpoint,
+                    this.acsitSetUpFactory.getZone1Headers(), this.privilegeHelper.getDefaultAttribute());
+            String policyFile = "src/test/resources/single-site-based-policy-set.json";
+            testPolicyName = this.policyHelper.setTestPolicy(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                    this.acsitSetUpFactory.getZone1Headers(), endpoint, policyFile);
+
+            ResponseEntity<PolicyEvaluationResult> postForEntity = this.acsitSetUpFactory.getAcsZoneAdminRestTemplate()
+                    .postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                            new HttpEntity<>(policyEvaluationRequest, this.acsitSetUpFactory.getZone1Headers()),
+                            PolicyEvaluationResult.class);
+
+            Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+            PolicyEvaluationResult responseBody = postForEntity.getBody();
+            Assert.assertEquals(responseBody.getEffect(), Effect.PERMIT);
+
+            PolicyEvaluationRequestV1 policyEvaluationRequest2 = this.policyHelper
+                    .createEvalRequest(subject.getSubjectIdentifier(), "ny");
+
+            postForEntity = this.acsitSetUpFactory.getAcsZoneAdminRestTemplate().postForEntity(
+                    endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                    new HttpEntity<>(policyEvaluationRequest2, this.acsitSetUpFactory.getZone1Headers()),
+                    PolicyEvaluationResult.class);
+
+            Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+            responseBody = postForEntity.getBody();
+            Assert.assertEquals(responseBody.getEffect(), Effect.NOT_APPLICABLE);
+
+        } finally {
+            if (testPolicyName != null) {
+                this.policyHelper.deletePolicySet(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                        this.acsitSetUpFactory.getAcsUrl(), testPolicyName, this.acsitSetUpFactory.getZone1Headers());
+            }
+        }
+    }
+
+    // TODO: Delete resource. Currently it's causing "405 Request method 'DELETE' not supported" error
+    // Not deleting resource prevents from running this test repeatedly.
+    // @Test
+    @Test
+    public void testPolicyEvalWithAttributeUriTemplate() throws Exception {
+        String testPolicyName = null;
+
+        // This is the extracted resource URI using attributeUriTemplate. See test policy.
+        String testResourceId = "/asset/1223";
+        try {
+            // OAuth2RestTemplate acsRestTemplate = this.acsitSetUpFactory.getAcsZoneAdminRestTemplate();
+
+            // set policy
+            String policyFile = "src/test/resources/policies/policy-set-with-attribute-uri-template.json";
+            testPolicyName = this.policyHelper.setTestPolicy(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                    this.acsitSetUpFactory.getZone1Headers(), this.acsitSetUpFactory.getAcsUrl(), policyFile);
+
+            // Policy Eval. without setting required attribute on resource. Should return DENY
+            // Note resourceId sent to eval request is the complete URI, from which /asset/1223 will be
+            // extracted by ACS, using "attributeUriTemplate": "/v1/region/report{attribute_uri}"
+            PolicyEvaluationRequestV1 evalRequest = this.policyHelper.createEvalRequest("GET", "testSubject",
+                    "/v1/region/report/asset/1223", null);
+
+            ResponseEntity<PolicyEvaluationResult> postForEntity = this.acsitSetUpFactory.getAcsZoneAdminRestTemplate()
+                    .postForEntity(this.acsitSetUpFactory.getAcsUrl() + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                            new HttpEntity<>(evalRequest, this.acsitSetUpFactory.getZone1Headers()),
+                            PolicyEvaluationResult.class);
+
+            Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+            Assert.assertEquals(postForEntity.getBody().getEffect(), Effect.DENY);
+
+            // Set resource attribute and evaluate again. expect PERMIT
+            // createResource adds a 'site' attribute with value 'sanramon' used by our test policy
+            BaseResource testResource = this.privilegeHelper.createResource(testResourceId);
+            this.privilegeHelper.postResources(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                    this.acsitSetUpFactory.getAcsUrl(), this.acsitSetUpFactory.getZone1Headers(), testResource);
+
+            postForEntity = this.acsitSetUpFactory.getAcsZoneAdminRestTemplate().postForEntity(
+                    this.acsitSetUpFactory.getAcsUrl() + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                    new HttpEntity<>(evalRequest, this.acsitSetUpFactory.getZone1Headers()),
+                    PolicyEvaluationResult.class);
+            Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+            Assert.assertEquals(postForEntity.getBody().getEffect(), Effect.PERMIT);
+
+        } finally {
+            if (testPolicyName != null) {
+                this.policyHelper.deletePolicySet(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                        this.acsitSetUpFactory.getAcsUrl(), testPolicyName, this.acsitSetUpFactory.getZone1Headers());
+            }
+        }
+
+    }
+
+    @Test(dataProvider = "endpointProvider")
+    public void testCreationOfValidPolicy(final String endpoint) throws Exception {
+        String policyFile = "src/test/resources/single-site-based-policy-set.json";
+        String testPolicyName = this.policyHelper.setTestPolicy(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                this.acsitSetUpFactory.getZone1Headers(), endpoint, policyFile);
+        PolicySet policySetSaved = this.getPolicySet(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                testPolicyName, this.acsitSetUpFactory.getZone1Headers(), endpoint);
+        Assert.assertEquals(testPolicyName, policySetSaved.getName());
+        this.policyHelper.deletePolicySet(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                this.acsitSetUpFactory.getAcsUrl(), testPolicyName, this.acsitSetUpFactory.getZone1Headers());
+    }
+
+    @Test(dataProvider = "endpointProvider")
+    public void testPolicyCreationInValidPolicy(final String endpoint) throws Exception {
+        String testPolicyName = "";
+        try {
+            String policyFile = "src/test/resources/missing-policy-set-name-policy.json";
+            testPolicyName = this.policyHelper.setTestPolicy(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                    this.acsitSetUpFactory.getZone1Headers(), endpoint, policyFile);
+        } catch (HttpClientErrorException e) {
+            this.acsTestUtil.assertExceptionResponseBody(e, "policy set name is missing");
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.UNPROCESSABLE_ENTITY);
+            return;
+        }
+        this.policyHelper.deletePolicySet(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                this.acsitSetUpFactory.getAcsUrl(), testPolicyName, this.acsitSetUpFactory.getZone1Headers());
+        Assert.fail("testPolicyCreationInValidPolicy should have failed");
+    }
+
+    @Test(dataProvider = "endpointProvider")
+    public void testPolicyCreationInValidWithBadPolicySetNamePolicy(final String endpoint) throws Exception {
+        String testPolicyName = "";
+        try {
+            String policyFile = "src/test/resources/policy-set-with-only-name-effect.json";
+            testPolicyName = this.policyHelper.setTestPolicy(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                    this.acsitSetUpFactory.getZone1Headers(), endpoint, policyFile);
+        } catch (HttpClientErrorException e) {
+            this.acsTestUtil.assertExceptionResponseBody(e, "is not URI friendly");
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.UNPROCESSABLE_ENTITY);
+            return;
+        }
+        this.policyHelper.deletePolicySet(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                this.acsitSetUpFactory.getAcsUrl(), testPolicyName, this.acsitSetUpFactory.getZone1Headers());
+        Assert.fail("testPolicyCreationInValidPolicy should have failed");
+    }
+
+    @Test(dataProvider = "endpointProvider")
+    public void testPolicyCreationJsonSchemaInvalidPolicySet(final String endpoint) throws Exception {
+        String testPolicyName = "";
+        try {
+            String policyFile = "src/test/resources/invalid-json-schema-policy-set.json";
+            testPolicyName = this.policyHelper.setTestPolicy(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                    this.acsitSetUpFactory.getZone1Headers(), endpoint, policyFile);
+        } catch (HttpClientErrorException e) {
+            this.acsTestUtil.assertExceptionResponseBody(e, "JSON Schema validation");
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.UNPROCESSABLE_ENTITY);
+            return;
+        }
+        this.policyHelper.deletePolicySet(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                this.acsitSetUpFactory.getAcsUrl(), testPolicyName, this.acsitSetUpFactory.getZone1Headers());
+        Assert.fail("testPolicyCreationInValidPolicy should have failed");
+    }
+
+    @Test(dataProvider = "endpointProvider")
+    public void testPolicyEvalNotApplicable(final String endpoint) throws Exception {
+        String testPolicyName = null;
+        try {
+            this.privilegeHelper.putSubject(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(), MARISSA_V1,
+                    this.acsitSetUpFactory.getAcsUrl(), this.acsitSetUpFactory.getZone1Headers(),
+                    this.privilegeHelper.getDefaultAttribute());
+
+            String policyFile = "src/test/resources/policy-set-with-multiple-policies-na-with-condition.json";
+            testPolicyName = this.policyHelper.setTestPolicy(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                    this.acsitSetUpFactory.getZone1Headers(), endpoint, policyFile);
+
+            PolicyEvaluationRequestV1 policyEvaluationRequest = this.policyHelper
+                    .createEvalRequest(MARISSA_V1.getSubjectIdentifier(), "sanramon");
+            ResponseEntity<PolicyEvaluationResult> postForEntity = this.acsitSetUpFactory.getAcsZoneAdminRestTemplate()
+                    .postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                            new HttpEntity<>(policyEvaluationRequest, this.acsitSetUpFactory.getZone1Headers()),
+                            PolicyEvaluationResult.class);
+
+            Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+            PolicyEvaluationResult responseBody = postForEntity.getBody();
+            Assert.assertEquals(responseBody.getEffect(), Effect.NOT_APPLICABLE);
+        } catch (Exception e) {
+            Assert.fail("testPolicyEvalNotApplicable should have NOT failed " + endpoint + " " + e.getMessage());
+        } finally {
+            this.policyHelper.deletePolicySet(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                    this.acsitSetUpFactory.getAcsUrl(), testPolicyName, this.acsitSetUpFactory.getZone1Headers());
+            this.privilegeHelper.deleteSubject(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                    this.acsitSetUpFactory.getAcsUrl(), MARISSA_V1.getSubjectIdentifier(),
+                    this.acsitSetUpFactory.getZone1Headers());
+        }
+    }
+
+    @Test(dataProvider = "endpointProvider")
+    public void testPolicyUpdateWithNoOauthToken(final String endpoint)
+            throws JsonParseException, JsonMappingException, IOException {
+        RestTemplate acs = new RestTemplate();
+        // Use vanilla rest template with no oauth token.
+        try {
+            String policyFile = "src/test/resources/policy-set-with-multiple-policies-na-with-condition.json";
+            this.policyHelper.setTestPolicy(acs, this.acsitSetUpFactory.getZone1Headers(), endpoint, policyFile);
+            Assert.fail("No exception thrown when making request without token.");
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.UNAUTHORIZED);
+        }
+
+    }
+
+    @Test(dataProvider = "endpointProvider")
+    public void testPolicyEvalWithNoOauthToken(final String endpoint) {
+        RestTemplate acs = new RestTemplate();
+        // Use vanilla rest template with no oauth token.
+        try {
+            acs.postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                    new HttpEntity<>(this.policyHelper.createEvalRequest(MARISSA_V1.getSubjectIdentifier(), "sanramon"),
+                            this.acsitSetUpFactory.getZone1Headers()),
+                    PolicyEvaluationResult.class);
+            Assert.fail("No exception thrown when making policy evaluation request without token.");
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.UNAUTHORIZED);
+        }
+    }
+
+    @Test(dataProvider = "endpointProvider")
+    public void testPolicyUpdateWithInsufficientScope(final String endpoint) throws Exception {
+        String testPolicyName;
+        try {
+            String policyFile = "src/test/resources/policy-set-with-multiple-policies-na-with-condition.json";
+            testPolicyName = this.policyHelper.setTestPolicy(this.acsitSetUpFactory.getAcsNoPolicyScopeRestTemplate(),
+                    this.acsitSetUpFactory.getZone1Headers(), endpoint, policyFile);
+            this.policyHelper.deletePolicySet(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                    this.acsitSetUpFactory.getAcsUrl(), testPolicyName, this.acsitSetUpFactory.getZone1Headers());
+            Assert.fail("No exception when trying to create policy set with no acs scope");
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.FORBIDDEN);
+        }
+    }
+
+    @Test(dataProvider = "endpointProvider")
+    public void testPolicyUpdateWithReadOnlyAccess(final String endpoint) throws Exception {
+        try {
+            String policyFile = "src/test/resources/policy-set-with-multiple-policies-na-with-condition.json";
+            this.policyHelper.setTestPolicy(this.acsitSetUpFactory.getAcsReadOnlyRestTemplate(),
+                    this.acsitSetUpFactory.getZone1Headers(), endpoint, policyFile);
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.FORBIDDEN);
+        }
+    }
+
+    @Test(dataProvider = "endpointProvider")
+    public void testPolicyReadWithReadOnlyAccess(final String endpoint) throws Exception {
+        String testPolicyName = this.policyHelper.setTestPolicy(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                this.acsitSetUpFactory.getZone1Headers(), endpoint,
+                "src/test/resources/single-site-based-policy-set.json");
+
+        ResponseEntity<PolicySet> policySetResponse = this.acsitSetUpFactory.getAcsReadOnlyRestTemplate().exchange(
+                endpoint + PolicyHelper.ACS_POLICY_SET_API_PATH + testPolicyName, HttpMethod.GET,
+                new HttpEntity<>(this.acsitSetUpFactory.getZone1Headers()), PolicySet.class);
+        Assert.assertEquals(testPolicyName, policySetResponse.getBody().getName());
+        this.policyHelper.deletePolicySet(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                this.acsitSetUpFactory.getAcsUrl(), testPolicyName, this.acsitSetUpFactory.getZone1Headers());
+    }
+
+    @Test
+    public void testCreatePolicyWithClientOnlyBasedToken() throws Exception {
+        String testPolicyName = null;
+        try {
+
+            PolicySet policySet = new ObjectMapper()
+                    .readValue(new File("src/test/resources/single-site-based-policy-set.json"), PolicySet.class);
+            testPolicyName = policySet.getName();
+            this.acsitSetUpFactory.getAcsZoneAdminRestTemplate().put(
+                    this.acsitSetUpFactory.getAcsUrl() + PolicyHelper.ACS_POLICY_SET_API_PATH + testPolicyName,
+                    new HttpEntity<>(policySet, this.acsitSetUpFactory.getZone1Headers()));
+        } finally {
+            this.policyHelper.deletePolicySet(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                    this.acsitSetUpFactory.getAcsUrl(), testPolicyName, this.acsitSetUpFactory.getZone1Headers());
+        }
+    }
+
+    @Test(dataProvider = "endpointProvider")
+    public void testGetAllPolicySets(final String endpoint) throws Exception {
+        String testPolicyName = this.policyHelper.setTestPolicy(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                this.acsitSetUpFactory.getZone1Headers(), endpoint,
+                "src/test/resources/single-site-based-policy-set.json");
+
+        String getAllPolicySetsURL = endpoint + PolicyHelper.ACS_POLICY_SET_API_PATH;
+
+        ResponseEntity<PolicySet[]> policySetsResponse = this.acsitSetUpFactory.getAcsReadOnlyRestTemplate().exchange(
+                getAllPolicySetsURL, HttpMethod.GET, new HttpEntity<>(this.acsitSetUpFactory.getZone1Headers()),
+                PolicySet[].class);
+
+        PolicySet[] policySets = policySetsResponse.getBody();
+        // should expect only one policySet per issuer, clientId and policySetId
+        Assert.assertEquals(1, policySets.length);
+        Assert.assertEquals(testPolicyName, policySets[0].getName());
+
+        this.policyHelper.deletePolicySet(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                this.acsitSetUpFactory.getAcsUrl(), testPolicyName, this.acsitSetUpFactory.getZone1Headers());
+    }
+
+    private PolicySet getPolicySet(final RestTemplate acs, final String policyName, final HttpHeaders headers,
+            final String acsEndpointParam) {
+        ResponseEntity<PolicySet> policySetResponse = acs.exchange(
+                acsEndpointParam + PolicyHelper.ACS_POLICY_SET_API_PATH + policyName, HttpMethod.GET,
+                new HttpEntity<>(headers), PolicySet.class);
+        return policySetResponse.getBody();
+    }
+
+    @DataProvider(name = "subjectProvider")
+    public Object[][] getSubject() {
+        Object[][] data = new Object[][] {
+                { MARISSA_V1, this.policyHelper.createEvalRequest(MARISSA_V1.getSubjectIdentifier(), "sanramon"),
+                        this.acsitSetUpFactory.getAcsUrl() },
+                { JOE_V1, this.policyHelper.createEvalRequest(JOE_V1.getSubjectIdentifier(), "sanramon"),
+                        this.acsitSetUpFactory.getAcsUrl() },
+                { PETE_V1, this.policyHelper.createEvalRequest(PETE_V1.getSubjectIdentifier(), "sanramon"),
+                        this.acsitSetUpFactory.getAcsUrl() },
+                { JLO_V1, this.policyHelper.createEvalRequest(JLO_V1.getSubjectIdentifier(), "sanramon"),
+                        this.acsitSetUpFactory.getAcsUrl() } };
+        return data;
+    }
+
+    @DataProvider(name = "endpointProvider")
+    public Object[][] getAcsEndpoint() {
+        Object[][] data = new Object[][] { { this.acsitSetUpFactory.getAcsUrl() } };
+        return data;
+    }
+
+    @AfterClass
+    public void cleanup() throws Exception {
+        this.privilegeHelper.deleteResources(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                this.acsitSetUpFactory.getAcsUrl(), this.acsitSetUpFactory.getZone1Headers());
+        this.privilegeHelper.deleteSubjects(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                this.acsitSetUpFactory.getAcsUrl(), this.acsitSetUpFactory.getZone1Headers());
+        this.policyHelper.deletePolicySets(this.acsitSetUpFactory.getAcsZoneAdminRestTemplate(),
+                this.acsitSetUpFactory.getAcsUrl(), this.acsitSetUpFactory.getZone1Headers());
+        this.acsitSetUpFactory.destroy();
+    }
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/integration/test/AttributeConnectorConfigurationIT.java b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/AttributeConnectorConfigurationIT.java
new file mode 100644
index 0000000..2183d50
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/AttributeConnectorConfigurationIT.java
@@ -0,0 +1,166 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.integration.test;
+
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.RESOURCE_CONNECTOR_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.SUBJECT_CONNECTOR_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.V1;
+
+import java.util.Collections;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestTemplate;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.rest.AttributeAdapterConnection;
+import com.ge.predix.acs.rest.AttributeConnector;
+import com.ge.predix.test.utils.ACSITSetUpFactory;
+
+@ContextConfiguration("classpath:integration-test-spring-context.xml")
+@Test
+public class AttributeConnectorConfigurationIT extends AbstractTestNGSpringContextTests {
+
+    @Autowired
+    private ACSITSetUpFactory acsitSetUpFactory;
+
+    private String acsUrl;
+    private OAuth2RestTemplate zone1Admin;
+    private OAuth2RestTemplate zone1ConnectorAdmin;
+    private OAuth2RestTemplate zone1ConnectorReadClient;
+    private HttpHeaders zone1Headers;
+
+    @BeforeClass
+    public void setup() throws Exception {
+        this.acsitSetUpFactory.setUp();
+
+        this.acsUrl = this.acsitSetUpFactory.getAcsUrl();
+
+        this.zone1Admin = this.acsitSetUpFactory.getAcsZoneAdminRestTemplate();
+
+        this.zone1ConnectorAdmin = this.acsitSetUpFactory
+                .getAcsZoneConnectorAdminRestTemplate(this.acsitSetUpFactory.getAcsZone1Name());
+        this.zone1ConnectorReadClient = this.acsitSetUpFactory
+                .getAcsZoneConnectorReadRestTemplate(this.acsitSetUpFactory.getAcsZone1Name());
+
+        this.zone1Headers = this.acsitSetUpFactory.getZone1Headers();
+
+    }
+
+    @Test(dataProvider = "requestUrlProvider")
+    public void testPutGetDeleteConnector(final String endpointUrl) throws Exception {
+        AttributeConnector expectedConnector = new AttributeConnector();
+        expectedConnector.setMaxCachedIntervalMinutes(100);
+        expectedConnector.setAdapters(Collections.singleton(new AttributeAdapterConnection("https://my-endpoint.com",
+                "https://my-uaa.com", "my-client", "my-secret")));
+        try {
+            this.zone1ConnectorAdmin.put(this.acsUrl + V1 + endpointUrl,
+                    new HttpEntity<>(expectedConnector, this.zone1Headers));
+        } catch (Exception e) {
+            Assert.fail("Unable to create attribute connector. " + e.getMessage());
+        }
+
+        try {
+            ResponseEntity<AttributeConnector> response = this.zone1ConnectorReadClient.exchange(
+                    this.acsUrl + V1 + endpointUrl, HttpMethod.GET, new HttpEntity<>(this.zone1Headers),
+                    AttributeConnector.class);
+            Assert.assertEquals(response.getBody(), expectedConnector);
+        } catch (Exception e) {
+            Assert.fail("Unable to retrieve attribute connector." + e.getMessage());
+        } finally {
+            this.zone1ConnectorAdmin.exchange(this.acsUrl + V1 + endpointUrl, HttpMethod.DELETE,
+                    new HttpEntity<>(this.zone1Headers), String.class);
+        }
+    }
+
+    @Test(dataProvider = "requestUrlProvider")
+    public void testCreateConnectorDeniedWithoutOauthToken(final String endpointUrl) throws Exception {
+        RestTemplate acs = new RestTemplate();
+        try {
+            acs.put(this.acsUrl + V1 + endpointUrl,
+                    new HttpEntity<>(new AttributeConnector(), this.zone1Headers));
+            Assert.fail("No exception thrown when configuring connector without a token.");
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.UNAUTHORIZED);
+        }
+    }
+
+    @Test(dataProvider = "requestUrlProvider")
+    public void testCreateConnectorDeniedWithoutSufficientScope(final String endpointUrl) throws Exception {
+        try {
+            this.zone1ConnectorReadClient.put(this.acsUrl + V1 + endpointUrl,
+                    new HttpEntity<>(new AttributeConnector(), this.zone1Headers));
+            Assert.fail("No exception thrown when creating connector without sufficient scope.");
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.FORBIDDEN);
+        }
+    }
+
+    // Due to the issue in spring security, 403 Forbidden response from the server, is received as a 400 Bad Request
+    // error code because error is not correctly translated by the JSON deserializer
+    //https://github.com/spring-projects/spring-security-oauth/issues/191
+    @Test(dataProvider = "requestUrlProvider")
+    public void testGetConnectorDeniedWithoutSufficientScope(final String endpointUrl) throws Exception {
+        try {
+            this.zone1Admin.exchange(this.acsUrl + V1 + endpointUrl, HttpMethod.GET,
+                    new HttpEntity<>(this.zone1Headers), AttributeConnector.class);
+            Assert.fail("No exception thrown when retrieving connector without sufficient scope.");
+        } catch (HttpClientErrorException e) {
+            e.printStackTrace();
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.FORBIDDEN);
+        } catch (OAuth2Exception e) {
+            e.printStackTrace();
+            Assert.assertEquals(e.getHttpErrorCode(), HttpStatus.BAD_REQUEST.value());
+        }
+    }
+
+    @Test(dataProvider = "requestUrlProvider")
+    public void testDeleteConnectorDeniedWithoutSufficientScope(final String endpointUrl) throws Exception {
+        try {
+            this.zone1ConnectorReadClient.exchange(this.acsUrl + V1 + endpointUrl, HttpMethod.DELETE,
+                    new HttpEntity<>(this.zone1Headers), String.class);
+            Assert.fail("No exception thrown when deleting connector without sufficient scope.");
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.FORBIDDEN);
+        }
+    }
+
+    @DataProvider(name = "requestUrlProvider")
+    private Object[][] requestUrlProvider() {
+        return new String[][] { { RESOURCE_CONNECTOR_URL }, { SUBJECT_CONNECTOR_URL } };
+    }
+
+    @AfterClass
+    public void cleanup() throws Exception {
+        this.acsitSetUpFactory.destroy();
+    }
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/integration/test/PolicyEvalCachingWithGraphDBIT.java b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/PolicyEvalCachingWithGraphDBIT.java
new file mode 100644
index 0000000..b267b7b
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/PolicyEvalCachingWithGraphDBIT.java
@@ -0,0 +1,254 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.integration.test;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.Parent;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+import com.ge.predix.test.utils.ACSITSetUpFactory;
+import com.ge.predix.test.utils.PolicyHelper;
+import com.ge.predix.test.utils.PrivilegeHelper;
+
+@ContextConfiguration("classpath:integration-test-spring-context.xml")
+@SuppressWarnings({ "nls" })
+public class PolicyEvalCachingWithGraphDBIT extends AbstractTestNGSpringContextTests {
+
+    @Autowired
+    private PolicyHelper policyHelper;
+
+    @Autowired
+    private PrivilegeHelper privilegeHelper;
+
+    @Autowired
+    private ACSITSetUpFactory acsitSetUpFactory;
+
+    private OAuth2RestTemplate acsAdminRestTemplate;
+
+    private HttpHeaders acsZone1Headers;
+
+    private static final String ISSUER_URI = "acs.example.org";
+    private static final Attribute TOP_SECRET_CLASSIFICATION = new Attribute(ISSUER_URI, "classification",
+            "top secret");
+    private static final Attribute SPECIAL_AGENTS_GROUP_ATTRIBUTE = new Attribute(ISSUER_URI, "group",
+            "special-agents");
+
+    private static final String FBI = "fbi";
+    private static final String SPECIAL_AGENTS_GROUP = "special-agents";
+    private static final String AGENT_MULDER = "mulder";
+    private static final String AGENT_SCULLY = "scully";
+    public static final String EVIDENCE_SCULLYS_TESTIMONY_ID = "/evidence/scullys-testimony";
+    private String acsUrl;
+
+    @BeforeClass
+    public void setup() throws JsonParseException, JsonMappingException, IOException {
+        this.acsitSetUpFactory.setUp();
+        this.acsZone1Headers = this.acsitSetUpFactory.getZone1Headers();
+        this.acsAdminRestTemplate = this.acsitSetUpFactory.getAcsZoneAdminRestTemplate();
+        this.acsUrl = this.acsitSetUpFactory.getAcsUrl();
+    }
+
+    @AfterMethod
+    public void cleanup() throws Exception {
+        this.privilegeHelper.deleteResources(this.acsAdminRestTemplate, this.acsUrl, this.acsZone1Headers);
+        this.privilegeHelper.deleteSubjects(this.acsAdminRestTemplate, this.acsUrl, this.acsZone1Headers);
+        this.policyHelper.deletePolicySets(this.acsAdminRestTemplate, this.acsUrl, this.acsZone1Headers);
+    }
+
+    /**
+     * This test makes sure that cached policy evaluation results are properly invalidated for a subject and its
+     * descendants, when attributes are changed for the parent subject.
+     */
+    @Test
+    public void testPolicyEvalCacheInvalidationWhenSubjectParentChanges() throws Exception {
+        BaseSubject fbi = new BaseSubject(this.FBI);
+
+        BaseSubject specialAgentsGroup = new BaseSubject(this.SPECIAL_AGENTS_GROUP);
+        specialAgentsGroup
+                .setParents(new HashSet<>(Arrays.asList(new Parent[] { new Parent(fbi.getSubjectIdentifier()) })));
+
+        BaseSubject agentMulder = new BaseSubject(this.AGENT_MULDER);
+        agentMulder.setParents(
+                new HashSet<>(Arrays.asList(new Parent[] { new Parent(specialAgentsGroup.getSubjectIdentifier()) })));
+
+        BaseSubject agentScully = new BaseSubject(this.AGENT_SCULLY);
+        agentScully.setParents(
+                new HashSet<>(Arrays.asList(new Parent[] { new Parent(specialAgentsGroup.getSubjectIdentifier()) })));
+
+        BaseResource scullysTestimony = new BaseResource(EVIDENCE_SCULLYS_TESTIMONY_ID);
+
+        PolicyEvaluationRequestV1 mulderPolicyEvaluationRequest = this.policyHelper
+                .createEvalRequest("GET", agentMulder.getSubjectIdentifier(), EVIDENCE_SCULLYS_TESTIMONY_ID, null);
+        PolicyEvaluationRequestV1 scullyPolicyEvaluationRequest = this.policyHelper
+                .createEvalRequest("GET", agentScully.getSubjectIdentifier(), EVIDENCE_SCULLYS_TESTIMONY_ID, null);
+
+        String endpoint = this.acsUrl;
+
+        // Set up fbi <-- specialAgentsGroup <-- (agentMulder, agentScully) subject hierarchy
+        this.privilegeHelper.putSubject(this.acsAdminRestTemplate, fbi, endpoint, this.acsZone1Headers);
+        this.privilegeHelper.putSubject(this.acsAdminRestTemplate, specialAgentsGroup, endpoint, this.acsZone1Headers,
+                this.SPECIAL_AGENTS_GROUP_ATTRIBUTE);
+        this.privilegeHelper.putSubject(this.acsAdminRestTemplate, agentMulder, endpoint, this.acsZone1Headers);
+        this.privilegeHelper.putSubject(this.acsAdminRestTemplate, agentScully, endpoint, this.acsZone1Headers);
+
+        // Set up resource
+        this.privilegeHelper.putResource(this.acsAdminRestTemplate, scullysTestimony, endpoint, this.acsZone1Headers,
+                this.SPECIAL_AGENTS_GROUP_ATTRIBUTE, this.TOP_SECRET_CLASSIFICATION);
+
+        // Set up policy
+        String policyFile = "src/test/resources/policies/complete-sample-policy-set-2.json";
+        this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.acsZone1Headers, endpoint, policyFile);
+
+        // Verify that policy is evaluated to DENY since top secret classification is not set
+        ResponseEntity<PolicyEvaluationResult> postForEntity = this.acsAdminRestTemplate
+                .postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                        new HttpEntity<>(mulderPolicyEvaluationRequest, this.acsZone1Headers),
+                        PolicyEvaluationResult.class);
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        PolicyEvaluationResult responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.DENY);
+
+        postForEntity = this.acsAdminRestTemplate.postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                new HttpEntity<>(scullyPolicyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.DENY);
+
+        // Change parent subject to add top secret classification
+        this.privilegeHelper.putSubject(this.acsAdminRestTemplate, specialAgentsGroup, endpoint, this.acsZone1Headers,
+                this.SPECIAL_AGENTS_GROUP_ATTRIBUTE, this.TOP_SECRET_CLASSIFICATION);
+
+        // Verify that policy is evaluated to PERMIT since top secret classification is now propogated from the parent
+        postForEntity = this.acsAdminRestTemplate.postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                new HttpEntity<>(mulderPolicyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.PERMIT);
+
+        postForEntity = this.acsAdminRestTemplate.postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                new HttpEntity<>(scullyPolicyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.PERMIT);
+    }
+
+    /**
+     * This test makes sure that cached policy evaluation results are properly invalidated for a resource and its
+     * descendants, when attributes are changed for the parent resource.
+     */
+    @Test
+    public void testPolicyEvalCacheInvalidationWhenResourceParentChanges() throws Exception {
+        BaseResource grandparentResource = new BaseResource("/secured-by-value/sites/east-bay");
+        BaseResource parentResource = new BaseResource("/secured-by-value/sites/sanramon");
+        BaseResource childResource = new BaseResource("/secured-by-value/sites/basement");
+
+        parentResource.setParents(
+                new HashSet<>(Arrays.asList(new Parent[] { new Parent(grandparentResource.getResourceIdentifier()) })));
+
+        childResource.setParents(
+                new HashSet<>(Arrays.asList(new Parent[] { new Parent(parentResource.getResourceIdentifier()) })));
+
+        BaseSubject agentMulder = new BaseSubject(this.AGENT_MULDER);
+
+        PolicyEvaluationRequestV1 sanramonPolicyEvaluationRequest = this.policyHelper
+                .createEvalRequest(agentMulder.getSubjectIdentifier(), "sanramon");
+
+        PolicyEvaluationRequestV1 basementPolicyEvaluationRequest = this.policyHelper
+                .createEvalRequest(agentMulder.getSubjectIdentifier(), "basement");
+
+        String endpoint = this.acsUrl;
+
+        this.privilegeHelper.putResource(this.acsAdminRestTemplate, grandparentResource, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getDefaultOrgAttribute());
+        this.privilegeHelper.putResource(this.acsAdminRestTemplate, parentResource, endpoint, this.acsZone1Headers);
+        this.privilegeHelper.putResource(this.acsAdminRestTemplate, childResource, endpoint, this.acsZone1Headers);
+        this.privilegeHelper.putSubject(this.acsAdminRestTemplate, agentMulder, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getDefaultAttribute(), this.privilegeHelper.getDefaultOrgAttribute());
+
+        String policyFile = "src/test/resources/policies/single-org-based.json";
+        this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.acsZone1Headers, endpoint, policyFile);
+
+        // Subject policy evaluation request for site "sanramon"
+        ResponseEntity<PolicyEvaluationResult> postForEntity = this.acsAdminRestTemplate
+                .postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                        new HttpEntity<>(sanramonPolicyEvaluationRequest, this.acsZone1Headers),
+                        PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        PolicyEvaluationResult responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.PERMIT);
+
+        // Subject policy evaluation request for site "basement"
+        postForEntity = this.acsAdminRestTemplate.postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                new HttpEntity<>(basementPolicyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.PERMIT);
+
+        // Change grandparent resource attributes from DefaultOrgAttribute to AlternateOrgAttribute
+        this.privilegeHelper.putResource(this.acsAdminRestTemplate, grandparentResource, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getAlternateOrgAttribute());
+
+        // Subject policy evaluation request for site "sanramon"
+        postForEntity = this.acsAdminRestTemplate.postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                new HttpEntity<>(sanramonPolicyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.NOT_APPLICABLE);
+
+        // Subject policy evaluation request for site "basement"
+        postForEntity = this.acsAdminRestTemplate.postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                new HttpEntity<>(basementPolicyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.NOT_APPLICABLE);
+    }
+
+    @AfterClass
+    public void destroy() {
+        this.acsitSetUpFactory.destroy();
+    }
+
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/integration/test/PolicyEvaluationCachingIT.java b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/PolicyEvaluationCachingIT.java
new file mode 100644
index 0000000..7bacc9f
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/PolicyEvaluationCachingIT.java
@@ -0,0 +1,388 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.integration.test;
+
+import static com.ge.predix.integration.test.SubjectResourceFixture.MARISSA_V1;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+import com.ge.predix.test.TestConfig;
+import com.ge.predix.test.utils.ACSITSetUpFactory;
+import com.ge.predix.test.utils.PolicyHelper;
+import com.ge.predix.test.utils.PrivilegeHelper;
+
+@ContextConfiguration("classpath:integration-test-spring-context.xml")
+@SuppressWarnings({ "nls" })
+public class PolicyEvaluationCachingIT extends AbstractTestNGSpringContextTests {
+
+    private String acsUrl;
+
+    @Autowired
+    private PolicyHelper policyHelper;
+
+    @Autowired
+    private PrivilegeHelper privilegeHelper;
+
+    @Autowired
+    private ACSITSetUpFactory acsitSetUpFactory;
+
+    private OAuth2RestTemplate acsAdminRestTemplate;
+    private HttpHeaders acsZone1Headers;
+
+    @BeforeClass
+    public void setup() throws JsonParseException, JsonMappingException, IOException {
+        TestConfig.setupForEclipse(); // Starts ACS when running the test in eclipse.
+        this.acsitSetUpFactory.setUp();
+        this.acsUrl = this.acsitSetUpFactory.getAcsUrl();
+        this.acsZone1Headers = this.acsitSetUpFactory.getZone1Headers();
+        this.acsAdminRestTemplate = this.acsitSetUpFactory.getAcsZoneAdminRestTemplate();
+    }
+
+    @AfterMethod
+    public void cleanup() throws Exception {
+        this.privilegeHelper.deleteResources(this.acsAdminRestTemplate, this.acsUrl, this.acsZone1Headers);
+        this.privilegeHelper.deleteSubjects(this.acsAdminRestTemplate, this.acsUrl, this.acsZone1Headers);
+        this.policyHelper.deletePolicySets(this.acsAdminRestTemplate, this.acsUrl, this.acsZone1Headers);
+    }
+
+    /**
+     * This test makes sure that cached policy evaluation results are properly invalidated when a policy set changes.
+     * Policy set name that is used to derive part of cache key does not change.
+     */
+    @Test
+    public void testPolicyEvalCacheWhenPolicySetChanges() throws Exception {
+        BaseSubject subject = MARISSA_V1;
+        PolicyEvaluationRequestV1 policyEvaluationRequest = this.policyHelper
+                .createEvalRequest(MARISSA_V1.getSubjectIdentifier(), "sanramon");
+        String endpoint = this.acsUrl;
+
+        this.privilegeHelper.putSubject(this.acsAdminRestTemplate, subject, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getDefaultAttribute());
+        String policyFile = "src/test/resources/policies/single-site-based.json";
+        this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.acsZone1Headers, endpoint, policyFile);
+
+        ResponseEntity<PolicyEvaluationResult> postForEntity = this.acsAdminRestTemplate
+                .postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                        new HttpEntity<>(policyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        PolicyEvaluationResult responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.PERMIT);
+
+        policyFile = "src/test/resources/policies/deny-all.json";
+        this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.acsZone1Headers, endpoint, policyFile);
+
+        postForEntity = this.acsAdminRestTemplate.postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                new HttpEntity<>(policyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.DENY);
+
+    }
+
+    /**
+     * This test makes sure that cached policy evaluation results are properly invalidated when one of the policies in
+     * a multiple policy set evaluation order list changes.
+     */
+    @Test
+    public void testPolicyEvalCacheWithMultiplePolicySets() throws Exception {
+
+        String indeterminatePolicyFile = "src/test/resources/policies/indeterminate.json";
+        String denyAllPolicyFile = "src/test/resources/policies/deny-all.json";
+        String siteBasedPolicyFile = "src/test/resources/policies/single-site-based.json";
+        String endpoint = this.acsUrl;
+
+        this.privilegeHelper.putSubject(this.acsAdminRestTemplate, MARISSA_V1, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getDefaultAttribute());
+
+        String indeterminatePolicySet = this.policyHelper
+                .setTestPolicy(this.acsAdminRestTemplate, this.acsZone1Headers, endpoint, indeterminatePolicyFile);
+        String denyAllPolicySet = this.policyHelper
+                .setTestPolicy(this.acsAdminRestTemplate, this.acsZone1Headers, endpoint, denyAllPolicyFile);
+
+        // test with a valid policy set evaluation order list
+        PolicyEvaluationRequestV1 policyEvaluationRequest = this.policyHelper
+                .createMultiplePolicySetsEvalRequest(MARISSA_V1.getSubjectIdentifier(), "sanramon",
+                        Stream.of(indeterminatePolicySet, denyAllPolicySet)
+                                .collect(Collectors.toCollection(LinkedHashSet::new)));
+
+        ResponseEntity<PolicyEvaluationResult> postForEntity = this.acsAdminRestTemplate
+                .postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                        new HttpEntity<>(policyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        PolicyEvaluationResult responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.DENY);
+
+        // test with one of the policy sets changed from the evaluation order list
+        String siteBasedPolicySet = this.policyHelper
+                .setTestPolicy(this.acsAdminRestTemplate, this.acsZone1Headers, endpoint, siteBasedPolicyFile);
+        policyEvaluationRequest = this.policyHelper
+                .createMultiplePolicySetsEvalRequest(MARISSA_V1.getSubjectIdentifier(), "sanramon",
+                        Stream.of(indeterminatePolicySet, siteBasedPolicySet)
+                                .collect(Collectors.toCollection(LinkedHashSet::new)));
+
+        postForEntity = this.acsAdminRestTemplate.postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                new HttpEntity<>(policyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.PERMIT);
+
+    }
+
+    /**
+     * This test makes sure that cached policy evaluation results are properly invalidated when an affected resource
+     * is created.
+     */
+    @Test
+    public void testPolicyEvalCacheWhenResourceAdded() throws Exception {
+        String endpoint = this.acsUrl;
+
+        // create test subject
+        BaseSubject subject = MARISSA_V1;
+        this.privilegeHelper.putSubject(this.acsAdminRestTemplate, subject, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getDefaultAttribute(), this.privilegeHelper.getDefaultOrgAttribute());
+
+        // create test policy set
+        String policyFile = "src/test/resources/policies/single-org-based.json";
+        this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.acsZone1Headers, endpoint, policyFile);
+
+        // post policy evaluation request
+        PolicyEvaluationRequestV1 policyEvaluationRequest = this.policyHelper
+                .createEvalRequest(MARISSA_V1.getSubjectIdentifier(), "sanramon");
+        ResponseEntity<PolicyEvaluationResult> postForEntity = this.acsAdminRestTemplate
+                .postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                        new HttpEntity<>(policyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        PolicyEvaluationResult responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.NOT_APPLICABLE);
+
+        // at this point evaluation decision is cached
+        // timestamps for subject and resource involved in the decision are also cached even though resource doesn't
+        // exist yet
+
+        // add a resource which is expected to reset resource cached timestamp and invalidate cached decision
+        BaseResource resource = new BaseResource("/secured-by-value/sites/sanramon");
+        this.privilegeHelper.putResource(this.acsAdminRestTemplate, resource, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getDefaultOrgAttribute());
+
+        // post policy evaluation request; decision should be reevaluated.
+        postForEntity = this.acsAdminRestTemplate.postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                new HttpEntity<>(policyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.PERMIT);
+    }
+
+    /**
+     * This test makes sure that cached policy evaluation results are properly invalidated when an affected resource
+     * is updated with different attributes.
+     */
+    @Test
+    public void testPolicyEvalCacheWhenResourceChanges() throws Exception {
+        BaseResource resource = new BaseResource("/secured-by-value/sites/sanramon");
+        BaseSubject subject = MARISSA_V1;
+        PolicyEvaluationRequestV1 policyEvaluationRequest = this.policyHelper
+                .createEvalRequest(MARISSA_V1.getSubjectIdentifier(), "sanramon");
+        String endpoint = this.acsUrl;
+
+        this.privilegeHelper.putResource(this.acsAdminRestTemplate, resource, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getDefaultOrgAttribute());
+        this.privilegeHelper.putSubject(this.acsAdminRestTemplate, subject, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getDefaultAttribute(), this.privilegeHelper.getDefaultOrgAttribute());
+        String policyFile = "src/test/resources/policies/single-org-based.json";
+        this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.acsZone1Headers, endpoint, policyFile);
+
+        ResponseEntity<PolicyEvaluationResult> postForEntity = this.acsAdminRestTemplate
+                .postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                        new HttpEntity<>(policyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        PolicyEvaluationResult responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.PERMIT);
+
+        // update resource with different attributes
+        this.privilegeHelper.putResource(this.acsAdminRestTemplate, resource, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getAlternateOrgAttribute());
+
+        postForEntity = this.acsAdminRestTemplate.postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                new HttpEntity<>(policyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.NOT_APPLICABLE);
+
+    }
+
+    /**
+     * This test makes sure that cached policy evaluation results are properly invalidated when an affected subject
+     * is created.
+     */
+    @Test
+    public void testPolicyEvalCacheWhenSubjectAdded() throws Exception {
+        String endpoint = this.acsUrl;
+
+        //create test resource
+        BaseResource resource = new BaseResource("/secured-by-value/sites/sanramon");
+        this.privilegeHelper.putResource(this.acsAdminRestTemplate, resource, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getDefaultAttribute());
+
+        // create test policy set
+        String policyFile = "src/test/resources/policies/single-site-based.json";
+        this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.acsZone1Headers, endpoint, policyFile);
+
+        // post policy evaluation request
+        PolicyEvaluationRequestV1 policyEvaluationRequest = this.policyHelper
+                .createEvalRequest(MARISSA_V1.getSubjectIdentifier(), "sanramon");
+        ResponseEntity<PolicyEvaluationResult> postForEntity = this.acsAdminRestTemplate
+                .postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                        new HttpEntity<>(policyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        PolicyEvaluationResult responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.NOT_APPLICABLE);
+
+        // at this point evaluation decision is cached
+        // timestamps for resource and subject involved in the decision are also cached even though subject doesn't
+        // exist yet
+
+        // add a subject which is expected to reset subject cached timestamp and invalidate cached decision
+        BaseSubject subject = MARISSA_V1;
+        this.privilegeHelper.putSubject(this.acsAdminRestTemplate, subject, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getDefaultAttribute(), this.privilegeHelper.getDefaultAttribute());
+
+        // post policy evaluation request; decision should be reevaluated.
+        postForEntity = this.acsAdminRestTemplate.postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                new HttpEntity<>(policyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.PERMIT);
+    }
+
+    /**
+     * This test makes sure that cached policy evaluation results are properly invalidated when an affected subject is
+     * updated or deleted.
+     */
+    @Test
+    public void testPolicyEvalCacheWhenSubjectChanges() throws Exception {
+        PolicyEvaluationRequestV1 policyEvaluationRequest = this.policyHelper
+                .createEvalRequest(MARISSA_V1.getSubjectIdentifier(), "sanramon");
+        String endpoint = this.acsUrl;
+
+        this.privilegeHelper.putSubject(this.acsAdminRestTemplate, MARISSA_V1, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getDefaultAttribute());
+        String policyFile = "src/test/resources/single-site-based-policy-set.json";
+        this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.acsZone1Headers, endpoint, policyFile);
+
+        ResponseEntity<PolicyEvaluationResult> postForEntity = this.acsAdminRestTemplate
+                .postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                        new HttpEntity<>(policyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        PolicyEvaluationResult responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.PERMIT);
+
+        this.privilegeHelper.putSubject(this.acsAdminRestTemplate, MARISSA_V1, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getAlternateAttribute());
+
+        postForEntity = this.acsAdminRestTemplate.postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                new HttpEntity<>(policyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.NOT_APPLICABLE);
+    }
+
+    /**
+     * This test makes sure that cached policy evaluation results are properly invalidated when a policy has multiple
+     * resource attribute URI templates that match the request.
+     */
+    @Test
+    public void testPolicyWithMultAttrUriTemplatatesEvalCache() throws Exception {
+        BaseSubject subject = MARISSA_V1;
+        PolicyEvaluationRequestV1 policyEvaluationRequest = this.policyHelper
+                .createEvalRequest("GET", MARISSA_V1.getSubjectIdentifier(), "/v1/site/1/plant/asset/1", null);
+        String endpoint = this.acsUrl;
+
+        this.privilegeHelper.putSubject(this.acsAdminRestTemplate, subject, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getDefaultAttribute(), this.privilegeHelper.getDefaultOrgAttribute());
+        String policyFile = "src/test/resources/policies/multiple-attribute-uri-templates.json";
+        this.policyHelper.setTestPolicy(this.acsAdminRestTemplate, this.acsZone1Headers, endpoint, policyFile);
+
+        ResponseEntity<PolicyEvaluationResult> postForEntity = this.acsAdminRestTemplate
+                .postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                        new HttpEntity<>(policyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        PolicyEvaluationResult responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.NOT_APPLICABLE);
+
+        BaseResource siteResource = new BaseResource("/site/1");
+        siteResource.setAttributes(new HashSet<Attribute>(
+                Arrays.asList(new Attribute[] { this.privilegeHelper.getDefaultOrgAttribute() })));
+        this.privilegeHelper.putResource(this.acsAdminRestTemplate, siteResource, endpoint, this.acsZone1Headers,
+                this.privilegeHelper.getDefaultOrgAttribute());
+
+        postForEntity = this.acsAdminRestTemplate.postForEntity(endpoint + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                new HttpEntity<>(policyEvaluationRequest, this.acsZone1Headers), PolicyEvaluationResult.class);
+
+        Assert.assertEquals(postForEntity.getStatusCode(), HttpStatus.OK);
+        responseBody = postForEntity.getBody();
+        Assert.assertEquals(responseBody.getEffect(), Effect.PERMIT);
+
+    }
+
+    @AfterClass
+    public void destroy() {
+        this.acsitSetUpFactory.destroy();
+    }
+
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/integration/test/PolicyEvaluationWithAttributeConnectorIT.java b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/PolicyEvaluationWithAttributeConnectorIT.java
new file mode 100644
index 0000000..81d1e89
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/PolicyEvaluationWithAttributeConnectorIT.java
@@ -0,0 +1,227 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.integration.test;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.sleuth.DefaultSpanNamer;
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.TraceKeys;
+import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
+import org.springframework.cloud.sleuth.instrument.web.ZipkinHttpSpanInjector;
+import org.springframework.cloud.sleuth.instrument.web.client.TraceRestTemplateInterceptor;
+import org.springframework.cloud.sleuth.log.NoOpSpanLogger;
+import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
+import org.springframework.cloud.sleuth.trace.DefaultTracer;
+import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.ImportResource;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.web.client.RestTemplate;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.rest.AttributeAdapterConnection;
+import com.ge.predix.acs.rest.AttributeConnector;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.test.utils.ACSITSetUpFactory;
+import com.ge.predix.test.utils.ACSTestUtil;
+import com.ge.predix.test.utils.PolicyHelper;
+import com.ge.predix.test.utils.ZoneFactory;
+
+@Configuration
+@ImportResource("classpath:integration-test-spring-context.xml")
+class PolicyEvaluationWithAttributeConnectorITConfiguration {
+
+    @Autowired
+    private ACSITSetUpFactory acsitSetUpFactory;
+
+    @Autowired
+    private DefaultTracer tracer;
+
+    private void setRestTemplateInterceptor(final RestTemplate restTemplate) {
+        restTemplate.setInterceptors(Collections.singletonList(
+                new TraceRestTemplateInterceptor(this.tracer, new ZipkinHttpSpanInjector(),
+                        new HttpTraceKeysInjector(this.tracer, new TraceKeys()))));
+    }
+
+    @Bean
+    public DefaultTracer tracer() {
+        return new DefaultTracer(new AlwaysSampler(), new Random(), new DefaultSpanNamer(), new NoOpSpanLogger(),
+                new ArrayListSpanAccumulator(), new TraceKeys());
+    }
+
+    @Bean
+    public OAuth2RestTemplate acsAdminRestTemplate() throws IOException {
+        OAuth2RestTemplate acsAdminRestTemplate;
+        this.acsitSetUpFactory.setUp();
+        acsAdminRestTemplate = this.acsitSetUpFactory.getAcsZonesAdminRestTemplate();
+        setRestTemplateInterceptor(acsAdminRestTemplate);
+        return acsAdminRestTemplate;
+    }
+    
+    @Bean
+    public ACSITSetUpFactory acsitSetUpFactory() {
+        return this.acsitSetUpFactory;
+    }
+}
+
+@ContextConfiguration(classes = { PolicyEvaluationWithAttributeConnectorITConfiguration.class })
+public class PolicyEvaluationWithAttributeConnectorIT extends AbstractTestNGSpringContextTests {
+
+    @Value("${ACS_UAA_URL}")
+    private String acsUaaUrl;
+
+    @Value("${ADAPTER_ENDPOINT:${ASSET_ADAPTER_URL}}")
+    private String adapterEndpoint;
+
+    @Value("${ADAPTER_UAA_TOKEN_URL:${ASSET_TOKEN_URL}}")
+    private String adapterUaaTokenUrl;
+
+    @Value("${ADAPTER_UAA_CLIENT_ID:${ASSET_CLIENT_ID}}")
+    private String adapterUaaClientId;
+
+    @Value("${ADAPTER_UAA_CLIENT_SECRET:${ASSET_CLIENT_SECRET}}")
+    private String adapterUaaClientSecret;
+
+    @Autowired
+    private ZoneFactory zoneFactory;
+
+    @Autowired
+    private PolicyHelper policyHelper;
+
+    @Autowired
+    private OAuth2RestTemplate acsAdminRestTemplate;
+
+    @Autowired
+    private ACSITSetUpFactory acsitSetUpFactory;
+
+    @Autowired
+    private DefaultTracer tracer;
+
+    private static final String TEST_PART_ID = "part/03f95db1-4255-4265-a509-f7bca3e1fee4";
+
+    private Zone zone;
+
+    private URI resourceAttributeConnectorUrl;
+
+    private HttpHeaders zoneHeader() throws IOException {
+        HttpHeaders httpHeaders = ACSTestUtil.httpHeaders();
+        httpHeaders.set(PolicyHelper.PREDIX_ZONE_ID, this.zone.getSubdomain());
+        return httpHeaders;
+    }
+
+    private void configureAttributeConnector(final boolean isActive) throws IOException {
+
+        List<AttributeAdapterConnection> adapters = Collections.singletonList(
+                new AttributeAdapterConnection(this.adapterEndpoint, this.adapterUaaTokenUrl, this.adapterUaaClientId,
+                        this.adapterUaaClientSecret));
+
+        AttributeConnector attributeConnector = new AttributeConnector();
+        attributeConnector.setIsActive(isActive);
+        attributeConnector.setAdapters(new HashSet<>(adapters));
+        this.acsAdminRestTemplate.exchange(this.resourceAttributeConnectorUrl, HttpMethod.PUT,
+                new HttpEntity<>(attributeConnector, zoneHeader()), AttributeConnector.class);
+    }
+
+    @BeforeClass
+    void beforeClass() throws IOException {
+
+        this.zone = this.acsitSetUpFactory.getZone1();
+        this.resourceAttributeConnectorUrl = URI.create(this.zoneFactory.getAcsBaseURL() + "/v1/connector/resource");
+    }
+
+    private void deconfigureAttributeConnector() throws IOException {
+        this.acsAdminRestTemplate
+                .exchange(this.resourceAttributeConnectorUrl, HttpMethod.DELETE, new HttpEntity<>(zoneHeader()),
+                        Void.class);
+    }
+
+    @AfterClass
+    void afterClass() throws IOException {
+        this.acsitSetUpFactory.destroy();
+    }
+
+    @Test(dataProvider = "adapterStatusesAndResultingEffects")
+    public void testPolicyEvaluationWithAdapters(final boolean adapterActive, final Effect expectedEffect,
+            final boolean enableSleuthTracing) throws Exception {
+        String testPolicyName = this.policyHelper
+                .setTestPolicy(this.acsAdminRestTemplate, zoneHeader(), this.zoneFactory.getAcsBaseURL(),
+                        "src/test/resources/policy-set-with-one-policy-using-resource-attributes-from-asset-adapter"
+                                + ".json");
+
+        try {
+            this.configureAttributeConnector(adapterActive);
+            PolicyEvaluationRequestV1 policyEvaluationRequest = this.policyHelper
+                    .createEvalRequest("GET", "testSubject", TEST_PART_ID, null);
+
+            if (enableSleuthTracing) {
+                this.tracer.continueSpan(Span.builder().traceId(1L).spanId(2L).parent(3L).build());
+            }
+
+            ResponseEntity<PolicyEvaluationResult> policyEvaluationResponse = this.acsAdminRestTemplate
+                    .postForEntity(this.zoneFactory.getAcsBaseURL() + PolicyHelper.ACS_POLICY_EVAL_API_PATH,
+                            new HttpEntity<>(policyEvaluationRequest, zoneHeader()), PolicyEvaluationResult.class);
+            Assert.assertEquals(policyEvaluationResponse.getStatusCode(), HttpStatus.OK);
+
+            HttpHeaders responseHeaders = policyEvaluationResponse.getHeaders();
+            Assert.assertTrue(
+                    responseHeaders.containsKey(Span.TRACE_ID_NAME) && StringUtils.isNotEmpty(Span.TRACE_ID_NAME));
+            if (enableSleuthTracing) {
+                Assert.assertEquals(Span.hexToId(responseHeaders.get(Span.TRACE_ID_NAME).get(0)), 1L);
+            }
+
+            PolicyEvaluationResult policyEvaluationResult = policyEvaluationResponse.getBody();
+            Assert.assertEquals(policyEvaluationResult.getEffect(), expectedEffect);
+        } finally {
+            this.policyHelper
+                    .deletePolicySet(this.acsAdminRestTemplate, this.zoneFactory.getAcsBaseURL(), testPolicyName,
+                            zoneHeader());
+            this.deconfigureAttributeConnector();
+        }
+    }
+
+    @DataProvider
+    private Object[][] adapterStatusesAndResultingEffects() {
+        return new Object[][] { { true, Effect.PERMIT, true }, { true, Effect.PERMIT, false },
+                { false, Effect.NOT_APPLICABLE, true } };
+    }
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/integration/test/PrivilegeManagementAccessControlServiceIT.java b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/PrivilegeManagementAccessControlServiceIT.java
new file mode 100644
index 0000000..67727fd
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/PrivilegeManagementAccessControlServiceIT.java
@@ -0,0 +1,572 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.integration.test;
+
+import static com.ge.predix.integration.test.SubjectResourceFixture.BOB_V1;
+import static com.ge.predix.integration.test.SubjectResourceFixture.JLO_V1;
+import static com.ge.predix.integration.test.SubjectResourceFixture.JOE_V1;
+import static com.ge.predix.integration.test.SubjectResourceFixture.MARISSA_V1;
+import static com.ge.predix.integration.test.SubjectResourceFixture.PETE_V1;
+import static com.ge.predix.integration.test.SubjectResourceFixture.SANRAMON;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.HttpServerErrorException;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.test.TestConfig;
+import com.ge.predix.test.utils.ACSITSetUpFactory;
+import com.ge.predix.test.utils.ACSTestUtil;
+import com.ge.predix.test.utils.PolicyHelper;
+import com.ge.predix.test.utils.PrivilegeHelper;
+import com.ge.predix.test.utils.ZoneFactory;
+
+@ContextConfiguration("classpath:integration-test-spring-context.xml")
+// @Test(dependsOnGroups = { "acsHealthCheck.*" })
+@Test
+public class PrivilegeManagementAccessControlServiceIT extends AbstractTestNGSpringContextTests {
+
+    private String acsZone1Name;
+
+    @Value("${zone1UaaUrl}/oauth/token")
+    private String primaryZoneIssuerId;
+
+    private String acsZone3Name;
+
+    @Autowired
+    private ZoneFactory zoneFactory;
+
+    @Autowired
+    private PrivilegeHelper privilegeHelper;
+
+    @Autowired
+    private ACSITSetUpFactory acsitSetUpFactory;
+
+    private String acsUrl;
+    private HttpHeaders zone1Headers;
+    private HttpHeaders zone3Headers;
+    private OAuth2RestTemplate acsAdminRestTemplate;
+
+    @BeforeClass
+    public void setup() throws JsonParseException, JsonMappingException, IOException {
+        TestConfig.setupForEclipse(); // Starts ACS when running the test in eclipse.
+        this.acsitSetUpFactory.setUp();
+        this.acsUrl = this.acsitSetUpFactory.getAcsUrl();
+        this.zone1Headers = this.acsitSetUpFactory.getZone1Headers();
+        this.zone3Headers = this.acsitSetUpFactory.getZone3Headers();
+        this.acsAdminRestTemplate = this.acsitSetUpFactory.getAcsZonesAdminRestTemplate();
+        this.acsZone1Name = this.acsitSetUpFactory.getZone1().getSubdomain();
+        this.acsZone3Name = this.acsitSetUpFactory.getAcsZone3Name();
+    }
+
+    public void testBatchCreateSubjectsEmptyList() {
+        List<BaseSubject> subjects = new ArrayList<BaseSubject>();
+        try {
+            this.acsAdminRestTemplate.postForEntity(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH,
+                    new HttpEntity<>(subjects, this.zone1Headers), BaseSubject[].class);
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.UNPROCESSABLE_ENTITY);
+            return;
+        }
+        Assert.fail("Expected unprocessable entity http client error.");
+    }
+
+    @Test
+    public void testBatchSubjectsDataConstraintViolationSubjectIdentifier() {
+        List<BaseSubject> subjects = new ArrayList<BaseSubject>();
+        subjects.add(this.privilegeHelper.createSubject("marissa"));
+        subjects.add(this.privilegeHelper.createSubject("marissa"));
+
+        try {
+            this.acsAdminRestTemplate.postForEntity(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH,
+                    new HttpEntity<>(subjects, this.zone1Headers), ResponseEntity.class);
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.UNPROCESSABLE_ENTITY);
+            return;
+        }
+        this.acsAdminRestTemplate
+                .exchange(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH + "/marissa", HttpMethod.DELETE,
+                        new HttpEntity<>(this.zone1Headers), ResponseEntity.class);
+        Assert.fail("Expected unprocessable entity http client error.");
+    }
+
+    public void testCreateSubjectWithMalformedJSON() {
+        try {
+            String badSubject = "{\"subject\": bad-subject-form\"}";
+            MultiValueMap<String, String> headers = ACSTestUtil.httpHeaders();
+            headers.add("Content-type", "application/json");
+            headers.add(PolicyHelper.PREDIX_ZONE_ID, this.acsZone1Name);
+            HttpEntity<String> httpEntity = new HttpEntity<String>(badSubject, headers);
+            this.acsAdminRestTemplate
+                    .put(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH + "/bad-subject-form", httpEntity);
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.BAD_REQUEST);
+            return;
+        }
+        this.acsAdminRestTemplate
+                .exchange(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH + "/bad-subject-form", HttpMethod.DELETE,
+                        new HttpEntity<>(this.zone1Headers), ResponseEntity.class);
+        Assert.fail("testCreateSubjectWithMalformedJSON should have failed!");
+    }
+
+    public void testCreateBatchSubjectsWithMalformedJSON() {
+        try {
+            String badSubject =
+                    "{\"subject\":{\"name\" : \"good-subject-brittany\"}," + "{\"subject\": bad-subject-sarah\"}";
+            MultiValueMap<String, String> headers = ACSTestUtil.httpHeaders();
+            headers.add("Content-type", "application/json");
+            headers.add(PolicyHelper.PREDIX_ZONE_ID, this.acsZone1Name);
+            HttpEntity<String> httpEntity = new HttpEntity<String>(badSubject, headers);
+            this.acsAdminRestTemplate
+                    .postForEntity(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH, httpEntity, Subject[].class);
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.BAD_REQUEST);
+            return;
+        }
+        this.acsAdminRestTemplate
+                .exchange(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH + "/bad-subject-sarah", HttpMethod.DELETE,
+                        new HttpEntity<>(this.zone1Headers), ResponseEntity.class);
+        this.acsAdminRestTemplate
+                .exchange(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH + "/good-subject-brittany",
+                        HttpMethod.DELETE, new HttpEntity<>(this.zone1Headers), ResponseEntity.class);
+        Assert.fail("testCreateBatchSubjectsWithMalformedJSON should have failed!");
+    }
+
+    public void testCreateResourceWithMalformedJSON() {
+        try {
+            String badResource = "{\"resource\": bad-resource-form\"}";
+            MultiValueMap<String, String> headers = ACSTestUtil.httpHeaders();
+            headers.add("Content-type", "application/json");
+            headers.add(PolicyHelper.PREDIX_ZONE_ID, this.acsZone1Name);
+            HttpEntity<String> httpEntity = new HttpEntity<String>(badResource, headers);
+            this.acsAdminRestTemplate
+                    .put(this.acsUrl + PrivilegeHelper.ACS_RESOURCE_API_PATH + "/bad-resource-form", httpEntity);
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.BAD_REQUEST);
+            return;
+        }
+        this.acsAdminRestTemplate
+                .exchange(this.acsUrl + PrivilegeHelper.ACS_RESOURCE_API_PATH + "/bad-resource-form", HttpMethod.DELETE,
+                        new HttpEntity<>(this.zone1Headers), ResponseEntity.class);
+        Assert.fail("testCreateResourceWithMalformedJSON should have failed!");
+    }
+
+    public void testCreateBatchResourcesWithMalformedJSON() {
+        try {
+            String badResource = "{\"resource\":{\"name\" : \"Site\", \"uriTemplate\" : "
+                    + "\"/secured-by-value/sites/{site_id}\"},{\"resource\": bad-resource-form\"}";
+            MultiValueMap<String, String> headers = ACSTestUtil.httpHeaders();
+            headers.add("Content-type", "application/json");
+            headers.add(PolicyHelper.PREDIX_ZONE_ID, this.acsZone1Name);
+            HttpEntity<String> httpEntity = new HttpEntity<String>(badResource, headers);
+            this.acsAdminRestTemplate.postForEntity(this.acsUrl + PrivilegeHelper.ACS_RESOURCE_API_PATH, httpEntity,
+                    BaseResource[].class);
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.BAD_REQUEST);
+            return;
+        }
+        this.acsAdminRestTemplate
+                .exchange(this.acsUrl + PrivilegeHelper.ACS_RESOURCE_API_PATH + "/bad-resource-form", HttpMethod.DELETE,
+                        new HttpEntity<>(this.zone1Headers), ResponseEntity.class);
+        this.acsAdminRestTemplate
+                .exchange(this.acsUrl + PrivilegeHelper.ACS_RESOURCE_API_PATH + "/Site", HttpMethod.DELETE,
+                        new HttpEntity<>(this.zone1Headers), ResponseEntity.class);
+        Assert.fail("testCreateBatchResourcesWithMalformedJSON should have failed!");
+    }
+
+    public void testBatchCreateResourcesEmptyList() {
+        List<BaseResource> resources = new ArrayList<BaseResource>();
+        try {
+            this.acsAdminRestTemplate.postForEntity(this.acsUrl + PrivilegeHelper.ACS_RESOURCE_API_PATH,
+                    new HttpEntity<>(resources, this.zone1Headers), BaseResource[].class);
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.UNPROCESSABLE_ENTITY);
+            return;
+        }
+        Assert.fail("Expected unprocessable entity http client error.");
+    }
+
+    public void testResourceUpdateAttributes() {
+        BaseResource resource1 = this.privilegeHelper.createResource("marissa");
+        BaseResource resource2 = this.privilegeHelper.createResource("marissa");
+        Set<Attribute> attributes = new HashSet<Attribute>();
+        Attribute attribute = new Attribute();
+        attribute.setName("site");
+        attribute.setIssuer("http://attributes.net");
+        attribute.setValue("sanfrancisco");
+        attributes.add(attribute);
+        resource2.setAttributes(attributes);
+
+        this.acsAdminRestTemplate.put(this.acsUrl + PrivilegeHelper.ACS_RESOURCE_API_PATH + "/marissa",
+                new HttpEntity<>(resource1, this.zone1Headers));
+        this.acsAdminRestTemplate.put(this.acsUrl + PrivilegeHelper.ACS_RESOURCE_API_PATH + "/marissa",
+                new HttpEntity<>(resource2, this.zone1Headers));
+        ResponseEntity<BaseResource> response = acsAdminRestTemplate
+                .exchange(this.acsUrl + PrivilegeHelper.ACS_RESOURCE_API_PATH + "/marissa", HttpMethod.GET,
+                        new HttpEntity<>(this.zone1Headers), BaseResource.class);
+        Assert.assertEquals(response.getBody(), resource2);
+        this.acsAdminRestTemplate
+                .exchange(this.acsUrl + PrivilegeHelper.ACS_RESOURCE_API_PATH + "/marissa", HttpMethod.DELETE,
+                        new HttpEntity<>(this.zone1Headers), ResponseEntity.class);
+    }
+
+    @Test
+    public void testBatchResourcesDataConstraintViolationResourceIdentifier() {
+        List<BaseResource> resources = new ArrayList<BaseResource>();
+        resources.add(this.privilegeHelper.createResource("dupResourceIdentifier"));
+        resources.add(this.privilegeHelper.createResource("dupResourceIdentifier"));
+
+        try {
+            // This POST causes a data constraint violation on the service bcos
+            // of duplicate
+            // resource_identifiers which returns a HTTP 422 error.
+            this.acsAdminRestTemplate.postForEntity(this.acsUrl + PrivilegeHelper.ACS_RESOURCE_API_PATH,
+                    new HttpEntity<>(resources, this.zone1Headers), ResponseEntity.class);
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.UNPROCESSABLE_ENTITY);
+            return;
+        }
+        this.acsAdminRestTemplate
+                .exchange(this.acsUrl + PrivilegeHelper.ACS_RESOURCE_API_PATH + "/marissa", HttpMethod.DELETE,
+                        new HttpEntity<>(this.zone1Headers), ResponseEntity.class);
+        Assert.fail("Expected unprocessable entity http client error on post for 2 resources with duplicate resource"
+                + "identifiers.");
+    }
+
+    @Test(dataProvider = "subjectProvider")
+    public void testPutGetDeleteSubject(final BaseSubject subject) throws UnsupportedEncodingException {
+        ResponseEntity<BaseSubject> responseEntity = null;
+        try {
+            this.privilegeHelper.putSubject(this.acsAdminRestTemplate, subject, this.acsUrl, this.zone1Headers,
+                    this.privilegeHelper.getDefaultAttribute());
+        } catch (HttpClientErrorException e) {
+            Assert.fail("Unable to create subject.");
+        }
+        String encodedSubjectIdentifier = URLEncoder.encode(subject.getSubjectIdentifier(), "UTF-8");
+        URI uri = URI.create(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH + encodedSubjectIdentifier);
+        try {
+            responseEntity = this.acsAdminRestTemplate
+                    .exchange(uri, HttpMethod.GET, new HttpEntity<>(this.zone1Headers), BaseSubject.class);
+            Assert.assertEquals(responseEntity.getStatusCode(), HttpStatus.OK);
+        } catch (HttpClientErrorException e) {
+            Assert.fail("Unable to get subject.");
+        }
+        try {
+            this.acsAdminRestTemplate
+                    .exchange(uri, HttpMethod.DELETE, new HttpEntity<>(this.zone1Headers), ResponseEntity.class);
+            responseEntity = this.acsAdminRestTemplate
+                    .exchange(uri, HttpMethod.GET, new HttpEntity<>(this.zone1Headers), BaseSubject.class);
+            Assert.fail("Subject " + subject.getSubjectIdentifier() + " was not properly deleted");
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.NOT_FOUND, "Subject was not deleted.");
+        }
+
+    }
+
+    // To test cascade delete for postgres, comment out delete-db-service and delete executions. Run integration with
+    // -PCloud. Bind db to pgPhpAdmin and browse the db to ensure all entries with zone 'test-zone-dev3' as a foreign
+    // key are deleted respectively.
+    public void testPutSubjectDeleteZone() throws JsonParseException, JsonMappingException, IOException {
+
+        this.zoneFactory.createTestZone(this.acsAdminRestTemplate, this.acsZone3Name,
+                Collections.singletonList(this.primaryZoneIssuerId));
+
+        ResponseEntity<BaseSubject> responseEntity = null;
+        try {
+            this.privilegeHelper.putSubject(this.acsAdminRestTemplate, MARISSA_V1, this.acsUrl, this.zone3Headers,
+                    this.privilegeHelper.getDefaultAttribute());
+
+        } catch (HttpClientErrorException e) {
+            Assert.fail("Unable to create subject.", e);
+        }
+        try {
+            responseEntity = this.acsAdminRestTemplate
+                    .exchange(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH + MARISSA_V1.getSubjectIdentifier(),
+                            HttpMethod.GET, new HttpEntity<>(this.zone3Headers), BaseSubject.class);
+            Assert.assertEquals(responseEntity.getStatusCode(), HttpStatus.OK);
+        } catch (HttpClientErrorException e) {
+            Assert.fail("Unable to get subject.", e);
+        }
+        try {
+            this.zoneFactory.deleteZone(this.acsAdminRestTemplate, this.acsZone3Name);
+            this.acsAdminRestTemplate
+                    .exchange(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH + MARISSA_V1.getSubjectIdentifier(),
+                            HttpMethod.GET, new HttpEntity<>(this.zone3Headers), BaseSubject.class);
+            Assert.fail("Zone '" + this.acsZone3Name + "' was not properly deleted.");
+        } catch (HttpServerErrorException e) {
+            // This following lines to be uncommented once TokenService returns the right exception instead of a
+            // 500 - Defect url https://rally1.rallydev.com/#/30377833713d/detail/defect/42793900179
+            // catch (OAuth2Exception e) {
+            // Assert.assertTrue(e.getSummary().contains(HttpStatus.FORBIDDEN.toString()),
+            // "Zone deletion did not produce the expected HTTP status code. Failed with: " + e);
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.BAD_REQUEST);
+        } catch (Exception e) {
+            Assert.fail("Failed with unexpected exception.", e);
+        }
+    }
+
+    public void testPutSubjectMismatchURI() {
+        try {
+            String subjectIdentifier = "marcia";
+            URI subjectUri = URI.create(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH + URLEncoder
+                    .encode(subjectIdentifier, "UTF-8"));
+            this.acsAdminRestTemplate.put(subjectUri, new HttpEntity<>(BOB_V1, this.zone1Headers));
+            Assert.fail("Subject " + subjectIdentifier + " was not supposed to be created");
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.UNPROCESSABLE_ENTITY);
+            return;
+        } catch (Exception e) {
+            Assert.fail("Unable to create subject.");
+        }
+        Assert.fail("Expected Unprocessible Entity status code in testPutSubjectMismatchURIV1");
+    }
+
+    @Test(dataProvider = "invalidSubjectPostProvider")
+    public void testPostSubjectNegativeCases(final BaseSubject subject, final String endpoint) {
+        try {
+
+            this.privilegeHelper.postMultipleSubjects(this.acsAdminRestTemplate, endpoint, this.zone1Headers, subject);
+
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.UNPROCESSABLE_ENTITY);
+            return;
+        } catch (Exception e) {
+            Assert.fail("Unable to create subject.");
+        }
+        Assert.fail("Expected Unprocessible Entity status code in testPostSubjectNegativeCases");
+    }
+
+    @Test(dataProvider = "subjectPostProvider")
+    public void testPostSubjectPostiveCases(final BaseSubject subject, final String endpoint) {
+        try {
+            ResponseEntity<Object> responseEntity = this.privilegeHelper
+                    .postMultipleSubjects(this.acsAdminRestTemplate, endpoint, this.zone1Headers, subject);
+            Assert.assertEquals(responseEntity.getStatusCode(), HttpStatus.NO_CONTENT);
+        } catch (Exception e) {
+            Assert.fail("Unable to create subject.");
+        }
+    }
+
+    @Test(dataProvider = "subjectPostProvider")
+    public void testPostSubjectsUpdateAttributes(final BaseSubject subject, final String endpoint) {
+        // This test was added to test that the graph repo behaves transactionally.
+        try {
+            BaseSubject subject2 = new BaseSubject(BOB_V1.getSubjectIdentifier());
+            subject2.setAttributes(new HashSet<Attribute>(
+                    Arrays.asList(new Attribute[] { this.privilegeHelper.getDefaultAttribute() })));
+            subject.setAttributes(new HashSet<Attribute>(
+                    Arrays.asList(new Attribute[] { this.privilegeHelper.getDefaultAttribute() })));
+            ResponseEntity<Object> responseEntity = this.privilegeHelper
+                    .postSubjects(this.acsAdminRestTemplate, endpoint, this.zone1Headers, subject, subject2);
+            Assert.assertEquals(responseEntity.getStatusCode(), HttpStatus.NO_CONTENT);
+            subject2.setAttributes(new HashSet<Attribute>(
+                    Arrays.asList(new Attribute[] { this.privilegeHelper.getAlternateAttribute() })));
+            subject.setAttributes(new HashSet<Attribute>(
+                    Arrays.asList(new Attribute[] { this.privilegeHelper.getAlternateAttribute() })));
+            this.privilegeHelper
+                    .postSubjects(this.acsAdminRestTemplate, endpoint, this.zone1Headers, subject, subject2);
+            String encodedSubjectIdentifier = URLEncoder.encode(subject.getSubjectIdentifier(), "UTF-8");
+            URI uri = URI.create(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH + encodedSubjectIdentifier);
+            ResponseEntity<BaseSubject> forEntity = this.acsAdminRestTemplate
+                    .exchange(uri, HttpMethod.GET, new HttpEntity<>(this.zone1Headers), BaseSubject.class);
+            Assert.assertTrue(
+                    forEntity.getBody().getAttributes().contains(this.privilegeHelper.getAlternateAttribute()));
+            encodedSubjectIdentifier = URLEncoder.encode(subject2.getSubjectIdentifier(), "UTF-8");
+            uri = URI.create(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH + encodedSubjectIdentifier);
+            forEntity = this.acsAdminRestTemplate
+                    .exchange(uri, HttpMethod.GET, new HttpEntity<>(this.zone1Headers), BaseSubject.class);
+            Assert.assertTrue(
+                    forEntity.getBody().getAttributes().contains(this.privilegeHelper.getAlternateAttribute()));
+
+            encodedSubjectIdentifier = URLEncoder.encode(subject.getSubjectIdentifier(), "UTF-8");
+            uri = URI.create(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH + encodedSubjectIdentifier);
+            forEntity = this.acsAdminRestTemplate
+                    .exchange(uri, HttpMethod.GET, new HttpEntity<>(this.zone1Headers), BaseSubject.class);
+            Assert.assertTrue(
+                    forEntity.getBody().getAttributes().contains(this.privilegeHelper.getAlternateAttribute()));
+            encodedSubjectIdentifier = URLEncoder.encode(subject2.getSubjectIdentifier(), "UTF-8");
+            uri = URI.create(this.acsUrl + PrivilegeHelper.ACS_SUBJECT_API_PATH + encodedSubjectIdentifier);
+            forEntity = this.acsAdminRestTemplate
+                    .exchange(uri, HttpMethod.GET, new HttpEntity<>(this.zone1Headers), BaseSubject.class);
+            Assert.assertTrue(
+                    forEntity.getBody().getAttributes().contains(this.privilegeHelper.getAlternateAttribute()));
+        } catch (Exception e) {
+            Assert.fail("Unable to create subject.");
+        }
+    }
+
+    @Test(dataProvider = "resourcePostProvider")
+    public void testPostResourcePostiveCases(final BaseResource resource, final String endpoint) {
+        try {
+            ResponseEntity<Object> responseEntity = this.privilegeHelper
+                    .postResources(this.acsAdminRestTemplate, endpoint, this.zone1Headers, resource);
+            Assert.assertEquals(responseEntity.getStatusCode(), HttpStatus.NO_CONTENT);
+        } catch (Exception e) {
+            Assert.fail("Unable to create resource.");
+        }
+    }
+
+    @Test(dataProvider = "invalidResourcePostProvider")
+    public void testPostResourceNegativeCases(final BaseResource resource, final String endpoint) {
+        try {
+            this.privilegeHelper.postResources(this.acsAdminRestTemplate, endpoint, this.zone1Headers, resource);
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.UNPROCESSABLE_ENTITY);
+            return;
+        } catch (Exception e) {
+            Assert.fail("Unable to create resource.");
+        }
+        Assert.fail("Expected UnprocessibleEntity status code in testPostResourceNegativeCases");
+    }
+
+    public void testPutGetDeleteResource() throws Exception {
+        try {
+            this.privilegeHelper.putResource(this.acsAdminRestTemplate, SANRAMON, this.acsUrl, this.zone1Headers,
+                    this.privilegeHelper.getDefaultAttribute());
+        } catch (Exception e) {
+            Assert.fail("Unable to create resource. " + e.getMessage());
+        }
+        URI resourceUri = URI.create(this.acsUrl + PrivilegeHelper.ACS_RESOURCE_API_PATH + URLEncoder
+                .encode(SANRAMON.getResourceIdentifier(), "UTF-8"));
+        try {
+            this.acsAdminRestTemplate
+                    .exchange(resourceUri, HttpMethod.GET, new HttpEntity<>(this.zone1Headers), BaseResource.class);
+        } catch (HttpClientErrorException e) {
+            Assert.fail("Unable to get resource.");
+        }
+        try {
+            this.acsAdminRestTemplate.exchange(resourceUri, HttpMethod.DELETE, new HttpEntity<>(this.zone1Headers),
+                    ResponseEntity.class);
+        } catch (HttpClientErrorException e) {
+            Assert.fail("Unable to delete resource.");
+        }
+        // properly delete
+        try {
+            this.acsAdminRestTemplate.exchange(resourceUri, HttpMethod.DELETE, new HttpEntity<>(this.zone1Headers),
+                    ResponseEntity.class);
+            this.acsAdminRestTemplate
+                    .exchange(resourceUri, HttpMethod.GET, new HttpEntity<>(this.zone1Headers), BaseResource.class);
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.NOT_FOUND);
+        }
+    }
+
+    public void testUpdateResourceURIMismatch() throws Exception {
+        try {
+            this.privilegeHelper.putResource(this.acsAdminRestTemplate, SANRAMON, this.acsUrl, this.zone1Headers,
+                    this.privilegeHelper.getDefaultAttribute());
+            URI resourceUri = URI.create(this.acsUrl + PrivilegeHelper.ACS_RESOURCE_API_PATH + URLEncoder
+                    .encode("/different/resource", "UTF-8"));
+            this.acsAdminRestTemplate.put(resourceUri, new HttpEntity<>(SANRAMON, this.zone1Headers));
+        } catch (HttpClientErrorException e) {
+            Assert.assertEquals(e.getStatusCode(), HttpStatus.UNPROCESSABLE_ENTITY);
+            return;
+        }
+        Assert.fail("Expected Unprocessible Entity status code in testUpdateResourceURIMismatchV1");
+    }
+
+    @DataProvider(name = "subjectProvider")
+    public Object[][] getSubjectProvider() {
+        Object[][] data = new Object[][] { { MARISSA_V1 }, { JOE_V1 }, { PETE_V1 }, { JLO_V1 }, { BOB_V1 } };
+        return data;
+    }
+
+    @DataProvider(name = "invalidSubjectPostProvider")
+    public Object[][] getInvalidSubjectsPost() {
+        Object[][] data = new Object[][] {
+                // empty subjectIdentifier
+                { new BaseSubject(null), this.acsUrl } };
+        return data;
+    }
+
+    @DataProvider(name = "resourcePostProvider")
+    public Object[][] getResourcesPost() {
+        Object[][] data = new Object[][] {
+                // non empty resourceIdentifier
+                { new BaseResource("/sites/sanramon"), this.acsUrl }, };
+        return data;
+    }
+
+    @DataProvider(name = "invalidResourcePostProvider")
+    public Object[][] getInvalidResourcesPost() {
+        Object[][] data = new Object[][] {
+                // empty resourceIdentifier
+                { new BaseResource(null), this.acsUrl }, };
+        return data;
+    }
+
+    @DataProvider(name = "subjectPostProvider")
+    public Object[][] getSubjectsPost() {
+        Object[][] data = new Object[][] {
+                // non empty subjectIdentifier
+                { MARISSA_V1, this.acsUrl } };
+        return data;
+    }
+
+    @DataProvider(name = "endpointProvider")
+    public Object[][] getAcsEndpoint() {
+        Object[][] data = new Object[][] { { this.acsUrl } };
+        return data;
+    }
+
+    @AfterMethod
+    public void cleanup() throws Exception {
+        this.privilegeHelper.deleteResources(this.acsAdminRestTemplate, this.acsUrl, this.zone1Headers);
+        this.privilegeHelper.deleteSubjects(this.acsAdminRestTemplate, this.acsUrl, this.zone1Headers);
+    }
+
+    @AfterClass
+    public void destroy() {
+        this.acsitSetUpFactory.destroy();
+    }
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/integration/test/SubjectResourceFixture.java b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/SubjectResourceFixture.java
new file mode 100644
index 0000000..9844d3b
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/integration/test/SubjectResourceFixture.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.integration.test;
+
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+
+public final class SubjectResourceFixture {
+    
+    private SubjectResourceFixture() {
+        //not called
+        throw new IllegalAccessError("Class is non-instantiable");
+     }
+
+    public static final BaseSubject MARISSA_V1 = new BaseSubject("marissa");
+    public static final BaseSubject BOB_V1 = new BaseSubject("GE/bob");
+    public static final BaseSubject JOE_V1 = new BaseSubject("joe@gmail.com");
+    public static final BaseSubject PETE_V1 = new BaseSubject("pete@gmail.com");
+    public static final BaseSubject JLO_V1 = new BaseSubject("123412341324");
+
+    public static final BaseResource SANRAMON = new BaseResource("/sites/sanramon/");
+
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/test/TestConfig.java b/acs-integration-tests/src/test/java/com/ge/predix/test/TestConfig.java
new file mode 100644
index 0000000..55b0dd7
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/test/TestConfig.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.test;
+
+import org.apache.commons.lang.StringUtils;
+import org.testng.annotations.BeforeSuite;
+
+import com.ge.predix.acs.AccessControlService;
+
+public final class TestConfig {
+
+    private TestConfig() {
+        //not called
+     }
+
+    private static boolean acsStarted;
+
+    public static synchronized boolean isAcsStarted() {
+        return acsStarted;
+    }
+
+    @BeforeSuite
+    public static synchronized void setup() {
+        if (!acsStarted) {
+            AccessControlService.main(new String[] {});
+            acsStarted = true;
+        }
+    }
+
+    @BeforeSuite
+    public static synchronized void setupForEclipse() {
+        String runInEclipse = System.getenv("RUN_IN_ECLIPSE");
+        if (StringUtils.isEmpty(runInEclipse) || !runInEclipse.equalsIgnoreCase("true")) {
+            return;
+        }
+        if (!acsStarted) {
+            System.out.println("*** Setting up test for Eclipse ***");
+            String springProfilesActive = System.getenv("SPRING_PROFILES_ACTIVE");
+            if (StringUtils.isEmpty(springProfilesActive)) {
+                springProfilesActive = "h2,public,simple-cache";
+            }
+            System.setProperty("spring.profiles.active", springProfilesActive);
+            AccessControlService.main(new String[] {});
+            acsStarted = true;
+        }
+    }
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/test/TestNameLogger.java b/acs-integration-tests/src/test/java/com/ge/predix/test/TestNameLogger.java
new file mode 100644
index 0000000..3e2eed7
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/test/TestNameLogger.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.test;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+
+public class TestNameLogger implements IInvokedMethodListener {
+    protected enum TestStatus {
+
+        STARTING("Starting"),
+        FINISHING("Finishing"),
+        SKIPPING("Skipping"),
+        ERRORED_OUT("Errored out on");
+
+        private final String name;
+
+        TestStatus(final String name) {
+            this.name = name;
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+    }
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(TestNameLogger.class);
+
+    protected static void logInvocation(final TestStatus testStatus, final IInvokedMethod method) {
+        ITestNGMethod iTestNGMethod = method.getTestMethod();
+        String methodName = iTestNGMethod.getTestClass().getName() + '#' + iTestNGMethod.getMethodName();
+
+        String methodType = "test";
+        if (method.isConfigurationMethod()) {
+            methodType = "configuration";
+        }
+
+        LOGGER.info("{} {} {} method: {}", (testStatus == TestStatus.ERRORED_OUT ? "!!!" : "==="), testStatus,
+                methodType, methodName);
+    }
+
+    @Override
+    public void beforeInvocation(final IInvokedMethod method, final ITestResult testResult) {
+        logInvocation(TestStatus.STARTING, method);
+    }
+
+    @Override
+    public void afterInvocation(final IInvokedMethod method, final ITestResult testResult) {
+        logInvocation((testResult.getStatus() == ITestResult.FAILURE ? TestStatus.ERRORED_OUT : TestStatus.FINISHING),
+                method);
+    }
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/test/utils/ACSITSetUpFactory.java b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/ACSITSetUpFactory.java
new file mode 100644
index 0000000..425b9fd
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/ACSITSetUpFactory.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.test.utils;
+
+import java.io.IOException;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+
+import com.ge.predix.acs.rest.Zone;
+
+/**
+ * @author Sebastian Torres Brown
+ * 
+ *         Setup class for Integration tests. Creates several rest clients with desired levels of authorities, it also
+ *         creates a zone.
+ *
+ */
+public interface ACSITSetUpFactory {
+
+
+    String getAcsUrl();
+
+    Zone getZone1();
+
+    Zone getZone2();
+
+    String getAcsZone1Name();
+
+    String getAcsZone2Name();
+
+    String getAcsZone3Name();
+
+    HttpHeaders getZone1Headers();
+
+    HttpHeaders getZone3Headers();
+
+    OAuth2RestTemplate getAcsZoneAdminRestTemplate();
+
+    OAuth2RestTemplate getAcsAdminRestTemplate(String zone);
+
+    OAuth2RestTemplate getAcsZone2AdminRestTemplate();
+
+    OAuth2RestTemplate getAcsReadOnlyRestTemplate();
+
+    OAuth2RestTemplate getAcsNoPolicyScopeRestTemplate();
+
+    OAuth2RestTemplate getAcsZonesAdminRestTemplate();
+
+    OAuth2RestTemplate getAcsZoneConnectorAdminRestTemplate(String zone);
+
+    OAuth2RestTemplate getAcsZoneConnectorReadRestTemplate(String zone);
+
+    void setUp() throws IOException;
+
+    void destroy();
+
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/test/utils/ACSITSetUpFactoryPublic.java b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/ACSITSetUpFactoryPublic.java
new file mode 100644
index 0000000..573ce64
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/ACSITSetUpFactoryPublic.java
@@ -0,0 +1,197 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.test.utils;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Scope;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.rest.Zone;
+
+@Component
+@Scope("prototype")
+public class ACSITSetUpFactoryPublic implements ACSITSetUpFactory {
+
+    private String acsZone1Name;
+    private String acsZone2Name;
+    private String acsZone3Name;
+
+    @Value("${ACS_TESTING_UAA}")
+    private String uaaUrl;
+
+    @Value("${UAA_ADMIN_SECRET:adminsecret}")
+    private String uaaAdminSecret;
+
+    @Value("${ACS_SERVICE_ID:predix-acs}")
+    private String serviceId;
+
+    private static final String OAUTH_ENDPOINT = "/oauth/token";
+
+    private String acsUrl;
+    private HttpHeaders zone1Headers;
+    private HttpHeaders zone3Headers;
+    private OAuth2RestTemplate acsAdminRestTemplate;
+    private OAuth2RestTemplate acsZonesAdminRestTemplate;
+    private OAuth2RestTemplate acsZone1RestTemplate;
+    private OAuth2RestTemplate acsZone2RestTemplate;
+    private OAuth2RestTemplate acsReadOnlyRestTemplate;
+    private OAuth2RestTemplate acsNoPolicyScopeRestTemplate;
+    private Zone zone1;
+    private Zone zone2;
+
+    private UAAACSClientsUtil uaaTestUtil;
+
+    @Autowired
+    private ZoneFactory zoneFactory;
+
+    @Override
+    public void setUp() throws IOException {
+        // TestConfig.setupForEclipse(); // Starts ACS when running the test in eclipse.
+        this.acsUrl = this.zoneFactory.getAcsBaseURL();
+
+        this.acsZone1Name = ZoneFactory.getRandomName(this.getClass().getSimpleName());
+        this.acsZone2Name = ZoneFactory.getRandomName(this.getClass().getSimpleName());
+        this.acsZone3Name = ZoneFactory.getRandomName(this.getClass().getSimpleName());
+
+        this.zone1Headers = ACSTestUtil.httpHeaders();
+        this.zone1Headers.set(PolicyHelper.PREDIX_ZONE_ID, this.acsZone1Name);
+
+        this.zone3Headers = ACSTestUtil.httpHeaders();
+        this.zone3Headers.set(PolicyHelper.PREDIX_ZONE_ID, this.acsZone3Name);
+
+        this.uaaTestUtil = new UAAACSClientsUtil(this.uaaUrl, this.uaaAdminSecret);
+
+        this.acsAdminRestTemplate = this.uaaTestUtil.createAcsAdminClientAndGetTemplate(this.acsZone1Name);
+        this.acsZonesAdminRestTemplate = this.uaaTestUtil
+                .createAcsAdminClient(Arrays.asList(this.acsZone1Name, this.acsZone2Name, this.acsZone3Name));
+        this.acsReadOnlyRestTemplate = this.uaaTestUtil.createReadOnlyPolicyScopeClient(this.acsZone1Name);
+        this.acsNoPolicyScopeRestTemplate = this.uaaTestUtil.createNoPolicyScopeClient(this.acsZone1Name);
+        this.zone1 = this.zoneFactory.createTestZone(this.acsAdminRestTemplate, this.acsZone1Name,
+                Collections.singletonList(this.uaaUrl + OAUTH_ENDPOINT));
+        this.acsZone1RestTemplate = this.uaaTestUtil.createZoneClientAndGetTemplate(this.acsZone1Name, this.serviceId);
+
+        this.zone2 = this.zoneFactory.createTestZone(this.acsAdminRestTemplate, this.acsZone2Name,
+                Collections.singletonList(this.uaaUrl + OAUTH_ENDPOINT));
+        this.acsZone2RestTemplate = this.uaaTestUtil.createZoneClientAndGetTemplate(this.acsZone2Name, this.serviceId);
+    }
+
+    @Override
+    public void destroy() {
+        this.zoneFactory.deleteZone(this.acsAdminRestTemplate, this.acsZone1Name);
+        this.zoneFactory.deleteZone(this.acsAdminRestTemplate, this.acsZone2Name);
+        this.uaaTestUtil.deleteClient(this.acsAdminRestTemplate.getResource().getClientId());
+        this.uaaTestUtil.deleteClient(this.acsZone1RestTemplate.getResource().getClientId());
+        this.uaaTestUtil.deleteClient(this.acsZone2RestTemplate.getResource().getClientId());
+        this.uaaTestUtil.deleteClient(this.acsReadOnlyRestTemplate.getResource().getClientId());
+        this.uaaTestUtil.deleteClient(this.acsNoPolicyScopeRestTemplate.getResource().getClientId());
+        this.uaaTestUtil.deleteClient(this.acsZonesAdminRestTemplate.getResource().getClientId());
+    }
+
+    @Override
+    public String getAcsUrl() {
+
+        return this.acsUrl;
+    }
+
+    @Override
+    public HttpHeaders getZone1Headers() {
+
+        return this.zone1Headers;
+    }
+
+    @Override
+    public OAuth2RestTemplate getAcsZoneAdminRestTemplate() {
+        return this.acsZone1RestTemplate;
+    }
+
+    @Override
+    public OAuth2RestTemplate getAcsZone2AdminRestTemplate() {
+
+        return this.acsZone2RestTemplate;
+    }
+
+    @Override
+    public OAuth2RestTemplate getAcsReadOnlyRestTemplate() {
+
+        return this.acsReadOnlyRestTemplate;
+    }
+
+    @Override
+    public OAuth2RestTemplate getAcsNoPolicyScopeRestTemplate() {
+
+        return this.acsNoPolicyScopeRestTemplate;
+    }
+
+    @Override
+    public Zone getZone1() {
+        return this.zone1;
+    }
+
+    @Override
+    public Zone getZone2() {
+        return this.zone2;
+    }
+
+    @Override
+    public String getAcsZone1Name() {
+        return this.acsZone1Name;
+    }
+
+    @Override
+    public String getAcsZone2Name() {
+        return this.acsZone2Name;
+    }
+
+    @Override
+    public String getAcsZone3Name() {
+        return this.acsZone3Name;
+    }
+
+    @Override
+    public HttpHeaders getZone3Headers() {
+        return this.zone3Headers;
+    }
+
+    @Override
+    public OAuth2RestTemplate getAcsZonesAdminRestTemplate() {
+        return this.acsZonesAdminRestTemplate;
+    }
+
+    @Override
+    public OAuth2RestTemplate getAcsZoneConnectorAdminRestTemplate(final String zone) {
+        return this.uaaTestUtil.createAdminConnectorScopeClient(zone);
+    }
+
+    @Override
+    public OAuth2RestTemplate getAcsZoneConnectorReadRestTemplate(final String zone) {
+        return this.uaaTestUtil.createReadOnlyConnectorScopeClient(zone);
+    }
+
+    @Override
+    public OAuth2RestTemplate getAcsAdminRestTemplate(final String zone) {
+        return new UAAACSClientsUtil(this.uaaUrl, this.uaaAdminSecret).createAcsAdminClient(Arrays.asList(zone));
+    }
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/test/utils/ACSTestUtil.java b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/ACSTestUtil.java
new file mode 100644
index 0000000..a771078
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/ACSTestUtil.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.test.utils;
+
+import java.net.ConnectException;
+import java.net.URI;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+import org.testng.Assert;
+
+/**
+ * @author acs-engineers@ge.com
+ */
+@Component
+@SuppressWarnings({ "nls", "javadoc" })
+public class ACSTestUtil {
+
+    // used in all integration tests - if changed, it will be reflected on all tests
+    public static final String ACS_VERSION = "/v1";
+
+    public void assertExceptionResponseBody(final HttpClientErrorException e, final String message) {
+        String responseBody = e.getResponseBodyAsString();
+        Assert.assertNotNull(responseBody);
+        Assert.assertTrue(responseBody.contains(message),
+                String.format("Expected=[%s], Actual=[%s]", message, responseBody));
+    }
+
+    public static HttpHeaders httpHeaders() {
+        HttpHeaders httpHeaders = new HttpHeaders();
+        httpHeaders.add(HttpHeaders.ACCEPT, MediaType.ALL_VALUE);
+        return httpHeaders;
+    }
+
+    public static boolean isServerListening(final URI url) {
+        RestTemplate restTemplate = new RestTemplate();
+        try {
+            restTemplate.getForObject(url, String.class);
+        } catch (RestClientException e) {
+            if (e.getCause() instanceof ConnectException) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/test/utils/CreatePolicyStatus.java b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/CreatePolicyStatus.java
new file mode 100644
index 0000000..ac8e798
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/CreatePolicyStatus.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.test.utils;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+public enum CreatePolicyStatus {
+    SUCCESS, JSON_ERROR, INVALID_POLICY_SET, ACS_ERROR
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/test/utils/OptionalTestSetup.java b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/OptionalTestSetup.java
new file mode 100644
index 0000000..fd61da9
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/OptionalTestSetup.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.test.utils;
+
+import java.util.Map;
+
+public interface OptionalTestSetup {
+
+    void setup(String zoneId, Map<String, Object> trustedIssuers);
+
+    void tearDown(String zoneId);
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/test/utils/PolicyHelper.java b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/PolicyHelper.java
new file mode 100644
index 0000000..47adac2
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/PolicyHelper.java
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.test.utils;
+
+import static com.ge.predix.test.utils.ACSTestUtil.ACS_VERSION;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Random;
+import java.util.Set;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+
+@Component
+public class PolicyHelper {
+    public static final String ACS_POLICY_SET_API_PATH = ACS_VERSION + "/policy-set/";
+    public static final String ACS_POLICY_EVAL_API_PATH = ACS_VERSION + "/policy-evaluation";
+
+    public static final String DEFAULT_ACTION = "GET";
+    public static final String NOT_MATCHING_ACTION = "HEAD";
+    public static final String PREDIX_ZONE_ID = "Predix-Zone-Id";
+
+    private static final String[] ACTIONS = { "GET", "POST", "DELETE", "PUT" };
+
+    @Autowired
+    private ZoneFactory zoneFactory;
+
+    public String setTestPolicy(final RestTemplate acs, final HttpHeaders headers, final String endpoint,
+            final String policyFile) throws JsonParseException, JsonMappingException, IOException {
+
+        PolicySet policySet = new ObjectMapper().readValue(new File(policyFile), PolicySet.class);
+        String policyName = policySet.getName();
+        acs.put(endpoint + ACS_POLICY_SET_API_PATH + policyName, new HttpEntity<>(policySet, headers));
+        return policyName;
+    }
+
+    public CreatePolicyStatus createPolicySet(final String policyFile, final RestTemplate restTemplate,
+            final HttpHeaders headers) {
+        PolicySet policySet;
+        try {
+            policySet = new ObjectMapper().readValue(new File(policyFile), PolicySet.class);
+            String policyName = policySet.getName();
+            restTemplate.put(zoneFactory.getAcsBaseURL() + ACS_POLICY_SET_API_PATH + policyName,
+                    new HttpEntity<>(policySet, headers));
+            return CreatePolicyStatus.SUCCESS;
+        } catch (IOException e) {
+            return CreatePolicyStatus.JSON_ERROR;
+        } catch (HttpClientErrorException httpException) {
+            return httpException.getStatusCode() != null
+                    && httpException.getStatusCode().equals(HttpStatus.UNPROCESSABLE_ENTITY)
+                            ? CreatePolicyStatus.INVALID_POLICY_SET : CreatePolicyStatus.ACS_ERROR;
+        } catch (RestClientException e) {
+            return CreatePolicyStatus.ACS_ERROR;
+        }
+    }
+
+    public ResponseEntity<PolicySet> getPolicySet(final String policyName, final RestTemplate restTemplate,
+            final String endpoint) {
+        ResponseEntity<PolicySet> policySetResponse = restTemplate
+                .getForEntity(endpoint + ACS_POLICY_SET_API_PATH + policyName, PolicySet.class);
+        return policySetResponse;
+    }
+
+    public PolicyEvaluationRequestV1 createRandomEvalRequest() {
+        Random r = new Random(System.currentTimeMillis());
+        Set<Attribute> subjectAttributes = Collections.emptySet();
+        return this.createEvalRequest(ACTIONS[r.nextInt(4)], String.valueOf(r.nextLong()),
+                "/alarms/sites/" + String.valueOf(r.nextLong()), subjectAttributes);
+    }
+
+    public PolicyEvaluationRequestV1 createMultiplePolicySetsEvalRequest(final String action,
+            final String subjectIdentifier, final String resourceIdentifier, final Set<Attribute> subjectAttributes,
+            final LinkedHashSet<String> policySetIds) {
+        PolicyEvaluationRequestV1 policyEvaluationRequest = new PolicyEvaluationRequestV1();
+        policyEvaluationRequest.setAction(action);
+        policyEvaluationRequest.setSubjectIdentifier(subjectIdentifier);
+        policyEvaluationRequest.setResourceIdentifier(resourceIdentifier);
+        policyEvaluationRequest.setSubjectAttributes(subjectAttributes);
+        policyEvaluationRequest.setPolicySetsEvaluationOrder(policySetIds);
+        return policyEvaluationRequest;
+    }
+
+    public PolicyEvaluationRequestV1 createMultiplePolicySetsEvalRequest(final String subjectIdentifier,
+            final String site, final LinkedHashSet<String> policySetIds) {
+        return createMultiplePolicySetsEvalRequest("GET", subjectIdentifier, "/secured-by-value/sites/" + site, null,
+                policySetIds);
+    }
+
+    public PolicyEvaluationRequestV1 createMultiplePolicySetsEvalRequest(final BaseSubject subject, final String site,
+            final LinkedHashSet<String> policySetIds) {
+        return createMultiplePolicySetsEvalRequest("GET", subject.getSubjectIdentifier(),
+                "/secured-by-value/sites/" + site, null, policySetIds);
+    }
+
+    public PolicyEvaluationRequestV1 createEvalRequest(final String action, final String subjectIdentifier,
+            final String resourceIdentifier, final Set<Attribute> subjectAttributes) {
+        PolicyEvaluationRequestV1 policyEvaluationRequest = new PolicyEvaluationRequestV1();
+        policyEvaluationRequest.setAction(action);
+        policyEvaluationRequest.setSubjectIdentifier(subjectIdentifier);
+        policyEvaluationRequest.setResourceIdentifier(resourceIdentifier);
+        policyEvaluationRequest.setSubjectAttributes(subjectAttributes);
+        return policyEvaluationRequest;
+    }
+
+    public PolicyEvaluationRequestV1 createEvalRequest(final String subjectIdentifier, final String site) {
+        return createEvalRequest("GET", subjectIdentifier, "/secured-by-value/sites/" + site, null);
+    }
+
+    public PolicyEvaluationRequestV1 createEvalRequest(final BaseSubject subject, final String site) {
+        return createEvalRequest("GET", subject.getSubjectIdentifier(), "/secured-by-value/sites/" + site, null);
+    }
+
+    public ResponseEntity<PolicyEvaluationResult> sendEvaluationRequest(final RestTemplate restTemplate,
+            final HttpHeaders headers, final PolicyEvaluationRequestV1 randomEvalRequest) {
+        ResponseEntity<PolicyEvaluationResult> evaluationResponse = restTemplate.postForEntity(
+                this.zoneFactory.getAcsBaseURL() + ACS_POLICY_EVAL_API_PATH,
+                new HttpEntity<>(randomEvalRequest, headers), PolicyEvaluationResult.class);
+        return evaluationResponse;
+    }
+
+    public void deletePolicySet(final RestTemplate restTemplate, final String acsUrl, final String testPolicyName) {
+        if (testPolicyName != null) {
+            restTemplate.delete(acsUrl + ACS_POLICY_SET_API_PATH + testPolicyName);
+        }
+    }
+
+    public void deletePolicySet(final RestTemplate restTemplate, final String acsUrl, final String testPolicyName,
+            final HttpHeaders headers) {
+        if (testPolicyName != null) {
+            restTemplate.exchange(acsUrl + ACS_POLICY_SET_API_PATH + testPolicyName, HttpMethod.DELETE,
+                    new HttpEntity<>(headers), String.class);
+        }
+    }
+
+    public PolicySet[] listPolicySets(final RestTemplate restTemplate, final String acsUrl, final HttpHeaders headers) {
+        URI uri = URI.create(acsUrl + ACS_POLICY_SET_API_PATH);
+        ResponseEntity<PolicySet[]> response = restTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<>(headers),
+                PolicySet[].class);
+        return response.getBody();
+    }
+
+    public void deletePolicySets(final RestTemplate restTemplate, final String acsUrl, final HttpHeaders headers)
+            throws Exception {
+        PolicySet[] policySets = listPolicySets(restTemplate, acsUrl, headers);
+        for (PolicySet policySet : policySets) {
+            deletePolicySet(restTemplate, acsUrl, policySet.getName(), headers);
+        }
+    }
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/test/utils/PrivilegeHelper.java b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/PrivilegeHelper.java
new file mode 100644
index 0000000..936a760
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/PrivilegeHelper.java
@@ -0,0 +1,294 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.test.utils;
+
+import static com.ge.predix.test.utils.ACSTestUtil.ACS_VERSION;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+
+@Component
+public class PrivilegeHelper {
+
+    private static final String ALTERNATE_ATTRIBUTE_VALUE = "sanfrancisco";
+    private static final String DEFAULT_ATTRIBUTE_VALUE = "sanramon";
+    private static final String DEFAULT_ATTRIBUTE_NAME = "site";
+
+    private static final String DEFAULT_ORG_ATTRIBUTE_NAME = "org";
+    private static final String DEFAULT_ORG_ATTRIBUTE_VALUE = "alliance";
+    private static final String ALTERNATE_ORG_ATTRIBUTE_VALUE = "syndicate";
+
+    public static final String ACS_SUBJECT_API_PATH = ACS_VERSION + "/subject/";
+    public static final String ACS_RESOURCE_API_PATH = ACS_VERSION + "/resource/";
+
+    public static final String DEFAULT_SUBJECT_ID = "any";
+    public static final String DEFAULT_SUBJECT_IDENTIFIER = "any";
+    public static final String DEFAULT_RESOURCE_IDENTIFIER = "any";
+    public static final String DEFAULT_ATTRIBUTE_ISSUER = "https://acs.attributes.int";
+
+    @Autowired
+    private ZoneFactory zoneFactory;
+
+    public BaseSubject putSubject(final OAuth2RestTemplate acsTemplate, final BaseSubject subject,
+            final String endpoint, final HttpHeaders headers, final Attribute... attributes)
+            throws UnsupportedEncodingException {
+
+        subject.setAttributes(new HashSet<>(Arrays.asList(attributes)));
+        URI subjectUri = URI
+                .create(endpoint + ACS_SUBJECT_API_PATH + URLEncoder.encode(subject.getSubjectIdentifier(), "UTF-8"));
+        acsTemplate.put(subjectUri, new HttpEntity<>(subject, headers));
+        return subject;
+    }
+
+    public void putSubject(final OAuth2RestTemplate restTemplate, final String subjectIdentifier,
+            final HttpHeaders headers, final Attribute... attributes) throws UnsupportedEncodingException {
+
+        BaseSubject subject = new BaseSubject(subjectIdentifier);
+        // no header needed, because it uses zone specific url
+        putSubject(restTemplate, subject, this.zoneFactory.getAcsBaseURL(), headers, attributes);
+    }
+
+    public ResponseEntity<Object> postMultipleSubjects(final OAuth2RestTemplate acsTemplate, final String endpoint,
+            final HttpHeaders headers, final BaseSubject... subjects) {
+        Attribute site = getDefaultAttribute();
+        Set<Attribute> attributes = new HashSet<>();
+        attributes.add(site);
+
+        List<BaseSubject> subjectsArray = new ArrayList<>();
+        for (BaseSubject s : subjects) {
+            s.setAttributes(attributes);
+            subjectsArray.add(s);
+        }
+        URI subjectUri = URI.create(endpoint + ACS_SUBJECT_API_PATH);
+
+        ResponseEntity<Object> responseEntity = acsTemplate.postForEntity(subjectUri,
+                new HttpEntity<>(subjectsArray, headers), Object.class);
+
+        return responseEntity;
+    }
+
+    public ResponseEntity<Object> postSubjectsWithDefaultAttributes(final OAuth2RestTemplate acsTemplate,
+            final String endpoint, final HttpHeaders headers, final BaseSubject... subjects) {
+        Attribute site = getDefaultAttribute();
+        Set<Attribute> attributes = new HashSet<>();
+        attributes.add(site);
+
+        for (BaseSubject s : subjects) {
+            s.setAttributes(attributes);
+        }
+        return postSubjects(acsTemplate, endpoint, headers, subjects);
+    }
+
+    public ResponseEntity<Object> postSubjects(final OAuth2RestTemplate acsTemplate, final String endpoint,
+            final HttpHeaders headers, final BaseSubject... subjects) {
+        List<BaseSubject> subjectsArray = new ArrayList<>();
+        for (BaseSubject s : subjects) {
+            subjectsArray.add(s);
+        }
+        URI subjectUri = URI.create(endpoint + ACS_SUBJECT_API_PATH);
+        ResponseEntity<Object> responseEntity = acsTemplate.postForEntity(subjectUri,
+                new HttpEntity<>(subjectsArray, headers), Object.class);
+        return responseEntity;
+    }
+
+    public BaseResource putResource(final OAuth2RestTemplate acsTemplate, final BaseResource resource,
+            final String endpoint, final HttpHeaders headers, final Attribute... attributes) throws Exception {
+
+        resource.setAttributes(new HashSet<>(Arrays.asList(attributes)));
+
+        String value = URLEncoder.encode(resource.getResourceIdentifier(), "UTF-8");
+
+        URI uri = new URI(endpoint + ACS_RESOURCE_API_PATH + value);
+        acsTemplate.put(uri, new HttpEntity<>(resource, headers));
+        return resource;
+    }
+
+    public ResponseEntity<Object> postResources(final OAuth2RestTemplate acsTemplate, final String endpoint,
+            final BaseResource... resources) {
+
+        Attribute site = getDefaultAttribute();
+        Set<Attribute> attributes = new HashSet<>();
+        attributes.add(site);
+
+        List<BaseResource> resourcesArray = new ArrayList<>();
+        for (BaseResource r : resources) {
+            r.setAttributes(attributes);
+            resourcesArray.add(r);
+        }
+        URI resourceUri = URI.create(endpoint + ACS_RESOURCE_API_PATH);
+
+        ResponseEntity<Object> responseEntity = acsTemplate.postForEntity(resourceUri, resourcesArray, Object.class);
+
+        return responseEntity;
+    }
+
+    public ResponseEntity<Object> postResourcesWithDefaultSiteAttribute(final OAuth2RestTemplate acsTemplate,
+            final String endpoint, final HttpHeaders headers, final BaseResource... resources) {
+
+        Attribute site = getDefaultAttribute();
+        Set<Attribute> attributes = new HashSet<>();
+        attributes.add(site);
+
+        for (BaseResource r : resources) {
+            r.setAttributes(attributes);
+        }
+        return postResources(acsTemplate, endpoint, headers, resources);
+    }
+
+    public ResponseEntity<Object> postResources(final OAuth2RestTemplate acsTemplate, final String endpoint,
+            final HttpHeaders headers, final BaseResource... resources) {
+
+        List<BaseResource> resourcesArray = new ArrayList<>();
+        for (BaseResource r : resources) {
+            resourcesArray.add(r);
+        }
+        URI resourceUri = URI.create(endpoint + ACS_RESOURCE_API_PATH);
+
+        ResponseEntity<Object> responseEntity = acsTemplate.postForEntity(resourceUri,
+                new HttpEntity<>(resourcesArray, headers), Object.class);
+
+        return responseEntity;
+    }
+
+    public void deleteSubject(final RestTemplate restTemplate, final String acsUrl, final String subjectId)
+            throws Exception {
+        deleteSubject(restTemplate, acsUrl, subjectId, null);
+    }
+
+    public void deleteSubject(final RestTemplate restTemplate, final String acsUrl, final String subjectId,
+            final HttpHeaders headers) throws Exception {
+        if (subjectId != null) {
+            URI subjectUri = URI.create(acsUrl + ACS_SUBJECT_API_PATH + URLEncoder.encode(subjectId, "UTF-8"));
+            restTemplate.exchange(subjectUri, HttpMethod.DELETE, new HttpEntity<>(headers), String.class);
+        }
+    }
+
+    public void deleteResource(final RestTemplate restTemplate, final String acsUrl, final String resourceId)
+            throws Exception {
+        deleteResource(restTemplate, acsUrl, resourceId, null);
+    }
+
+    public void deleteResource(final RestTemplate restTemplate, final String acsUrl, final String resourceId,
+            final HttpHeaders headers) throws Exception {
+        if (resourceId != null) {
+            URI resourceUri = URI.create(acsUrl + ACS_RESOURCE_API_PATH + URLEncoder.encode(resourceId, "UTF-8"));
+            restTemplate.exchange(resourceUri, HttpMethod.DELETE, new HttpEntity<>(headers), String.class);
+        }
+    }
+
+    public BaseSubject createSubject(final String subjectIdentifier) {
+        BaseSubject subject = new BaseSubject();
+        subject.setSubjectIdentifier(subjectIdentifier);
+        Set<Attribute> attributes = new HashSet<>();
+        attributes.add(getDefaultAttribute());
+        subject.setAttributes(attributes);
+        return subject;
+    }
+
+    public BaseResource createResource(final String resourceIdentifier) {
+        BaseResource resource = new BaseResource();
+        resource.setResourceIdentifier(resourceIdentifier);
+        Set<Attribute> attributes = new HashSet<>();
+        attributes.add(getDefaultAttribute());
+        resource.setAttributes(attributes);
+        return resource;
+    }
+
+    public Attribute getDefaultAttribute() {
+        Attribute site = new Attribute();
+        site.setIssuer(DEFAULT_ATTRIBUTE_ISSUER);
+        site.setName(DEFAULT_ATTRIBUTE_NAME);
+        site.setValue(DEFAULT_ATTRIBUTE_VALUE);
+        return site;
+    }
+
+    public Attribute getAlternateAttribute() {
+        Attribute site = new Attribute();
+        site.setIssuer(DEFAULT_ATTRIBUTE_ISSUER);
+        site.setName(DEFAULT_ATTRIBUTE_NAME);
+        site.setValue(ALTERNATE_ATTRIBUTE_VALUE);
+        return site;
+    }
+
+    public Attribute getDefaultOrgAttribute() {
+        Attribute site = new Attribute();
+        site.setIssuer(DEFAULT_ATTRIBUTE_ISSUER);
+        site.setName(DEFAULT_ORG_ATTRIBUTE_NAME);
+        site.setValue(DEFAULT_ORG_ATTRIBUTE_VALUE);
+        return site;
+    }
+
+    public Attribute getAlternateOrgAttribute() {
+        Attribute site = new Attribute();
+        site.setIssuer(DEFAULT_ATTRIBUTE_ISSUER);
+        site.setName(DEFAULT_ORG_ATTRIBUTE_NAME);
+        site.setValue(ALTERNATE_ORG_ATTRIBUTE_VALUE);
+        return site;
+    }
+
+    public void deleteSubjects(final RestTemplate restTemplate, final String acsUrl, final HttpHeaders headers)
+            throws Exception {
+        BaseSubject[] subjects = listSubjects(restTemplate, acsUrl, headers);
+        for (BaseSubject subject : subjects) {
+            deleteSubject(restTemplate, acsUrl, subject.getSubjectIdentifier(), headers);
+        }
+    }
+
+    public BaseSubject[] listSubjects(final RestTemplate restTemplate, final String acsUrl, final HttpHeaders headers) {
+        URI uri = URI.create(acsUrl + ACS_SUBJECT_API_PATH);
+        ResponseEntity<BaseSubject[]> response = restTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<>(headers),
+                BaseSubject[].class);
+        return response.getBody();
+    }
+
+    public void deleteResources(final RestTemplate restTemplate, final String acsUrl, final HttpHeaders headers)
+            throws Exception {
+        BaseResource[] resources = listResources(restTemplate, acsUrl, headers);
+        for (BaseResource resource : resources) {
+            deleteResource(restTemplate, acsUrl, resource.getResourceIdentifier(), headers);
+        }
+    }
+
+    public BaseResource[] listResources(final RestTemplate restTemplate, final String acsEndpoint,
+            final HttpHeaders headers) {
+        URI uri = URI.create(acsEndpoint + ACS_RESOURCE_API_PATH);
+        ResponseEntity<BaseResource[]> response = restTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<>(headers),
+                BaseResource[].class);
+        return response.getBody();
+    }
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/test/utils/UAAACSClientsUtil.java b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/UAAACSClientsUtil.java
new file mode 100644
index 0000000..3df8884
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/UAAACSClientsUtil.java
@@ -0,0 +1,241 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.test.utils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
+import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
+import org.springframework.security.oauth2.provider.client.BaseClientDetails;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import com.ge.predix.acs.utils.JsonUtils;
+
+public class UAAACSClientsUtil {
+
+    private static final JsonUtils JSON_UTILS = new JsonUtils();
+
+    private final OAuth2RestTemplate uaaAdminTemplate;
+    private final String uaaUrl;
+
+    public UAAACSClientsUtil(final String uaaUrl, final String adminSecret) {
+        this.uaaUrl = uaaUrl;
+        this.uaaAdminTemplate = getOAuth2RestTemplateForClient(this.uaaUrl + "/oauth/token", "admin", adminSecret);
+    }
+
+    public OAuth2RestTemplate createAcsAdminClient(final List<String> acsZones) {
+
+        ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
+        authorities.add(new SimpleGrantedAuthority("acs.zones.admin"));
+        authorities.add(new SimpleGrantedAuthority("acs.attributes.read"));
+        authorities.add(new SimpleGrantedAuthority("acs.attributes.write"));
+        authorities.add(new SimpleGrantedAuthority("acs.policies.read"));
+        authorities.add(new SimpleGrantedAuthority("acs.policies.write"));
+        authorities.add(new SimpleGrantedAuthority("acs.connectors.read"));
+        authorities.add(new SimpleGrantedAuthority("acs.connectors.write"));
+        for (int i = 0; i < acsZones.size(); i++) {
+            authorities.add(new SimpleGrantedAuthority("predix-acs.zones." + acsZones.get(i) + ".admin"));
+            authorities.add(new SimpleGrantedAuthority("predix-acs.zones." + acsZones.get(i) + ".user"));
+        }
+        OAuth2RestTemplate restTemplate = createScopeClient(acsZones.get(0), "super-admin");
+        this.createClientWithAuthorities(restTemplate.getResource().getClientId(),
+                restTemplate.getResource().getClientSecret(), authorities);
+        return restTemplate;
+    }
+
+    public OAuth2RestTemplate createAcsAdminClientAndGetTemplate(final String zoneName) {
+        OAuth2RestTemplate restTemplate = createScopeClient(zoneName, "admin");
+        this.createAcsAdminClient(restTemplate.getResource().getClientId(),
+                restTemplate.getResource().getClientSecret());
+        return restTemplate;
+    }
+
+    public OAuth2RestTemplate createZoneClientAndGetTemplate(final String zoneName, final String serviceId) {
+        OAuth2RestTemplate restTemplate = createScopeClient(zoneName, "zoneAdmin");
+        this.createAcsZoneClient(zoneName, restTemplate.getResource().getClientId(),
+                restTemplate.getResource().getClientSecret(), serviceId);
+        return restTemplate;
+    }
+
+    public OAuth2RestTemplate createReadOnlyPolicyScopeClient(final String zoneName) {
+        OAuth2RestTemplate restTemplate = createScopeClient(zoneName, "admin", "readonly");
+        this.createReadOnlyPolicyScopeClient(restTemplate.getResource().getClientId(),
+                restTemplate.getResource().getClientSecret(), zoneName);
+        return restTemplate;
+    }
+
+    public OAuth2RestTemplate createNoPolicyScopeClient(final String zoneName) {
+        OAuth2RestTemplate restTemplate = createScopeClient(zoneName, "admin", "nopolicy");
+        this.createNoPolicyScopeClient(restTemplate.getResource().getClientId(),
+                restTemplate.getResource().getClientSecret(), zoneName);
+        return restTemplate;
+
+    }
+
+    public OAuth2RestTemplate createReadOnlyConnectorScopeClient(final String zoneName) {
+        OAuth2RestTemplate restTemplate = createScopeClient(zoneName, "admin-connector", "readonly");
+        this.createReadOnlyConnectorScopeClient(restTemplate.getResource().getClientId(),
+                restTemplate.getResource().getClientSecret(), zoneName);
+        return restTemplate;
+    }
+
+    public OAuth2RestTemplate createAdminConnectorScopeClient(final String zoneName) {
+        OAuth2RestTemplate restTemplate = createScopeClient(zoneName, "admin-connector");
+        this.createAdminConnectorScopeClient(restTemplate.getResource().getClientId(),
+                restTemplate.getResource().getClientSecret(), zoneName);
+        return restTemplate;
+    }
+
+    public void createAcsAdminClient(final String clientId, final String clientSecret) {
+        createClientWithAuthorities(clientId, clientSecret,
+                Collections.singletonList(new SimpleGrantedAuthority("acs.zones.admin")));
+    }
+
+    public void createNoPolicyScopeClient(final String clientId, final String clientSecret, final String zone) {
+        this.createClientWithAuthorities(clientId, clientSecret,
+                Collections.singletonList(new SimpleGrantedAuthority("predix-acs.zones." + zone + ".user")));
+    }
+
+    public void createReadOnlyPolicyScopeClient(final String clientId, final String clientSecret, final String zone) {
+        this.createClientWithAuthorities(clientId, clientSecret,
+                Arrays.asList(new SimpleGrantedAuthority("predix-acs.zones." + zone + ".user"),
+                        new SimpleGrantedAuthority("acs.policies.read")));
+    }
+
+    public void createReadOnlyConnectorScopeClient(final String clientId, final String clientSecret,
+            final String zone) {
+        this.createClientWithAuthorities(clientId, clientSecret,
+                Arrays.asList(new SimpleGrantedAuthority("predix-acs.zones." + zone + ".user"),
+                        new SimpleGrantedAuthority("acs.connectors.read")));
+    }
+
+    public void createAdminConnectorScopeClient(final String clientId, final String clientSecret, final String zone) {
+        this.createClientWithAuthorities(clientId, clientSecret,
+                Arrays.asList(new SimpleGrantedAuthority("predix-acs.zones." + zone + ".user"),
+                        new SimpleGrantedAuthority("acs.connectors.read"),
+                        new SimpleGrantedAuthority("acs.connectors.write")));
+    }
+
+    public void createAcsZoneClient(final String acsZone, final String clientId, final String clientSecret,
+            final String serviceId) {
+        ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>() {
+            {
+                add(new SimpleGrantedAuthority("acs.attributes.read"));
+                add(new SimpleGrantedAuthority("acs.attributes.write"));
+                add(new SimpleGrantedAuthority("acs.policies.read"));
+                add(new SimpleGrantedAuthority("acs.policies.write"));
+                add(new SimpleGrantedAuthority(serviceId + ".zones." + acsZone + ".user"));
+                add(new SimpleGrantedAuthority(serviceId + ".zones." + acsZone + ".admin"));
+            }
+        };
+        createClientWithAuthorities(clientId, clientSecret, authorities);
+    }
+
+    private void createClientWithAuthorities(final String clientId, final String clientSecret,
+            final Collection<? extends GrantedAuthority> authorities) {
+        BaseClientDetails client = new BaseClientDetails();
+        client.setAuthorities(authorities);
+        client.setAuthorizedGrantTypes(Collections.singletonList("client_credentials"));
+        client.setClientId(clientId);
+        client.setClientSecret(clientSecret);
+        client.setResourceIds(Collections.singletonList("uaa.none"));
+        createOrUpdateClient(client);
+    }
+
+    private BaseClientDetails createOrUpdateClient(final BaseClientDetails client) {
+
+        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
+        headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
+        headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
+        HttpEntity<String> postEntity = new HttpEntity<>(JSON_UTILS.serialize(client), headers);
+
+        ResponseEntity<String> clientCreate = null;
+        try {
+            clientCreate = this.uaaAdminTemplate.exchange(this.uaaUrl + "/oauth/clients", HttpMethod.POST, postEntity,
+                    String.class);
+            if (clientCreate.getStatusCode() == HttpStatus.CREATED) {
+                return JSON_UTILS.deserialize(clientCreate.getBody(), BaseClientDetails.class);
+            } else {
+                throw new RuntimeException("Unexpected return code for client create: " + clientCreate.getStatusCode());
+            }
+        } catch (InvalidClientException ex) {
+            if (ex.getMessage().equals("Client already exists: " + client.getClientId())) {
+                HttpEntity<String> putEntity = new HttpEntity<String>(JSON_UTILS.serialize(client), headers);
+                ResponseEntity<String> clientUpdate = this.uaaAdminTemplate.exchange(
+                        this.uaaUrl + "/oauth/clients/" + client.getClientId(), HttpMethod.PUT, putEntity,
+                        String.class);
+                if (clientUpdate.getStatusCode() == HttpStatus.OK) {
+                    return JSON_UTILS.deserialize(clientUpdate.getBody(), BaseClientDetails.class);
+                } else {
+                    throw new RuntimeException(
+                            "Unexpected return code for client update: " + clientUpdate.getStatusCode());
+                }
+            }
+        }
+        throw new RuntimeException("Unexpected return code for client creation: " + clientCreate.getStatusCode());
+    }
+
+    public void deleteClient(final String clientId) {
+        this.uaaAdminTemplate.delete(this.uaaUrl + "/oauth/clients/" + clientId);
+    }
+
+    private OAuth2RestTemplate createScopeClient(final String zoneName, final String clientRole) {
+        return this.createScopeClient(zoneName, clientRole, null);
+    }
+
+    private OAuth2RestTemplate createScopeClient(final String zoneName, final String clientRole,
+            final String clientType) {
+        String clientId = clientRole + '-' + zoneName;
+        if (clientType != null) {
+            clientId += '-' + clientType;
+        }
+        String clientSecret = clientId + "-secret";
+        return this.getOAuth2RestTemplateForClient(this.uaaUrl + "/oauth/token", clientId, clientSecret);
+    }
+
+    private OAuth2RestTemplate getOAuth2RestTemplateForClient(final String tokenUrl, final String clientId,
+            final String clientSecret) {
+        ClientCredentialsResourceDetails resource = new ClientCredentialsResourceDetails();
+        resource.setAccessTokenUri(tokenUrl);
+        resource.setClientId(clientId);
+        resource.setClientSecret(clientSecret);
+        OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resource);
+        CloseableHttpClient httpClient = HttpClientBuilder.create().useSystemProperties().build();
+        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
+        restTemplate.setRequestFactory(requestFactory);
+
+        return restTemplate;
+    }
+}
diff --git a/acs-integration-tests/src/test/java/com/ge/predix/test/utils/ZoneFactory.java b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/ZoneFactory.java
new file mode 100644
index 0000000..e1a01de
--- /dev/null
+++ b/acs-integration-tests/src/test/java/com/ge/predix/test/utils/ZoneFactory.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.test.utils;
+
+import static com.ge.predix.test.utils.ACSTestUtil.ACS_VERSION;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.UUID;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Profile;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+
+import com.ge.predix.acs.rest.Zone;
+
+@Component
+@Profile({ "public", "public-titan" })
+public class ZoneFactory {
+
+    @Value("${ACS_URL}")
+    private String acsBaseUrl;
+
+    @Value("${CF_BASE_DOMAIN:localhost}")
+    private String cfBaseDomain;
+
+    public static final String ACS_ZONE_API_PATH = ACS_VERSION + "/zone/";
+
+    /**
+     * Creates a random zone name. Having a random zone name avoids test collisions when executing in parallel
+     * 
+     * @param clazz
+     *            a classname to pass
+     * @return String randomize name of the zone
+     */
+    static String getRandomName(final String clazz) {
+        return clazz + UUID.randomUUID().toString();
+    }
+
+    /**
+     * Creates desired Zone. Makes a call to the zone API and creates a zone in order to execute your set of test
+     * against it.
+     * 
+     * @param restTemplate
+     * @param zoneId
+     * @param trustedIssuerIds
+     * @return Zone
+     * @throws IOException
+     */
+    public Zone createTestZone(final RestTemplate restTemplate, final String zoneId,
+            final List<String> trustedIssuerIds) throws IOException {
+        Zone zone = new Zone(zoneId, zoneId, "Zone for integration testing.");
+        restTemplate.put(this.acsBaseUrl + ACS_ZONE_API_PATH + zoneId, zone);
+        return zone;
+    }
+
+    /**
+     * Deletes desired Zone. Makes a client call that deletes the desired zone. This method should be use after the
+     * set of tests for that zone are finished.
+     * 
+     * @param restTemplate
+     * @param zoneName
+     * @return HttpStatus
+     */
+    public HttpStatus deleteZone(final RestTemplate restTemplate, final String zoneName) {
+        try {
+            restTemplate.delete(this.acsBaseUrl + ACS_ZONE_API_PATH + zoneName);
+            return HttpStatus.NO_CONTENT;
+        } catch (HttpClientErrorException httpException) {
+            return httpException.getStatusCode();
+        } catch (RestClientException e) {
+            return HttpStatus.INTERNAL_SERVER_ERROR;
+        }
+    }
+
+    public String getAcsBaseURL() {
+        return this.acsBaseUrl;
+    }
+
+    public String getZoneSpecificUrl(final String zoneId) {
+        URI uri = null;
+        String zoneurl = null;
+        try {
+            uri = new URI(this.acsBaseUrl);
+        } catch (URISyntaxException e) {
+            e.printStackTrace();
+        }
+        zoneurl = uri.getScheme() + "://" + zoneId + '.' + getServiceDomain();
+        if (uri.getPort() != -1) {
+            zoneurl += ':' + uri.getPort();
+        }
+        return zoneurl;
+    }
+
+    public String getServiceDomain() {
+        return this.cfBaseDomain;
+    }
+
+}
diff --git a/acs-integration-tests/src/test/resources/acceptance-test-spring-context.xml b/acs-integration-tests/src/test/resources/acceptance-test-spring-context.xml
new file mode 100644
index 0000000..2811e0c
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/acceptance-test-spring-context.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
+    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
+
+    <import resource="classpath:shared-test-spring-context.xml" />
+
+</beans>
diff --git a/acs-integration-tests/src/test/resources/application.properties b/acs-integration-tests/src/test/resources/application.properties
new file mode 100644
index 0000000..7369216
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/application.properties
@@ -0,0 +1,30 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+server.port = ${ACS_LOCAL_PORT}
+
+###### Properties used by all tests in this project
+
+acs.zone.header.name=Predix-Zone-Id
+
+zacClientId=${ZAC_CLIENT_ID:not-used}
+zacClientSecret=${ZAC_CLIENT_SECRET:not-used}
+
+zone1UaaUrl=${ZONE1_UAA_URL:${ACS_UAA_URL}}
+
+spring.zipkin.enabled=false
diff --git a/acs-integration-tests/src/test/resources/com/ge/predix/acceptance/test/policy/admin/policy-creation.feature b/acs-integration-tests/src/test/resources/com/ge/predix/acceptance/test/policy/admin/policy-creation.feature
new file mode 100644
index 0000000..0d5ac2c
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/com/ge/predix/acceptance/test/policy/admin/policy-creation.feature
@@ -0,0 +1,20 @@
+Feature: PolicySet Creation
+Scenario: Create a policy with no action defined
+    Given A policy with no action defined
+    Then the policy creation returns SUCCESS
+
+Scenario: Create a policy with single valid action defined
+    Given A policy with single valid action defined
+    Then the policy creation returns SUCCESS
+
+Scenario: Create a policy with multiple valid actions defined
+    Given A policy with multiple valid actions defined
+    Then the policy creation returns SUCCESS
+
+Scenario: Create a policy with single invalid action defined
+    Given A policy with single invalid action defined
+    Then the policy creation returns INVALID_POLICY_SET
+
+Scenario: Create a policy with multiple actions containing one invalid action defined
+    Given A policy with multiple actions containing one invalid action defined
+    Then the policy creation returns INVALID_POLICY_SET
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/com/ge/predix/acceptance/test/policy/evaluation/policy-evaluation-actions.feature b/acs-integration-tests/src/test/resources/com/ge/predix/acceptance/test/policy/evaluation/policy-evaluation-actions.feature
new file mode 100644
index 0000000..a690283
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/com/ge/predix/acceptance/test/policy/evaluation/policy-evaluation-actions.feature
@@ -0,0 +1,37 @@
+Feature: PolicyEvaluationActions
+
+Scenario: Policy Evaluation request and no action defined in ACS policy
+    Given an existing policy with no defined action
+    When A policy evaluation is requested with any HTTP action
+    Then policy evaluation returns PERMIT
+
+Scenario: Policy Evaluation request with action matching one of the actions defined in ACS policy
+	Given an existing policy set stored in ACS with multiple actions
+    When A policy evaluation is requested with an HTTP action matching one of the policies in the action list
+    Then policy evaluation returns PERMIT
+    
+Scenario: Policy Evaluation request with action matching the action defined in ACS policy
+	Given an existing policy set stored in ACS with a single action
+    When A policy evaluation is requested with an HTTP action matching the policy action
+    Then policy evaluation returns PERMIT
+
+Scenario: Policy Evaluation request with action NOT matching any of the actions defined in ACS policy
+    Given an existing policy set stored in ACS with multiple actions
+    When A policy evaluation is requested with an HTTP action not matching one of the policies in the action list
+    Then policy evaluation returns NOT_APPLICABLE
+   
+Scenario: Policy Evaluation request with action NOT matching the action defined in ACS policy
+	Given an existing policy set stored in ACS with a single action
+    When A policy evaluation is requested with an HTTP action not matching the policy action
+    Then policy evaluation returns NOT_APPLICABLE
+
+Scenario: Policy Evaluation request and empty action defined in ACS policy
+    Given an existing policy with empty defined action
+    When A policy evaluation is requested with any HTTP action
+    Then policy evaluation returns PERMIT
+
+Scenario: Policy Evaluation request and null action defined in ACS policy
+    Given an existing policy with null defined action
+    When A policy evaluation is requested with any HTTP action
+    Then policy evaluation returns PERMIT
+    
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/com/ge/predix/acceptance/test/policy/evaluation/policy-evaluation.feature b/acs-integration-tests/src/test/resources/com/ge/predix/acceptance/test/policy/evaluation/policy-evaluation.feature
new file mode 100644
index 0000000..0d0fe4d
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/com/ge/predix/acceptance/test/policy/evaluation/policy-evaluation.feature
@@ -0,0 +1,55 @@
+Feature: PolicyEvaluation
+  Policy evaluation feature allows the users of ACS to get access decisions based on policy evaluation. This feature expects
+  policy to be defined. Optionally user can setup attributes which are used during policy evaluation. As a part of this call, 
+  user can supply subject, resource and action which are mandatory fields. They can also supply subject attributes which are 
+  optional - when supplied they should be merged with subject attributes that are provisioned in ACS and should get used during
+  policy evaluation.
+
+  Scenario: policy evaluation request which returns PERMIT
+    Given A policy set that allows access to all
+    When Any evaluation request
+    Then policy evaluation returns PERMIT
+
+  Scenario: policy evaluation request which returns DENY
+    Given A policy set that allows access to none
+    When Any evaluation request
+    Then policy evaluation returns DENY
+
+  Scenario: policy evaluation request which returns PERMIT based on role using condition
+    Given A policy set that allows access only to subject with role administrator using condition
+    When Evaluation request which has the subject attribute role with the value administrator
+    Then policy evaluation returns PERMIT
+
+  Scenario: policy evaluation request which returns DENY based on role using condition
+    Given A policy set that allows access only to subject with role administrator using condition
+    When Evaluation request which has subject attribute which are null
+    Then policy evaluation returns DENY
+
+  Scenario: policy evaluation request which returns PERMIT based on role using matcher
+    Given A policy set that allows access only to subject with role administrator using matcher
+    When Evaluation request which has the subject attribute role with the value administrator
+    Then policy evaluation returns PERMIT
+    And policy evaluation response includes subject attribute role with the value administrator
+    
+  Scenario: policy evaluation request which returns PERMIT based on ACS subject attributes 
+    Given A policy set that allows access only to subject with role administrator using matcher
+    And ACS has subject attribute role with value administrator for the subject
+    When Evaluation request which has no subject attribute
+    Then policy evaluation returns PERMIT
+    And policy evaluation response includes subject attribute role with the value administrator
+    
+  Scenario: policy evaluation request which returns PERMIT based union of ACS subject attributes and evaluation request attributes
+    Given A policy set that allows access only to subject with role administrator using matcher
+    And ACS has subject attribute role with value analyst for the subject
+    When Evaluation request which has the subject attribute role with the value administrator
+    Then policy evaluation returns PERMIT
+    And policy evaluation response includes subject attribute role with the value administrator
+    And policy evaluation response includes subject attribute role with the value analyst
+    
+  Scenario: policy evaluation request which returns PERMIT based on ACS subject attributes and evaluation request attributes
+    Given A policy set that allows access only to subject with role administrator and site sanramon
+    And ACS has subject attribute site with value sanramon for the subject
+    When Evaluation request for resource /site/sanramon which has the subject attribute role with the value administrator
+    Then policy evaluation returns PERMIT
+    And policy evaluation response includes subject attribute role with the value administrator
+    And policy evaluation response includes subject attribute site with the value sanramon 
diff --git a/acs-integration-tests/src/test/resources/com/ge/predix/acceptance/test/zone/admin/zone-enforcement.feature b/acs-integration-tests/src/test/resources/com/ge/predix/acceptance/test/zone/admin/zone-enforcement.feature
new file mode 100644
index 0000000..b7bdae7
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/com/ge/predix/acceptance/test/zone/admin/zone-enforcement.feature
@@ -0,0 +1,33 @@
+Feature: Zone enforcement
+
+    Scenario Outline: A client can only access resources in the zone to which it is registered
+    Given zone 1 and zone 2
+    When client_one does a PUT on <api> with <identifier_in_zone1> in zone 1
+    And client_one does a GET on <api> with <identifier_in_zone1> in zone 1
+    Then the request has status code 200
+    When client_two does a GET on <api> with <identifier_in_zone1> in zone 1
+    Then the request has status code 403
+    When client_two does a PUT on <api> with <identifier_in_zone1> in zone 2
+    And client_two does a GET on <api> with <identifier_in_zone1> in zone 2
+    Then the request has status code 200
+
+    Examples:
+    | api | identifier_in_zone1|
+    |subject|subject_id_1|
+    |resource|resource_id_1|
+    |policy-set|policy_set_1|
+
+    Scenario Outline: A client can only delete resources in the zone to which it is registered
+    Given zone 1 and zone 2
+    When client_one does a PUT on <api> with <identifier_in_zone1> in zone 1
+    And client_two does a DELETE on <api> with <identifier_in_zone1> in zone 2
+    And client_one does a GET on <api> with <identifier_in_zone1> in zone 1
+    Then the request has status code 200
+    When client_one does a DELETE on <api> with <identifier_in_zone1> in zone 1
+    Then the request has status code 204
+
+    Examples:
+    | api | identifier_in_zone1|
+    |subject|subject_id_1|
+    |resource|resource_id_1|
+    |policy-set|policy_set_1|
diff --git a/acs-integration-tests/src/test/resources/cucumber.xml b/acs-integration-tests/src/test/resources/cucumber.xml
new file mode 100644
index 0000000..aa12183
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/cucumber.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
+    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
+
+    <context:component-scan base-package="com.ge.predix.acceptance.test" />
+
+    <!-- wire beans required for testing -->
+    <import resource="classpath:acceptance-test-spring-context.xml" />
+</beans>
+
diff --git a/acs-integration-tests/src/test/resources/deny-all.json b/acs-integration-tests/src/test/resources/deny-all.json
new file mode 100644
index 0000000..ba983c1
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/deny-all.json
@@ -0,0 +1,9 @@
+{
+    "name" : "deny-all",
+    "policies" : [
+        {
+            "name" : "Access to everyone to everything",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/integration-test-spring-context.xml b/acs-integration-tests/src/test/resources/integration-test-spring-context.xml
new file mode 100644
index 0000000..ae72655
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/integration-test-spring-context.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
+    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
+
+    <import resource="classpath:shared-test-spring-context.xml" />
+
+    <!-- <context:component-scan base-package="com.ge.predix.integration.test"/> -->
+
+    <beans profile="integration">
+        <context:component-scan base-package="com.ge.predix.cloudfoundry,com.ge.predix.acs.cloudfoundry"/>
+    </beans>
+</beans>
diff --git a/acs-integration-tests/src/test/resources/invalid-json-schema-policy-set.json b/acs-integration-tests/src/test/resources/invalid-json-schema-policy-set.json
new file mode 100644
index 0000000..b785b79
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/invalid-json-schema-policy-set.json
@@ -0,0 +1,3 @@
+{
+    "name" : "invalid-simple-policy-1"
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/log4j.xml b/acs-integration-tests/src/test/resources/log4j.xml
new file mode 100644
index 0000000..051bf5a
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/log4j.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<!-- Introducing logback.xml for acs-int-tests package. Adding log level
+    control for class ResourceOwnerPasswordAccessTokenProvider to avoid printing
+    password details in log file -->
+<log4j:configuration debug="false"
+    xmlns:log4j='http://jakarta.apache.org/log4j/'>
+    <appender name="console" class="org.apache.log4j.ConsoleAppender">
+        <layout class="org.apache.log4j.PatternLayout">
+        <param name="ConversionPattern" value="%d{YYYY-MM-dd HH:mm:ss} %p [%t] %c [%F:%L] %m%n"/>
+        </layout>
+    </appender>
+
+    <logger name="java">
+        <level value="warn" />
+    </logger>
+
+    <!-- To avoid printing UAA password credentials info in log file, changing
+        the log level for the following class to INFO. -->
+    <!--     <logger
+        name="org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider"
+        level="INFO" /> -->
+
+    <logger name="org.springframework.security">
+        <level value ="info" />
+    </logger>
+
+    <logger name="com.ge.predix">
+        <level value ="debug" />
+    </logger>
+
+    <logger name="org.apache.http.wire">
+        <level value ="debug" />
+    </logger>
+
+    <logger name="http.wire">
+        <level value ="debug" />
+    </logger>
+
+    <logger name="com.ge.predix.test.TestNameLogger">
+        <level value="DEBUG"/>
+    </logger>
+
+    <logger name="com.ge.predix.test.TestNamePredixLogger">
+        <level value="DEBUG"/>
+    </logger>
+
+    <!-- Uncomment to debug Cloud-Foundry-related operations -->
+    <!--<logger name="cloudfoundry-client">-->
+        <!--<level value="DEBUG"/>-->
+    <!--</logger>-->
+
+    <root>
+        <level value = "INFO" />
+        <appender-ref ref="console" />
+    </root>
+
+</log4j:configuration>
diff --git a/acs-integration-tests/src/test/resources/missing-policy-set-name-policy.json b/acs-integration-tests/src/test/resources/missing-policy-set-name-policy.json
new file mode 100644
index 0000000..8aa96a3
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/missing-policy-set-name-policy.json
@@ -0,0 +1,28 @@
+{
+    
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/sanramon"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "subject1",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { 
+                  "name" : "condition name",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), 'sanramon')" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/multiple-actions-defined-policy-set.json b/acs-integration-tests/src/test/resources/multiple-actions-defined-policy-set.json
new file mode 100644
index 0000000..aeafb79
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/multiple-actions-defined-policy-set.json
@@ -0,0 +1,17 @@
+{
+    "name" : "multiple-actions-defined-policy-set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher_MUST_match_this_policy_during_this_test",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "any"
+                },
+                "action" : "GET,POST, DELETE"
+            },
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/multiple-actions-with-single-invalid-action-defined-policy-set.json b/acs-integration-tests/src/test/resources/multiple-actions-with-single-invalid-action-defined-policy-set.json
new file mode 100644
index 0000000..9d7c57b
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/multiple-actions-with-single-invalid-action-defined-policy-set.json
@@ -0,0 +1,17 @@
+{
+    "name" : "multiple-actions-with-single-invalid-action-defined-policy-set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher_MUST_match_this_policy_during_this_test",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "any"
+                },
+                "action" : "GET , PUT,POST1, DELETE"
+            },
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/multiple-site-based-policy-set.json b/acs-integration-tests/src/test/resources/multiple-site-based-policy-set.json
new file mode 100644
index 0000000..f10ea09
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/multiple-site-based-policy-set.json
@@ -0,0 +1,55 @@
+{
+    "name" : "multiple-site-test-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When a operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { 
+                  "name":"",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uriVariable('site_id'))" 
+                }
+            ],
+            "effect" : "DENY"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When a operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }               
+            },
+            "conditions" : [
+                { 
+                  "name":"",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uriVariable('site_id'))" 
+                }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/no-defined-action-policy-set.json b/acs-integration-tests/src/test/resources/no-defined-action-policy-set.json
new file mode 100644
index 0000000..6abd405
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/no-defined-action-policy-set.json
@@ -0,0 +1,16 @@
+{
+    "name" : "no-defined-action-policy-set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher_MUST_match_this_policy_during_this_test",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "any"
+                }
+            },
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/permit-admin-only-using-condition-policy-set.json b/acs-integration-tests/src/test/resources/permit-admin-only-using-condition-policy-set.json
new file mode 100644
index 0000000..9dfb1aa
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/permit-admin-only-using-condition-policy-set.json
@@ -0,0 +1,19 @@
+{
+    "name" : "permit-admin-only-policy-set",
+    "policies" : [
+        {
+            "name" : "Subject with role Administrator has access to everything",
+            "conditions" : [
+                { 
+                  "name":"",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'role'), 'administrator')" 
+                }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "DENY to everyone else",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/permit-admin-only-using-matcher-policy-set.json b/acs-integration-tests/src/test/resources/permit-admin-only-using-matcher-policy-set.json
new file mode 100644
index 0000000..1e39953
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/permit-admin-only-using-matcher-policy-set.json
@@ -0,0 +1,28 @@
+{
+    "name" : "permit-admin-only-using-matcher-policy-set",
+    "policies" : [
+        {
+            "name" : "Subject with role Administrator has access to everything",
+             "target" : {
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "any"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "subject1",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "role",
+                          "value" : "administrator" }
+                    ]
+                }
+            },
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "DENY to everyone else",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/permit-admin-with-site-access-matcher-and-condition.json b/acs-integration-tests/src/test/resources/permit-admin-with-site-access-matcher-and-condition.json
new file mode 100644
index 0000000..59dfa21
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/permit-admin-with-site-access-matcher-and-condition.json
@@ -0,0 +1,34 @@
+{
+    "name" : "permit-admin-with-site-access-matcher-and-condition",
+    "policies" : [
+        {
+            "name" : "Subject with role Administrator and site access has access to the site",
+             "target" : {
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/site/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "subject1",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "role",
+                          "value" : "administrator" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { 
+                  "name":"",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uriVariable('site_id'))" 
+                }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "DENY to everyone else",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/permit-all.json b/acs-integration-tests/src/test/resources/permit-all.json
new file mode 100644
index 0000000..562ebc3
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/permit-all.json
@@ -0,0 +1,9 @@
+{
+    "name" : "permit-all",
+    "policies" : [
+        {
+            "name" : "Access to everyone to everything",
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/policies/complete-sample-policy-set-2.json b/acs-integration-tests/src/test/resources/policies/complete-sample-policy-set-2.json
new file mode 100644
index 0000000..b9e6060
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/policies/complete-sample-policy-set-2.json
@@ -0,0 +1,61 @@
+{
+    "name" : "test-policy-set",
+    "policies" : [
+        {
+            "name" : "Agents can access a site if they are stationed at the site.",
+            "target" : {
+                "name" : "When an agent accesses a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Agent",
+                    "attributes" : [
+                        { "issuer" : "acs.example.org",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('acs.example.org', 'site'), resource.uriVariable('site_id'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Agents can access evidence if they are a member of the evidence group and have the right clearance.",
+            "target" : {
+                "name" : "When an agent accesses evidence",
+                "resource" : {
+                    "name" : "Evidence",
+                    "uriTemplate" : "/evidence/{evidence_id}",
+                    "attributes" : [
+                        { "issuer" : "acs.example.org",
+                          "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Agent",
+                    "attributes" : [
+                        { "issuer" : "acs.example.org",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "resource.and(subject).haveSame('acs.example.org', 'group').result()" },
+                { "name" : "has clearance",
+                  "condition" : "resource.and(subject).haveSame('acs.example.org', 'classification').result()" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Deny all other operations by default",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/policies/deny-all.json b/acs-integration-tests/src/test/resources/policies/deny-all.json
new file mode 100644
index 0000000..408ea91
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/policies/deny-all.json
@@ -0,0 +1,9 @@
+{
+    "name" : "default",
+    "policies" : [
+        {
+            "name" : "Access to everyone to everything",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/policies/indeterminate.json b/acs-integration-tests/src/test/resources/policies/indeterminate.json
new file mode 100644
index 0000000..b88a5f1
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/policies/indeterminate.json
@@ -0,0 +1,15 @@
+{
+    "name" : "default",
+    "policies" : [
+        {
+            "name" : "never",
+            "target" : {
+                "resource" : {
+                    "uriTemplate" : "/never"
+                },
+                "action" : "POST"
+            },
+            "effect" : "PERMIT"
+        }
+    ]
+}
diff --git a/acs-integration-tests/src/test/resources/policies/large-policy-set.json b/acs-integration-tests/src/test/resources/policies/large-policy-set.json
new file mode 100644
index 0000000..ab7196b
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/policies/large-policy-set.json
@@ -0,0 +1,2025 @@
+{  
+   "name":"PolicySet-for-APM",
+   "policies":[  
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/alarms/{uuid}"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/asset/tz/timeZone/{assetId}"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/config/{version}/tenants/{tenantId}/components/{componentName}"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/faulttolerance/create"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profiles"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profiles"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profiles"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profiles"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profiles"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileSources"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileSources"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileSources"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileSources"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileSources"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileTypes"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileTypes"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileTypes"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileTypes"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileTypes"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/tags"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/tags"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/tags"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/tags"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/tags"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/templates"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/templates"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/templates"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/templates"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/templates"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/ss/v0/analytics"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/ss/v0/analytics/{assetUUID}"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/ss/v0/blueprints"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/timeseries/data"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/timeseries/smartsignaldata"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/{version}/umgmt/users/me"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/acs/{anySubPath}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/acs/{anySubPath}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/acs/{anySubPath}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/acs/{anySubPath}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/pgs/asset/incremental/upload"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/pgs/asset/upload"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/pgs/assetType/upload"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/pgs/om/alarm/create"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/pgs/ss/alarm/create"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/task/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/tasks"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/alarms"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmreports/{version}/timeseries/digest"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmreports/{version}/timeseries/init"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/alarmprofiles/profile/profileByName"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/alarmtemplates/getTemplatesForAlarm"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/assetType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/assetType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/assetType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterpriseType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterpriseType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterpriseType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segmentType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segmentType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segmentType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/siteType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/siteType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/siteType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/algorithm"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/algorithm/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/algorithm/associate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/tag/associate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/tag/disassociate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}/tag/associate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}/tag/disassociate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}/tag/associate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}/tag/disassociate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/tag"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/tag"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/tag/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/blobstorage/attachments"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/alarms"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/alarms/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/v2/alarms"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/v2/alarms"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmreports/{version}/reports"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"services/apm/{version}/alarmtemplates/views/getViews"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/alarmtemplates/getTemplateName"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/alarmtemplates/templates"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/alarmtemplates/views/getViews"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetbrowser/asset"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/bySourceKey"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/children"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/parent"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/assetType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise/{uuid}/sites"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprises"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterpriseType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterpriseType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}/children"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}/parent"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segmentType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segmentType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}/segments"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/siteType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/siteType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/measurementtags"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/tags"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/timeseries/{uuid}/{startTime}/{endTime}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/blobstorage/attachments"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/blobstorage/attachments"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/blobstorage/attachments/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/blobstorage/attachments/{uuid}/download"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/casemanagement/cases"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/casemanagement/cases"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/casemanagement/cases/{caseuuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/notemanagement/notes"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/notemanagement/notes"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/notemanagement/notes/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/gasturbineperformance/assetpair"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/gasturbineperformance/{page}/currentperformance"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/gasturbineperformance/{page}/timeseries/{startTimeMillis}/{endTimeMillis}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/heatexchanger/assetpair"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/heatexchanger/{phase}/currentperformance"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/heatexchanger/{phase}/timeseries/{startTimeMillis}/{endTimeMillis}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/processcompressor/assetpair"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/processcompressor/{page}/currentperformance"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/processcompressor/{page}/timeseries/{startTimeMillis}/{endTimeMillis}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/processsurveillance/kpi"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/processsurveillance/tier2/pages"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/propanephase/historical/{phase}/{startTimeMillis}/{endTimeMillis}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/{version}/umgmt/users"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/{version}/umgmt/users/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/{version}/umgmt/users/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/assetType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager,' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "resource":{  
+               "uriTemplate":"/services/{anySubPath:.+}"
+            },
+            "action":"GET"
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'ReadOnly' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "name":"Deny all operations by default",
+         "effect":"DENY"
+      }
+   ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/policies/multiple-attribute-uri-templates.json b/acs-integration-tests/src/test/resources/policies/multiple-attribute-uri-templates.json
new file mode 100644
index 0000000..25e5059
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/policies/multiple-attribute-uri-templates.json
@@ -0,0 +1,57 @@
+{
+    "name" : "default",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are in the site organization.",
+            "target" : {
+                "name" : "When a operator reads a site",
+                "resource" : {
+                    "name" : "Site and asset",
+                    "uriTemplate" : "/v1/site/{site_id}/plant/asset/{asset_id}",
+                    "attributeUriTemplate": "/v1{attribute_uri}/plant/asset/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "org" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name":""
+                }
+            },
+            "conditions" : [
+                { 
+                  "name":"",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'org'), resource.attributes('https://acs.attributes.int', 'org'))"
+                }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read an asset if they are in the asset organization.",
+            "target" : {
+                "name" : "When a operator reads a site",
+                "resource" : {
+                    "name" : "Site and asset",
+                    "uriTemplate" : "/v1/site/{site_id}/plant/asset/{asset_id}",
+                    "attributeUriTemplate": "/v1/site/{site_id}/plant{attribute_uri}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "org" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name":""
+                }
+            },
+            "conditions" : [
+                { 
+                  "name":"",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'org'), resource.attributes('https://acs.attributes.int', 'org'))"
+                }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/policies/permit-admin-only-using-condition.json b/acs-integration-tests/src/test/resources/policies/permit-admin-only-using-condition.json
new file mode 100644
index 0000000..4e44372
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/policies/permit-admin-only-using-condition.json
@@ -0,0 +1,19 @@
+{
+    "name" : "permit-admin-only-using-condition",
+    "policies" : [
+        {
+            "name" : "Subject with role Administrator has access to everything",
+            "conditions" : [
+                { 
+                  "name":"",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'role'), 'administrator')" 
+                }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "DENY to everyone else",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/policies/policy-set-with-attribute-uri-template.json b/acs-integration-tests/src/test/resources/policies/policy-set-with-attribute-uri-template.json
new file mode 100644
index 0000000..8db21bb
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/policies/policy-set-with-attribute-uri-template.json
@@ -0,0 +1,31 @@
+{
+    "name" : "policy-set-with-uri-attribute-template",
+    "policies" : [
+        {
+            "name" : "match-site-attribute-policy",
+            "target" : {
+                "resource" : {
+                    "uriTemplate" : "/v1/region/report/asset/{asset_id}",
+                    "attributeUriTemplate": "/v1/region/report{attribute_uri}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                },
+                "action" : "GET"
+            },
+           "conditions" : [
+                { 
+                  "condition" : "match.single(resource.attributes('https://acs.attributes.int', 'site'), 'sanramon')" 
+                }
+            ],
+            "effect" : "PERMIT"
+        }
+        ,
+        
+        {
+            "name" : "deny-all-policy",
+            "effect" : "DENY"
+        }
+    ]
+}
diff --git a/acs-integration-tests/src/test/resources/policies/single-org-based-no-target.json b/acs-integration-tests/src/test/resources/policies/single-org-based-no-target.json
new file mode 100644
index 0000000..3ed50bf
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/policies/single-org-based-no-target.json
@@ -0,0 +1,15 @@
+{
+    "name" : "default",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they belong to the site organization.",
+            "conditions" : [
+                { 
+                  "name":"",
+                  "condition" : "match.single(resource.attributes('https://acs.attributes.int', 'org'), 'alliance')"
+                }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/policies/single-org-based.json b/acs-integration-tests/src/test/resources/policies/single-org-based.json
new file mode 100644
index 0000000..7853800
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/policies/single-org-based.json
@@ -0,0 +1,26 @@
+{
+    "name" : "default",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they belong to the site organization.",
+            "target" : {
+                "name" : "When a operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name":""
+                }
+            },
+            "conditions" : [
+                { 
+                  "name":"",
+                  "condition" : "match.single(resource.attributes('https://acs.attributes.int', 'org'), 'alliance')"
+                }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/policies/single-site-based.json b/acs-integration-tests/src/test/resources/policies/single-site-based.json
new file mode 100644
index 0000000..4979a2e
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/policies/single-site-based.json
@@ -0,0 +1,26 @@
+{
+    "name" : "default",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When a operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name":""
+                }
+            },
+            "conditions" : [
+                { 
+                  "name":"",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uriVariable('site_id'))" 
+                }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/policies/single-site-subject-based-policy-set.json b/acs-integration-tests/src/test/resources/policies/single-site-subject-based-policy-set.json
new file mode 100644
index 0000000..0513a4d
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/policies/single-site-subject-based-policy-set.json
@@ -0,0 +1,26 @@
+{
+    "name" : "single-site-subject-based-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When a operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name":""
+                }
+            },
+            "conditions" : [
+                { 
+                  "name":"",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uriVariable('site_id'))" 
+                }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/policy-set-with-multiple-policies-na-with-condition.json b/acs-integration-tests/src/test/resources/policy-set-with-multiple-policies-na-with-condition.json
new file mode 100644
index 0000000..6cee5ae
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/policy-set-with-multiple-policies-na-with-condition.json
@@ -0,0 +1,78 @@
+{
+    "name" : "test-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "DENY"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "PERMIT"
+        }
+        
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/policy-set-with-one-policy-using-resource-attributes-from-asset-adapter.json b/acs-integration-tests/src/test/resources/policy-set-with-one-policy-using-resource-attributes-from-asset-adapter.json
new file mode 100644
index 0000000..8b28770
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/policy-set-with-one-policy-using-resource-attributes-from-asset-adapter.json
@@ -0,0 +1,47 @@
+{
+  "name": "test-policy-set-with-asset-attributes",
+  "policies": [
+    {
+      "name": "Policy for a part",
+      "conditions": [
+        {
+          "name": "has a specific id",
+          "condition": "match.single(resource.attributes('https://not/set/yet', 'id'), '03f95db1-4255-4265-a509-f7bca3e1fee4')"
+        },
+        {
+          "name": "is part of a specific collection",
+          "condition": "match.single(resource.attributes('https://not/set/yet', 'collection'), 'part')"
+        },
+        {
+          "name": "belongs to a specific model",
+          "condition": "match.single(resource.attributes('https://not/set/yet', 'partModel'), '/partmodels/9a92831d-42f1-4f9e-86bf-4c0914f481e4')"
+        },
+        {
+          "name": "belongs to a specific structural model",
+          "condition": "match.single(resource.attributes('https://not/set/yet', 'structureModel'), '/structureModels/8c787978-bd8b-417a-b759-f63a8a6d43ee')"
+        },
+        {
+          "name": "has a specific serial number",
+          "condition": "match.single(resource.attributes('https://not/set/yet', 'serialNumber'), '775277328')"
+        },
+        {
+          "name": "has a specific parent",
+          "condition": "match.single(resource.attributes('https://not/set/yet', 'parent'), '/part/01af94ed-5425-44e4-9f6e-2a58cba7b559')"
+        },
+        {
+          "name": "belongs to a specific aircraft part",
+          "condition": "match.single(resource.attributes('https://not/set/yet', 'aircraftPart'), '/aircraftPart/13a71359-db68-4602-aac5-a8fa401c3194')"
+        },
+        {
+          "name": "belongs to a specific aircraft part model",
+          "condition": "match.single(resource.attributes('https://not/set/yet', 'aircraftPartModel'), '/aircraftPartModels/1dc6a36d-a24e-4fec-a181-f576c95a8104')"
+        },
+        {
+          "name": "has a specific uri",
+          "condition": "match.single(resource.attributes('https://not/set/yet', 'uri'), '/part/03f95db1-4255-4265-a509-f7bca3e1fee4')"
+        }
+      ],
+      "effect": "PERMIT"
+    }
+  ]
+}
diff --git a/acs-integration-tests/src/test/resources/policy-set-with-only-name-effect.json b/acs-integration-tests/src/test/resources/policy-set-with-only-name-effect.json
new file mode 100644
index 0000000..45bed53
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/policy-set-with-only-name-effect.json
@@ -0,0 +1,8 @@
+{
+    "name": "my policy set",
+    "policies" : [
+        {
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/shared-test-spring-context.xml b/acs-integration-tests/src/test/resources/shared-test-spring-context.xml
new file mode 100644
index 0000000..0ae0825
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/shared-test-spring-context.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
+    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
+
+    <context:annotation-config/>
+    <context:component-scan base-package="com.ge.predix.test"/>
+
+    <bean
+            class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
+            xmlns="http://www.springframework.org/schema/beans">
+        <property name="locations">
+            <list>
+                <value>classpath:application.properties</value>
+            </list>
+        </property>
+    </bean>
+
+</beans>
diff --git a/acs-integration-tests/src/test/resources/single-action-defined-policy-set.json b/acs-integration-tests/src/test/resources/single-action-defined-policy-set.json
new file mode 100644
index 0000000..048b92c
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/single-action-defined-policy-set.json
@@ -0,0 +1,17 @@
+{
+    "name" : "single-action-defined-policy-set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher_MUST_match_this_policy_during_this_test",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "any"
+                },
+                "action" : "GET"
+            },
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/single-empty-action-defined-policy-set.json b/acs-integration-tests/src/test/resources/single-empty-action-defined-policy-set.json
new file mode 100644
index 0000000..efc9cfb
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/single-empty-action-defined-policy-set.json
@@ -0,0 +1,17 @@
+{
+    "name" : "single-empty-action-defined-policy-set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher_MUST_match_this_policy_during_this_test",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "any"
+                },
+                "action" : "   "
+            },
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/single-invalid-action-defined-policy-set.json b/acs-integration-tests/src/test/resources/single-invalid-action-defined-policy-set.json
new file mode 100644
index 0000000..c27e501
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/single-invalid-action-defined-policy-set.json
@@ -0,0 +1,17 @@
+{
+    "name" : "single-invalid-action-defined-policy-set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher_MUST_match_this_policy_during_this_test",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "any"
+                },
+                "action" : "GET1"
+            },
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/single-null-action-defined-policy-set.json b/acs-integration-tests/src/test/resources/single-null-action-defined-policy-set.json
new file mode 100644
index 0000000..873d76b
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/single-null-action-defined-policy-set.json
@@ -0,0 +1,17 @@
+{
+    "name" : "single-null-action-defined-policy-set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher_MUST_match_this_policy_during_this_test",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "any"
+                },
+                "action" : null
+            },
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/single-site-based-policy-set.json b/acs-integration-tests/src/test/resources/single-site-based-policy-set.json
new file mode 100644
index 0000000..0a3b90f
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/single-site-based-policy-set.json
@@ -0,0 +1,26 @@
+{
+    "name" : "single-site-based-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When a operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name":""
+                }
+            },
+            "conditions" : [
+                { 
+                  "name":"",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uriVariable('site_id'))" 
+                }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/src/test/resources/testCompleteACSFlow.json b/acs-integration-tests/src/test/resources/testCompleteACSFlow.json
new file mode 100644
index 0000000..8a5aa0c
--- /dev/null
+++ b/acs-integration-tests/src/test/resources/testCompleteACSFlow.json
@@ -0,0 +1,34 @@
+{
+    "name" : "testCompleteACSFlow",
+    "policies" : [
+        {
+            "target" : {
+                "resource" : {
+                    "uriTemplate" : "/alarms/sites/{site_id}",
+                     "attributes" : [
+                     	{
+                     	"issuer":"issuerId1",
+                     	"name":"region"
+                     	}
+                     ]
+                },
+                "action" : "GET",
+                "subject" : {
+                     "attributes" : [
+                     	{
+                     	"issuer":"issuerId1",
+                     	"name":"site"
+                     	}
+                     ]
+                }
+            },
+            "conditions" : [
+                { 
+                  "condition" : 
+                  "match.single(subject.attributes('issuerId1', 'site'), resource.uriVariable('site_id')) & resource.attributes('issuerId1','region').contains('testregion')" 
+                }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/acs-integration-tests/testng-public-titan.xml b/acs-integration-tests/testng-public-titan.xml
new file mode 100644
index 0000000..f1b31a7
--- /dev/null
+++ b/acs-integration-tests/testng-public-titan.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+
+<suite name="testAll">
+    <test name="public-titan">
+        <classes>
+            <class name="com.ge.predix.test.TestConfig"/>
+            <class name="com.ge.predix.integration.test.AttributeConnectorConfigurationIT" />
+            <class name="com.ge.predix.acceptance.test.ACSAcceptanceIT"/>
+            <class name="com.ge.predix.integration.test.AccessControlServiceIT"/>
+            <class name="com.ge.predix.integration.test.PrivilegeManagementAccessControlServiceIT"/>
+            <class name="com.ge.predix.integration.test.PolicyEvalCachingWithGraphDBIT"/>
+            <class name="com.ge.predix.integration.test.PolicyEvaluationCachingIT"/>
+            <class name="com.ge.predix.integration.test.ACSCorsFilterIT" />
+            <class name="com.ge.predix.acceptance.test.policy.admin.PolicyCreationCucumberTest" />
+            <class name="com.ge.predix.acceptance.test.policy.evaluation.PolicyEvaluationCucumberTest" />
+            <class name="com.ge.predix.acceptance.test.zone.admin.ZoneEnforcementCucumberTest" />
+        </classes>
+    </test>
+</suite>
diff --git a/acs-integration-tests/testng-public.xml b/acs-integration-tests/testng-public.xml
new file mode 100644
index 0000000..0401f24
--- /dev/null
+++ b/acs-integration-tests/testng-public.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+
+<suite name="testAll">
+    <test name="public">
+        <classes>
+            <class name="com.ge.predix.test.TestConfig"/>
+            <class name="com.ge.predix.integration.test.AttributeConnectorConfigurationIT" />
+            <class name="com.ge.predix.acceptance.test.ACSAcceptanceIT"/>
+            <class name="com.ge.predix.integration.test.AccessControlServiceIT"/>
+            <class name="com.ge.predix.integration.test.PrivilegeManagementAccessControlServiceIT"/>
+            <class name="com.ge.predix.integration.test.PolicyEvaluationCachingIT"/>
+            <class name="com.ge.predix.integration.test.ACSCorsFilterIT" />
+            <class name="com.ge.predix.acceptance.test.policy.admin.PolicyCreationCucumberTest" />
+            <class name="com.ge.predix.acceptance.test.policy.evaluation.PolicyEvaluationCucumberTest" />
+            <class name="com.ge.predix.acceptance.test.zone.admin.ZoneEnforcementCucumberTest" />
+        </classes>
+    </test>
+</suite>
diff --git a/acs-integration-tests/uaa/config/uaa.yml b/acs-integration-tests/uaa/config/uaa.yml
new file mode 100644
index 0000000..7560188
--- /dev/null
+++ b/acs-integration-tests/uaa/config/uaa.yml
@@ -0,0 +1,625 @@
+# Configuration in this file ixs overridden by an external file
+# if any of these exist:
+# [$UAA_CONFIG_URL, $UAA_CONFIG_PATH/uaa.yml, $CLOUD_FOUNDRY_CONFIG_PATH/uaa.yml]
+
+#spring_profiles: mysql,default,ldap
+#spring_profiles: postgresql,default
+#spring_profiles: ldap,default,hsqldb
+#spring_profiles: saml
+#spring_profiles: keystone,default,hsqldb
+
+#database:
+#  driverClassName: org.postgresql.Driver
+#  url: jdbc:postgresql:uaa
+#  username: pivotal
+#  password:
+#  maxactive: 100
+#  maxidle: 10
+#  minidle: 3
+#  removeabandoned: false
+#  logabandoned: true
+#  abandonedtimeout: 300
+#  evictionintervalms: 15000
+#  caseinsensitive: false
+
+#note - this is not the place to set these properties
+# - they are just here for documentation purposes
+#database.driverClassName: org.postgresql.Driver
+#database.url: jdbc:postgresql:uaa
+#database.username: root
+#database.password: changeme
+
+#MS SQL Server Install https://docs.microsoft.com/en-us/sql/linux/sql-server-linux-setup-ubuntu
+#sqlserver commands are (first command is to allow contained user authentication for a DB
+#sp_configure 'contained database authentication', 1
+#go
+#RECONFIGURE
+#go
+#CREATE DATABASE uaa CONTAINMENT = PARTIAL;
+#go
+#USE uaa;
+#go
+#CREATE USER root WITH PASSWORD = 'changemeCHANGEME1234!';
+#go
+#EXEC sp_addrolemember N'db_owner', N'root';
+#go
+
+
+#postgresql commands that were run were
+#create database uaa;
+#create user root with superuser password 'changeme';
+
+#database:
+#  driverClassName: org.mariadb.jdbc.Driver
+#  url: jdbc:mysql://localhost:8080/uaa
+#  username: root
+#  password: blabla
+
+#mysql commands that were run
+#create database uaa;
+#SET PASSWORD FOR 'root'@'localhost' = PASSWORD('changeme');
+#CREATE USER 'root'@'127.0.0.1';
+#GRANT ALL TO 'root'@'127.0.0.1' WITH GRANT OPTION;
+#SET PASSWORD FOR 'root'@'127.0.0.1' = PASSWORD('changeme');
+
+# A comprehensive list of hostnames routed to the UAA default zone. The UAA uses this to resolve subdomains for Identity Zones.
+# Defaults to 'localhost'
+#zones:
+#  internal:
+#    hostnames:
+#      - host1.domain.com
+#      - host2
+#      - testzone3.localhost
+#      - testzone4.localhost
+
+#authentication:
+#  policy:
+#    lockoutAfterFailures: 5
+#    countFailuresWithinSeconds: 3600
+#    lockoutPeriodSeconds: 600
+
+# Set this property to true for disabling authentication via the internal IDP. Defaults to false.
+#disableInternalAuth: false
+
+# Set this property to true for disabling access to user management endpoints and controllers. Defaults to false.
+#disableInternalUserManagement: true
+
+#keystone:
+#  authentication:
+#    url: http://localhost:35357/v2.0/tokens
+#    url: http://localhost:5000/v3/auth/tokens
+
+#ldap:
+#  profile:
+#    file: ldap/ldap-simple-bind.xml
+#  base:
+#    url: 'ldaps://192.168.3.39:10636/'
+#    userDnPattern: 'cn={0},ou=Users,dc=test,dc=com;cn={0},ou=OtherUsers,dc=example,dc=com'
+#  ssl:
+#    skipverification: false
+#    sslCertificate: ! '-----BEGIN CERTIFICATE-----
+#    MIIBfTCCAScCBgFDfaC2yzANBgkqhkiG9w0BAQUFADBCMQswCQYDVQQGEwJVUzEMMAoGA1UEChMD
+#    QVNGMRIwEAYDVQQLEwlEaXJlY3RvcnkxETAPBgNVBAMTCEFwYWNoZURTMB4XDTE0MDExMDE5Mjg0
+#    MVoXDTE1MDExMDE5Mjg0MVowTDELMAkGA1UEBhMCVVMxDDAKBgNVBAoTA0FTRjESMBAGA1UECxMJ
+#    RGlyZWN0b3J5MRswGQYDVQQDExJmaGFuaWstd29ya3N0YXRpb24wXDANBgkqhkiG9w0BAQEFAANL
+#    ADBIAkEAuA6Nmto6NFCCJ+CwsBnT2cvMxuYgf26iZ3ckIpLhs2V4ZJ4PFinR6JZUsVnRp0RbYoV5
+#    iW6F91XDTVtAMtDTJwIDAQABMA0GCSqGSIb3DQEBBQUAA0EATFGpEIprKYcnc+JuNcSQ8v2P2J7e
+#    lQ23NhTaljASF0g8AZ7SZEItU8JFYqf/KnNJ7FPwo4LbMbr7Zg6BRKBvnQ==
+#    -----END CERTIFICATE-----'
+#    sslCertificateAlias: ldaps
+#    tls: <none | simple | external>
+#  externalGroupsWhitelist:
+#    - admin
+#    - user
+#  emailDomain:
+#    - example.com
+#  attributeMappings:
+#    given_name: givenname
+#    family_name: sn
+#    phone_number: telephonenumber
+#    user.attribute.employeeCostCenter: costCenter
+#    user.attribute.terribleBosses: uaaManager
+#  providerDescription: 'Human readable description of this provider'
+
+
+#ldap:
+#  profile:
+#    file: ldap/ldap-search-and-bind.xml
+#  base:
+#    url: 'ldap://localhost:389/'
+#    userDn: 'cn=admin,dc=test,dc=com'
+#    password: 'password'
+#    searchBase: 'dc=test,dc=com'
+#    searchFilter: 'cn={0}'
+#    referral: follow
+#  groups:
+#      file: 'ldap/ldap-groups-map-to-scopes.xml'
+#      searchBase: 'dc=test,dc=com'
+#      groupSearchFilter: 'member={0}'
+#      searchSubtree: true
+#      maxSearchDepth: 10
+#      autoAdd: true
+#      ignorePartialResultException: true
+
+#ldap:
+#  profile:
+#    file: ldap/ldap-search-and-compare.xml
+#  base:
+#    url: 'ldap://localhost:10389/'
+#    userDn: 'cn=admin,dc=test,dc=com'
+#    password: 'password'
+#    searchBase: ''
+#    searchFilter: 'cn={0}'
+#    passwordAttributeName: userPassword
+#    passwordEncoder: org.cloudfoundry.identity.uaa.provider.ldap.DynamicPasswordComparator
+#    localPasswordCompare: true
+
+#password:
+#  policy:
+#    minLength: 0
+#    maxLength: 128
+#    requireUpperCaseCharacter: 0
+#    requireLowercaseCharacter: 0
+#    requireDigit: 0
+#    requireSpecialCharacter: 0
+#    expirePasswordInMonths: 0
+
+scim:
+  groups:
+    zones.read: Read identity zones
+    zones.write: Create and update identity zones
+    idps.read: Retrieve identity providers
+    idps.write: Create and update identity providers
+    clients.admin: Create, modify and delete OAuth clients
+    clients.write: Create and modify OAuth clients
+    clients.read: Read information about OAuth clients
+    clients.secret: Change the password of an OAuth client
+    scim.write: Create, modify and delete SCIM entities, i.e. users and groups
+    scim.read: Read all SCIM entities, i.e. users and groups
+    scim.create: Create users
+    scim.userids: Read user IDs and retrieve users by ID
+    scim.zones: Control a user's ability to manage a zone
+    scim.invite: Send invitations to users
+    password.write: Change your password
+    oauth.approval: Manage approved scopes
+    oauth.login: Authenticate users outside of the UAA
+    openid: Access profile information, i.e. email, first and last name, and phone number
+    groups.update: Update group information and memberships
+    uaa.user: Act as a user in the UAA
+    uaa.resource: Serve resources protected by the UAA
+    uaa.admin: Act as an administrator throughout the UAA
+    uaa.none: Forbid acting as a user
+    uaa.offline_token: Allow offline access
+
+oauth:
+  user:
+    authorities:
+      - openid
+      - scim.me
+      - cloud_controller.read
+      - cloud_controller.write
+      - cloud_controller_service_permissions.read
+      - password.write
+      - scim.userids
+      - uaa.user
+      - approvals.me
+      - oauth.approvals
+      - profile
+      - roles
+      - user_attributes
+      - uaa.offline_token
+        # List of OAuth clients
+  clients:
+    admin:
+      id: admin
+      secret: adminsecret
+      authorized-grant-types: client_credentials
+      scope: none
+      authorities: uaa.admin,clients.admin,clients.read,clients.write,clients.secre
+#  client:
+#    secret:
+#      policy:
+#        minLength: 0
+#        maxLength: 128
+#        requireUpperCaseCharacter: 0
+#        requireLowerCaseCharacter: 0
+#        requireDigit: 0
+#        requireSpecialCharacter: 0
+
+# Default token signing key. Each installation MUST provide a unique key
+# in order for tokens to be usable only on that installation.
+jwt:
+  token:
+    verification-key: |
+      -----BEGIN PUBLIC KEY-----
+      MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0m59l2u9iDnMbrXHfqkO
+      rn2dVQ3vfBJqcDuFUK03d+1PZGbVlNCqnkpIJ8syFppW8ljnWweP7+LiWpRoz0I7
+      fYb3d8TjhV86Y997Fl4DBrxgM6KTJOuE/uxnoDhZQ14LgOU2ckXjOzOdTsnGMKQB
+      LCl0vpcXBtFLMaSbpv1ozi8h7DJyVZ6EnFQZUWGdgTMhDrmqevfx95U/16c5WBDO
+      kqwIn7Glry9n9Suxygbf8g5AzpWcusZgDLIIZ7JTUldBb8qU2a0Dl4mvLZOn4wPo
+      jfj9Cw2QICsc5+Pwf21fP+hzf+1WSRHbnYv8uanRO0gZ8ekGaghM/2H6gqJbo2nI
+      JwIDAQAB
+      -----END PUBLIC KEY-----
+    signing-key: |
+      -----BEGIN RSA PRIVATE KEY-----
+      MIIEowIBAAKCAQEA0m59l2u9iDnMbrXHfqkOrn2dVQ3vfBJqcDuFUK03d+1PZGbV
+      lNCqnkpIJ8syFppW8ljnWweP7+LiWpRoz0I7fYb3d8TjhV86Y997Fl4DBrxgM6KT
+      JOuE/uxnoDhZQ14LgOU2ckXjOzOdTsnGMKQBLCl0vpcXBtFLMaSbpv1ozi8h7DJy
+      VZ6EnFQZUWGdgTMhDrmqevfx95U/16c5WBDOkqwIn7Glry9n9Suxygbf8g5AzpWc
+      usZgDLIIZ7JTUldBb8qU2a0Dl4mvLZOn4wPojfj9Cw2QICsc5+Pwf21fP+hzf+1W
+      SRHbnYv8uanRO0gZ8ekGaghM/2H6gqJbo2nIJwIDAQABAoIBAHPV9rSfzllq16op
+      zoNetIJBC5aCcU4vJQBbA2wBrgMKUyXFpdSheQphgY7GP/BJTYtifRiS9RzsHAYY
+      pAlTQEQ9Q4RekZAdd5r6rlsFrUzL7Xj/CVjNfQyHPhPocNqwrkxp4KrO5eL06qcw
+      UzT7UtnoiCdSLI7IL0hIgJZP8J1uPNdXH+kkDEHE9xzU1q0vsi8nBLlim+ioYfEa
+      Q/Q/ovMNviLKVs+ZUz+wayglDbCzsevuU+dh3Gmfc98DJw6n6iClpd4fDPqvhxUO
+      BDeQT1mFeHxexDse/kH9nygxT6E4wlU1sw0TQANcT6sHReyHT1TlwnWlCQzoR3l2
+      RmkzUsECgYEA8W/VIkfyYdUd5ri+yJ3iLdYF2tDvkiuzVmJeA5AK2KO1fNc7cSPK
+      /sShHruc0WWZKWiR8Tp3d1XwA2rHMFHwC78RsTds+NpROs3Ya5sWd5mvmpEBbL+z
+      cl3AU9NLHVvsZjogmgI9HIMTTl4ld7GDsFMt0qlCDztqG6W/iguQCx8CgYEA3x/j
+      UkP45/PaFWd5c1DkWvmfmi9UxrIM7KeyBtDExGIkffwBMWFMCWm9DODw14bpnqAA
+      jH5AhQCzVYaXIdp12b+1+eOOckYHwzjWOFpJ3nLgNK3wi067jVp0N0UfgV5nfYw/
+      +YoHfYRCGsM91fowh7wLcyPPwmSAbQAKwbOZKfkCgYEAnccDdZ+m2iA3pitdIiVr
+      RaDzuoeHx/IfBHjMD2/2ZpS1aZwOEGXfppZA5KCeXokSimj31rjqkWXrr4/8E6u4
+      PzTiDvm1kPq60r7qi4eSKx6YD15rm/G7ByYVJbKTB+CmoDekToDgBt3xo+kKeyna
+      cUQqUdyieunM8bxja4ca3ukCgYAfrDAhomJ30qa3eRvFYcs4msysH2HiXq30/g0I
+      aKQ12FSjyZ0FvHEFuQvMAzZM8erByKarStSvzJyoXFWhyZgHE+6qDUJQOF6ruKq4
+      DyEDQb1P3Q0TSVbYRunOWrKRM6xvJvSB4LUVfSvBDsv9TumKqwfZDVFVn9yXHHVq
+      b6sjSQKBgDkcyYkAjpOHoG3XKMw06OE4OKpP9N6qU8uZOuA8ZF9ZyR7vFf4bCsKv
+      QH+xY/4h8tgL+eASz5QWhj8DItm8wYGI5lKJr8f36jk0JLPUXODyDAeN6ekXY9LI
+      fudkijw0dnh28LJqbkFF5wLNtATzyCfzjp+czrPMn9uqLNKt/iVD
+      -----END RSA PRIVATE KEY-----
+#    claims:
+#      exclude:
+#        - authorities
+#    policy:
+#      # Will override global validity policies for the default zone only.
+#      accessTokenValiditySeconds: 3600
+#      refreshTokenValiditySeconds: 3600
+#      activeKeyId: key-id-1
+#      keys:
+#        key-id-1:
+#          signingKey: |
+#            -----BEGIN RSA PRIVATE KEY-----
+#            MIIEowIBAAKCAQEA0m59l2u9iDnMbrXHfqkOrn2dVQ3vfBJqcDuFUK03d+1PZGbV
+#            lNCqnkpIJ8syFppW8ljnWweP7+LiWpRoz0I7fYb3d8TjhV86Y997Fl4DBrxgM6KT
+#            JOuE/uxnoDhZQ14LgOU2ckXjOzOdTsnGMKQBLCl0vpcXBtFLMaSbpv1ozi8h7DJy
+#            VZ6EnFQZUWGdgTMhDrmqevfx95U/16c5WBDOkqwIn7Glry9n9Suxygbf8g5AzpWc
+#            usZgDLIIZ7JTUldBb8qU2a0Dl4mvLZOn4wPojfj9Cw2QICsc5+Pwf21fP+hzf+1W
+#            SRHbnYv8uanRO0gZ8ekGaghM/2H6gqJbo2nIJwIDAQABAoIBAHPV9rSfzllq16op
+#            zoNetIJBC5aCcU4vJQBbA2wBrgMKUyXFpdSheQphgY7GP/BJTYtifRiS9RzsHAYY
+#            pAlTQEQ9Q4RekZAdd5r6rlsFrUzL7Xj/CVjNfQyHPhPocNqwrkxp4KrO5eL06qcw
+#            UzT7UtnoiCdSLI7IL0hIgJZP8J1uPNdXH+kkDEHE9xzU1q0vsi8nBLlim+ioYfEa
+#            Q/Q/ovMNviLKVs+ZUz+wayglDbCzsevuU+dh3Gmfc98DJw6n6iClpd4fDPqvhxUO
+#            BDeQT1mFeHxexDse/kH9nygxT6E4wlU1sw0TQANcT6sHReyHT1TlwnWlCQzoR3l2
+#            RmkzUsECgYEA8W/VIkfyYdUd5ri+yJ3iLdYF2tDvkiuzVmJeA5AK2KO1fNc7cSPK
+#            /sShHruc0WWZKWiR8Tp3d1XwA2rHMFHwC78RsTds+NpROs3Ya5sWd5mvmpEBbL+z
+#            cl3AU9NLHVvsZjogmgI9HIMTTl4ld7GDsFMt0qlCDztqG6W/iguQCx8CgYEA3x/j
+#            UkP45/PaFWd5c1DkWvmfmi9UxrIM7KeyBtDExGIkffwBMWFMCWm9DODw14bpnqAA
+#            jH5AhQCzVYaXIdp12b+1+eOOckYHwzjWOFpJ3nLgNK3wi067jVp0N0UfgV5nfYw/
+#            +YoHfYRCGsM91fowh7wLcyPPwmSAbQAKwbOZKfkCgYEAnccDdZ+m2iA3pitdIiVr
+#            RaDzuoeHx/IfBHjMD2/2ZpS1aZwOEGXfppZA5KCeXokSimj31rjqkWXrr4/8E6u4
+#            PzTiDvm1kPq60r7qi4eSKx6YD15rm/G7ByYVJbKTB+CmoDekToDgBt3xo+kKeyna
+#            cUQqUdyieunM8bxja4ca3ukCgYAfrDAhomJ30qa3eRvFYcs4msysH2HiXq30/g0I
+#            aKQ12FSjyZ0FvHEFuQvMAzZM8erByKarStSvzJyoXFWhyZgHE+6qDUJQOF6ruKq4
+#            DyEDQb1P3Q0TSVbYRunOWrKRM6xvJvSB4LUVfSvBDsv9TumKqwfZDVFVn9yXHHVq
+#            b6sjSQKBgDkcyYkAjpOHoG3XKMw06OE4OKpP9N6qU8uZOuA8ZF9ZyR7vFf4bCsKv
+#            QH+xY/4h8tgL+eASz5QWhj8DItm8wYGI5lKJr8f36jk0JLPUXODyDAeN6ekXY9LI
+#            fudkijw0dnh28LJqbkFF5wLNtATzyCfzjp+czrPMn9uqLNKt/iVD
+#            -----END RSA PRIVATE KEY-----
+#      # Sets the default validity for all zones
+#      global:
+#        accessTokenValiditySeconds: 3600
+#        refreshTokenValiditySeconds: 3600
+#    # This is a feature flag to turn on/off the refresh token issuance behavior. If set to true, the refresh token is only granted to clients with a scope of refresh_token for offline access.
+#    refresh:
+#      restrict_grant: true
+#      unique: false
+#      format: jwt
+
+# Configure whitelist for allowing cross-origin XMLHttpRequest requests.
+#cors:
+#  xhr:
+#    allowed:
+#      headers:
+#        - Accept
+#        - Authorization
+#        - Content-Type
+#        - X-Requested-With
+#      origin:
+#        - ^localhost$
+#        - ^.*\.localhost$
+#      uris:
+#        - ^/uaa/userinfo$
+#        - ^/uaa/logout\.do$
+#      methods:
+#        - GET
+#        - OPTIONS
+#  default:
+#    allowed:
+#      headers:
+#        - Accept
+#        - Authorization
+#        - Content-Type
+#        - X-Requested-With
+#      origin:
+#        - ^localhost$
+#        - ^.*\.localhost$
+#      uris:
+#        - ^/uaa/userinfo$
+#        - ^/uaa/logout\.do$
+#      methods:
+#        - GET
+#        - PUT
+#        - POST
+#        - DELETE
+
+# Deprecated: More to follow
+# customize static asset source, provides control over visual branding
+# (defaults to /resources/oss)
+assetBaseUrl: /resources/predix
+
+#tiles:
+#  - name: Pivotal Network
+#    login-link: https://network.gopivotal.com/login
+#    image: /resources/pivotal/images/network-logo-gray.png
+#  - name: Pivotal Web Services
+#    login-link: https://console.10.244.0.34.xip.io
+#    image: /resources/pivotal/images/pws-logo-gray.png
+#  - name: Pivotal Partners
+#    login-link: https://partners.gopivotal.com/login
+#    image: /resources/pivotal/images/partners-logo-gray.png
+
+#links:
+  # Custom self service links (will only be displayed if selfServiceLinksEnabled is true)
+  # If selfServiceLinksEnabled is true and these custom links are not provided then the Login Server
+  # will use internal links.
+#  passwd: /forgot_password
+#  signup: /create_account
+
+#notifications:
+#  url: http://localhost:3001
+#logout:
+#  redirect:
+#    url: /login
+#    parameter:
+#      whitelist:
+#        - https://url1.domain1.com/logout-success
+#        - https://url2.domain2.com/logout-success
+
+login:
+  # Enable create account and forgot password links on the Login Server (enabled by default)
+  #selfServiceLinksEnabled: true
+  #base URL that the login server can be reached at
+#  oauth:
+#    providers:
+#      my-oauth-provider:
+#        type: oauth2.0
+#        authUrl: http://my-auth.com
+#        tokenUrl: http://my-token.com
+#        tokenKey: my-token-key
+#        tokenKeyUrl:
+#        issuer: token issuer (iss)
+#        scopes:
+#          - openid
+#          - scope.example
+#        emailDomain:
+#          - example.com
+#        linkText: My Oauth Provider
+#        showLinkText: true
+#        addShadowUserOnLogin: false
+#        relyingPartyId: uaa
+#        relyingPartySecret: secret
+#        attributeMappings:
+#          given_name: firstName
+#          family_name: lastname
+#          user_name: username
+#          external_groups:
+#            - scopes_example_group
+#            - roles_example_group
+  url: http://localhost:8080/uaa
+#  idpDiscoveryEnabled: true
+#  accountChooserEnabled: true
+
+  # SAML Key Configuration
+  # The location and credentials of the certificate for this SP
+  # See README.md for details on how to create this.
+  serviceProviderKey: |
+    -----BEGIN RSA PRIVATE KEY-----
+    MIICXQIBAAKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5
+    L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vA
+    fpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQAB
+    AoGAVOj2Yvuigi6wJD99AO2fgF64sYCm/BKkX3dFEw0vxTPIh58kiRP554Xt5ges
+    7ZCqL9QpqrChUikO4kJ+nB8Uq2AvaZHbpCEUmbip06IlgdA440o0r0CPo1mgNxGu
+    lhiWRN43Lruzfh9qKPhleg2dvyFGQxy5Gk6KW/t8IS4x4r0CQQD/dceBA+Ndj3Xp
+    ubHfxqNz4GTOxndc/AXAowPGpge2zpgIc7f50t8OHhG6XhsfJ0wyQEEvodDhZPYX
+    kKBnXNHzAkEAyCA76vAwuxqAd3MObhiebniAU3SnPf2u4fdL1EOm92dyFs1JxyyL
+    gu/DsjPjx6tRtn4YAalxCzmAMXFSb1qHfwJBAM3qx3z0gGKbUEWtPHcP7BNsrnWK
+    vw6By7VC8bk/ffpaP2yYspS66Le9fzbFwoDzMVVUO/dELVZyBnhqSRHoXQcCQQCe
+    A2WL8S5o7Vn19rC0GVgu3ZJlUrwiZEVLQdlrticFPXaFrn3Md82ICww3jmURaKHS
+    N+l4lnMda79eSp3OMmq9AkA0p79BvYsLshUJJnvbk76pCjR28PK4dV1gSDUEqQMB
+    qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/
+    -----END RSA PRIVATE KEY-----
+  serviceProviderKeyPassword: password
+  serviceProviderCertificate: |
+    -----BEGIN CERTIFICATE-----
+    MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEO
+    MAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEO
+    MAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5h
+    cnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwx
+    CzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAM
+    BgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAb
+    BgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GN
+    ADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39W
+    qS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOw
+    znoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4Ha
+    MIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGc
+    gBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYD
+    VQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYD
+    VQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJh
+    QGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ
+    0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxC
+    KdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkK
+    RpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=
+    -----END CERTIFICATE-----
+
+  # SAML - The entity base url is the location of this application
+  # (The host and port of the application that will accept assertions)
+  entityBaseURL: http://localhost:8080/uaa
+  # The entityID of this SP
+  entityID: cloudfoundry-saml-login
+  saml:
+    #Entity ID Alias to login at /saml/SSO/alias/{login.saml.entityIDAlias}
+    #entityIDAlias: cloudfoundry-saml-login
+    #Default nameID if IDP nameID is not set
+    nameID: 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'
+    #Default assertionConsumerIndex if IDP value is not set
+    assertionConsumerIndex: 0
+    #Local/SP metadata - sign metadata
+    signMetaData: true
+    #Local/SP metadata - requests signed
+    signRequest: true
+    #Local/SP metadata - want incoming assertions signed
+    #wantAssertionSigned: true
+    #Algorithm for SAML signatures. Defaults to SHA1.  Accepts SHA1, SHA256, SHA512
+    #signatureAlgorithm: SHA256
+    socket:
+      # URL metadata fetch - pool timeout
+      connectionManagerTimeout: 10000
+      # URL metadata fetch - read timeout
+      soTimeout: 10000
+#BEGIN SAML PROVIDERS
+#    providers:
+#      okta-signed-or-encrypted:
+#        idpMetadata: |
+#          <?xml version="1.0" encoding="UTF-8"?><md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://www.okta.com/k36wkjw6EAEJVZXFFDAU"><md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><md:KeyDescriptor use="signing"><ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:X509Data><ds:X509Certificate>MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG
+#          A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+#          MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu
+#          Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC
+#          VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM
+#          BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN
+#          AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU
+#          WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O
+#          Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL
+#          3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk
+#          vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6
+#          GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFb</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://pivotal.oktapreview.com/app/pivotal_cfsamltemplate1_1/k36wkjw6EAEJVZXFFDAU/sso/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://pivotal.oktapreview.com/app/pivotal_cfsamltemplate1_1/k36wkjw6EAEJVZXFFDAU/sso/saml"/></md:IDPSSODescriptor></md:EntityDescriptor>
+#        nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+#        assertionConsumerIndex: 0
+#        metadataTrustCheck: true
+#        showSamlLoginLink: true
+#        linkText: 'Okta Preview Signed'
+#      okta-local:
+#        idpMetadata: https://pivotal.oktapreview.com/app/k36wkjw6EAEJVZXFFDAU/sso/saml/metadata
+#        nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+#        assertionConsumerIndex: 0
+#        metadataTrustCheck: true
+#        showSamlLoginLink: true
+#        linkText: 'Okta Preview 1'
+#        iconUrl: 'http://link.to/icon.jpg'
+#        addShadowUserOnLogin: true
+#        externalGroupsWhitelist:
+#          - admin
+#          - user
+#        emailDomain:
+#          - example.com
+#        attributeMappings:
+#          given_name: firstName
+#          family_name: surname
+#        providerDescription: 'Human readable description of this provider'
+#      okta-local-2:
+#        idpMetadata: |
+#          <?xml version="1.0" encoding="UTF-8"?><md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://www.okta.com/k2lw4l5bPODCMIIDBRYZ"><md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><md:KeyDescriptor use="signing"><ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:X509Data><ds:X509Certificate>MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG
+#          A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+#          MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu
+#          Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC
+#          VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM
+#          BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN
+#          AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU
+#          WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O
+#          Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL
+#          3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk
+#          vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6
+#          GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFb</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://pivotal.oktapreview.com/app/pivotal_pivotalcfstaging_1/k2lw4l5bPODCMIIDBRYZ/sso/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://pivotal.oktapreview.com/app/pivotal_pivotalcfstaging_1/k2lw4l5bPODCMIIDBRYZ/sso/saml"/></md:IDPSSODescriptor></md:EntityDescriptor>
+#        nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+#        assertionConsumerIndex: 0
+#        metadataTrustCheck: true
+#        showSamlLoginLink: true
+#        linkText: 'Okta Preview 2'
+#        addShadowUserOnLogin: true
+#      vsphere.local:
+#        idpMetadata: https://win2012-sso2.localdomain:7444/websso/SAML2/Metadata/vsphere.local
+#        nameID: urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
+#        assertionConsumerIndex: 0
+#        showSamlLoginLink: true
+#        linkText: 'Log in with vCenter SSO'
+#        addShadowUserOnLogin: true
+#        groupMappingMode: EXPLICITLY_MAPPED
+#      openam-local:
+#        idpMetadata: http://localhost:8081/openam/saml2/jsp/exportmetadata.jsp?entityid=http://localhost:8081/openam
+#        nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+#        assertionConsumerIndex: 0
+#        signMetaData: false
+#        signRequest: false
+#        showSamlLoginLink: true
+#        linkText: 'Log in with OpenAM'
+#        addShadowUserOnLogin: true
+#        groupMappingMode: AS_SCOPES
+#END SAML PROVIDERS
+
+  authorize:
+    url: http://localhost:8080/uaa/oauth/authorize
+
+uaa:
+  # The hostname of the UAA that this login server will connect to
+  url: http://localhost:8080/uaa
+  token:
+    url: http://localhost:8080/uaa/oauth/token
+  approvals:
+    url: http://localhost:8080/uaa/approvals
+  login:
+    url: http://localhost:8080/uaa/authenticate
+  limitedFunctionality:
+    enabled: false
+    whitelist:
+      endpoints:
+        - /oauth/authorize/**
+        - /oauth/token/**
+        - /check_token/**
+        - /login/**
+        - /login.do
+        - /logout/**
+        - /logout.do
+        - /saml/**
+        - /autologin/**
+        - /authenticate/**
+        - /idp_discovery/**
+      methods:
+        - GET
+        - HEAD
+        - OPTIONS
+
+issuer:
+  uri: http://localhost:8080/uaa
+
+# Google Analytics
+#analytics:
+#  code: secret_code
+#  domain: example.com
+
+#smtp:
+#  host: localhost
+#  port: 2525
+#  user:
+#  password:
+
+branding:
+  companyName: Predix
+# productLogo: |
+#   iVBORw0KGgoAAAANSUhEUgAAAR8AAAFoCAYAAABucP80AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAALEgAACxIB0t1+/AAAZKVJREFUeNrtnXecXkX1/99n5j5lWyqQ0EJHepdeAgHpRZqKKILg15+9IVIEQhMExYJdsQPSVDoCAkrvPfQAARLSk21PuXPO74+7kc1md/PsZpdNmffr9bz22Vtm7tznzufOnDlzBiKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiUQikUgkEolEIpFIJDIAyFBfQGTpZrvz9yGUqwRTMEDJnhrXy0n6/leR7PikkOfxM+4c6uJEliKi+ES6ZdvzJxDKASsrT8+cxGajNiyYWSNKHY4cgiN7fhZ87H8fJQBVM9px2tpUt3K5rTKHpJDgCp7HT79rqIsXWPY92jEwd/quP0D6/vZiS+7SonQE8b9iZ9U08VtGC3fql2znwF/tiqeJECKWUXM4VBFvHOdmSrGW1qxMU4U5BrjHjwYYRSSkB2tp
+# squareLogo: |
+#   AAABAAQAQEAAAAEAIAAoQAAARgAAACAgAAABACAAKBAAAG5AAAAYGAAAAQAgACgJAACWUAAAEBAAAAEAIAAoBAAAvlkAACgAAABAAAAAgAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAAgwAAAMMAAADuAAAA/wAAAP8AAADuAAAAwwAAAIMAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAawAAAPAAAADdAAAAeQAAAC0AAAAAAAAKAAAAvgAAANoAAAApAAAAXAAAAOUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAADrAAAAXgAAAA
+  footerLegalText:  "Copyright © 2017 General Electric Company. All rights reserved."
+  footerLinks:
+    #Contact Us: mailto:info@predix.io
+    #GE Digital: https://www.gesoftware.com
+    #Legal: https://predix-io.run.asv-sb.ice.predix.io/legal
\ No newline at end of file
diff --git a/checkstyle-config/gog-sun-checks-eclipse.xml b/checkstyle-config/gog-sun-checks-eclipse.xml
new file mode 100644
index 0000000..c6e8fe9
--- /dev/null
+++ b/checkstyle-config/gog-sun-checks-eclipse.xml
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
+
+<!--
+    This configuration file was written by the eclipse-cs plugin configuration editor
+-->
+<!--
+    Checkstyle-Configuration: Guardian's Checkstyle
+    Description: none
+-->
+<module name="Checker">
+  <property name="severity" value="error"/>
+  <module name="TreeWalker">
+    <property name="tabWidth" value="4"/>
+    <module name="FileContentsHolder"/>
+    <module name="JavadocMethod">
+      <property name="severity" value="ignore"/>
+      <property name="suppressLoadErrors" value="true"/>
+      <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+    </module>
+    <module name="JavadocType">
+      <property name="severity" value="ignore"/>
+      <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+    </module>
+    <module name="JavadocVariable">
+      <property name="excludeScope" value="private"/>
+    </module>
+    <module name="JavadocStyle"/>
+    <module name="ConstantName"/>
+    <module name="LocalFinalVariableName"/>
+    <module name="LocalVariableName"/>
+    <module name="MemberName"/>
+    <module name="MethodName"/>
+    <module name="PackageName"/>
+    <module name="ParameterName"/>
+    <module name="StaticVariableName"/>
+    <module name="TypeName"/>
+    <module name="AvoidStarImport"/>
+    <module name="IllegalImport"/>
+    <module name="RedundantImport"/>
+    <module name="UnusedImports"/>
+    <module name="LineLength">
+      <property name="max" value="120"/>
+    </module>
+    <module name="MethodLength"/>
+    <module name="ParameterNumber"/>
+    <module name="EmptyForIteratorPad"/>
+    <module name="GenericWhitespace"/>
+    <module name="MethodParamPad"/>
+    <module name="NoWhitespaceAfter">
+      <property name="tokens" value="BNOT,DEC,DOT,INC,LNOT,UNARY_MINUS,UNARY_PLUS"/>
+    </module>
+    <module name="NoWhitespaceBefore"/>
+    <module name="OperatorWrap"/>
+    <module name="ParenPad"/>
+    <module name="TypecastParenPad"/>
+    <module name="WhitespaceAfter"/>
+    <module name="WhitespaceAround">
+      <property name="tokens" value="ASSIGN,BAND,BAND_ASSIGN,BOR,BOR_ASSIGN,BSR,BSR_ASSIGN,BXOR,BXOR_ASSIGN,COLON,DIV,DIV_ASSIGN,EQUAL,GE,GT,LAND,LCURLY,LE,LITERAL_ASSERT,LITERAL_CATCH,LITERAL_DO,LITERAL_ELSE,LITERAL_FINALLY,LITERAL_FOR,LITERAL_IF,LITERAL_RETURN,LITERAL_SYNCHRONIZED,LITERAL_TRY,LITERAL_WHILE,LOR,LT,MINUS,MINUS_ASSIGN,MOD,MOD_ASSIGN,NOT_EQUAL,PLUS,PLUS_ASSIGN,QUESTION,RCURLY,SL,SLIST,SL_ASSIGN,SR,SR_ASSIGN,STAR,STAR_ASSIGN,LITERAL_ASSERT,TYPE_EXTENSION_AND"/>
+    </module>
+    <module name="ModifierOrder"/>
+    <module name="RedundantModifier"/>
+    <module name="AvoidNestedBlocks"/>
+    <module name="EmptyBlock"/>
+    <module name="LeftCurly"/>
+    <module name="NeedBraces"/>
+    <module name="RightCurly"/>
+    <module name="AvoidInlineConditionals">
+      <property name="severity" value="ignore"/>
+      <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+    </module>
+    <module name="EmptyStatement"/>
+    <module name="EqualsHashCode"/>
+    <module name="HiddenField">
+      <property name="ignoreConstructorParameter" value="true"/>
+      <property name="ignoreSetter" value="true"/>
+    </module>
+    <module name="IllegalInstantiation"/>
+    <module name="InnerAssignment"/>
+    <module name="MagicNumber">
+      <property name="severity" value="ignore"/>
+      <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+    </module>
+    <module name="MissingSwitchDefault"/>
+    <module name="SimplifyBooleanExpression"/>
+    <module name="SimplifyBooleanReturn"/>
+    <module name="DesignForExtension">
+      <property name="severity" value="ignore"/>
+      <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+    </module>
+    <module name="FinalClass"/>
+    <module name="HideUtilityClassConstructor"/>
+    <module name="InterfaceIsType"/>
+    <module name="VisibilityModifier"/>
+    <module name="ArrayTypeStyle"/>
+    <module name="FinalParameters"/>
+    <module name="TodoComment">
+      <property name="severity" value="ignore"/>
+      <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+    </module>
+    <module name="UpperEll"/>
+  </module>
+  <module name="JavadocPackage">
+    <property name="severity" value="ignore"/>
+    <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+  </module>
+  <module name="NewlineAtEndOfFile">
+    <property name="severity" value="ignore"/>
+    <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+  </module>
+  <module name="Translation"/>
+  <module name="FileLength">
+    <property name="max" value="2048"/>
+    <property name="fileExtensions" value="*.java,*.xml"/>
+  </module>
+  <module name="FileTabCharacter">
+    <property name="severity" value="ignore"/>
+    <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+  </module>
+  <module name="RegexpSingleline">
+    <property name="severity" value="ignore"/>
+    <property name="format" value="\s+$"/>
+    <property name="message" value="Line has trailing spaces."/>
+    <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+  </module>
+  <module name="SuppressionCommentFilter"/>
+</module>
diff --git a/commons/.gitignore b/commons/.gitignore
new file mode 100644
index 0000000..5ccf145
--- /dev/null
+++ b/commons/.gitignore
@@ -0,0 +1,14 @@
+/build
+/bin
+/target
+.classpath
+.project
+.settings
+target
+/test-output/
+.DS_Store
+.springBeans
+.idea
+*.ipr
+*.iml
+.checkstyle
diff --git a/commons/pom.xml b/commons/pom.xml
new file mode 100644
index 0000000..9fd4b65
--- /dev/null
+++ b/commons/pom.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+<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/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.ge.predix</groupId>
+        <artifactId>acs</artifactId>
+        <version>5.0.3-SNAPSHOT</version>
+        <relativePath>../</relativePath>
+    </parent>
+
+    <artifactId>acs-commons</artifactId>
+    <name>Predix Access Control Commons</name>
+    <packaging>jar</packaging>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.hibernate</groupId>
+                    <artifactId>hibernate-validator</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>log4j-over-slf4j</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>ch.qos.logback</groupId>
+                    <artifactId>logback-classic</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.6</version>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy-all</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ge.predix</groupId>
+            <artifactId>acs-model</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.fge</groupId>
+            <artifactId>json-schema-validator</artifactId>
+            <version>2.2.6</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.google.code.findbugs</groupId>
+                    <artifactId>jsr305</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.googlecode.json-simple</groupId>
+            <artifactId>json-simple</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/attribute/AttributeType.java b/commons/src/main/java/com/ge/predix/acs/commons/attribute/AttributeType.java
new file mode 100644
index 0000000..27fc98e
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/attribute/AttributeType.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.attribute;
+
+/**
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings("javadoc")
+public final class AttributeType {
+    private final String issuer;
+    private final String name;
+
+    public AttributeType(final String issuer, final String name) {
+        this.issuer = issuer;
+        this.name = name;
+    }
+
+    public String getIssuer() {
+        return this.issuer;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = (prime * result) + ((this.issuer == null) ? 0 : this.issuer.hashCode());
+        result = (prime * result) + ((this.name == null) ? 0 : this.name.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        AttributeType other = (AttributeType) obj;
+        if (this.issuer == null) {
+            if (other.issuer != null) {
+                return false;
+            }
+        } else if (!this.issuer.equals(other.issuer)) {
+            return false;
+        }
+        if (this.name == null) {
+            if (other.name != null) {
+                return false;
+            }
+        } else if (!this.name.equals(other.name)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "AttributeType [issuer=" + this.issuer + ", name=" + this.name + "]";
+    }
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/exception/UntrustedIssuerException.java b/commons/src/main/java/com/ge/predix/acs/commons/exception/UntrustedIssuerException.java
new file mode 100644
index 0000000..94cfeb1
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/exception/UntrustedIssuerException.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.exception;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings("javadoc")
+public class UntrustedIssuerException extends RuntimeException {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    public UntrustedIssuerException(final String message, final Throwable cause, final boolean enableSuppression,
+            final boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+    public UntrustedIssuerException(final Throwable cause) {
+        super(cause);
+    }
+
+    public UntrustedIssuerException() {
+        super();
+    }
+
+    public UntrustedIssuerException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    public UntrustedIssuerException(final String message) {
+        super(message);
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/AbstractHandler.java b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/AbstractHandler.java
new file mode 100644
index 0000000..26a23e8
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/AbstractHandler.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.ge.predix.acs.commons.attribute.AttributeType;
+import com.ge.predix.acs.model.Attribute;
+
+/**
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings({ "nls", "javadoc" })
+public abstract class AbstractHandler {
+    private final String name;
+
+    // This map is used to find all the values of a specific attribute (e.g. all
+    // groups a subject belongs to).
+    // NOTE: The intention is that this is an immutable data structure. Do not
+    // expose methods that modify this.
+    private final Map<AttributeType, Set<Attribute>> attributeTypeMap = new HashMap<>();
+
+    // This map is used to check if an attribute of a specific value exists
+    // (e.g. is a subject a member of a group).
+    // NOTE: The intention is that this is an immutable data structure. Do not
+    // expose methods that modify this.
+    private final Set<Attribute> attributes = new HashSet<>();
+
+    public AbstractHandler(final String name, final Set<Attribute> attributes) {
+        this.name = name;
+        if (null != attributes) {
+            this.attributes.addAll(attributes);
+        }
+        this.attributes.forEach(this::indexAttributeByType);
+    }
+
+    private void indexAttributeByType(final Attribute attribute) {
+        AttributeType attributeType = new AttributeType(attribute.getIssuer(), attribute.getName());
+        Set<Attribute> attributesForType = this.attributeTypeMap.get(attributeType);
+        if (null == attributesForType) {
+            this.attributeTypeMap.put(attributeType, new HashSet<>(Arrays.asList(attribute)));
+        } else {
+            attributesForType.add(attribute);
+        }
+    }
+
+    public Set<String> attributes(final String attributeIssuer, final String attributeName) {
+        Set<Attribute> attributeSet = attributes(new AttributeType(attributeIssuer, attributeName));
+        return attributeSet.stream().map(Attribute::getValue).collect(Collectors.toSet());
+    }
+
+    protected Set<Attribute> attributes(final AttributeType attributeType) {
+        return this.attributeTypeMap.getOrDefault(attributeType, Collections.emptySet());
+    }
+
+    @Override
+    public String toString() {
+        return "AbstractHandler [attributeMap=" + this.attributeTypeMap + "]";
+    }
+
+    public AbstractHandler has(final AttributeType attributeType) throws ConditionAssertionFailedException {
+
+        if (!this.attributeTypeMap.containsKey(attributeType)) {
+            throw new ConditionAssertionFailedException(this.name + " does not have " + attributeType + ".");
+        }
+
+        return this;
+    }
+
+    public AbstractHandler has(final Attribute attribute) throws ConditionAssertionFailedException {
+
+        if (!this.attributes.contains(attribute)) {
+            throw new ConditionAssertionFailedException(this.name + " does not have " + attribute + ".");
+        }
+
+        return this;
+    }
+
+    public AbstractHandlers and(final AbstractHandler handler) {
+
+        return new AbstractHandlers(this, handler);
+    }
+
+    public String getName() {
+        return this.name;
+    }
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/AbstractHandlers.java b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/AbstractHandlers.java
new file mode 100644
index 0000000..7b7a46b
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/AbstractHandlers.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import com.ge.predix.acs.commons.attribute.AttributeType;
+import com.ge.predix.acs.model.Attribute;
+
+public class AbstractHandlers {
+
+    private final List<AbstractHandler> handlers = new ArrayList<>();
+
+    public AbstractHandlers(final AbstractHandler... handlers) {
+        for (AbstractHandler handler : handlers) {
+            this.handlers.add(handler);
+        }
+    }
+
+    public AbstractHandlers add(final AbstractHandler handler) {
+        this.handlers.add(handler);
+        return this;
+    }
+
+    public AbstractHandlers haveSame(final String criteriaAttributeIssuer, final String criteriaAttributeName) {
+
+        if (this.handlers.isEmpty()) {
+            return this;
+        }
+
+        AttributeType criteriaAttributeType = new AttributeType(criteriaAttributeIssuer, criteriaAttributeName);
+        Iterator<AbstractHandler> iter = this.handlers.iterator();
+        AbstractHandler current = iter.next();
+        // If this fails it will throw an exception
+        current.has(criteriaAttributeType);
+
+        StringBuilder handlerNames = new StringBuilder();
+        handlerNames.append(current.getName());
+        while (iter.hasNext()) {
+            AbstractHandler next = iter.next();
+            handlerNames.append(" and " + next.getName());
+            Set<Attribute> intersection = new HashSet<>(current.attributes(criteriaAttributeType));
+            intersection.retainAll(next.attributes(criteriaAttributeType));
+            if (intersection.isEmpty()) {
+                throw new ConditionAssertionFailedException(
+                        "No intersection exists between " + handlerNames.toString() + " on " + criteriaAttributeType
+                                + ".");
+            }
+            current = next;
+        }
+
+        return this;
+    }
+
+    /**
+     * Always returns true because if a condition was not successfully met one of the matchers above would throw an
+     * exception.
+     */
+    public boolean result() {
+        return true;
+    }
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/ConditionAssertionFailedException.java b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/ConditionAssertionFailedException.java
new file mode 100644
index 0000000..1b42e07
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/ConditionAssertionFailedException.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition;
+
+public class ConditionAssertionFailedException extends RuntimeException {
+
+    /**
+     * Generated serialization id.
+     */
+    private static final long serialVersionUID = -2210971706499534221L;
+
+    public ConditionAssertionFailedException(final String message) {
+        super(message);
+    }
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/ConditionParsingException.java b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/ConditionParsingException.java
new file mode 100644
index 0000000..ba46bbc
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/ConditionParsingException.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition;
+
+/**
+ * A condition shell may throw this exception when parsing a condition script if it is invalid or has other errors
+ * that result in a script compilation failure.
+ *
+ * @author acs-engineers@ge.com
+ */
+public class ConditionParsingException extends Exception {
+    /**
+     * Serialization id.
+     */
+    private static final long serialVersionUID = 2112986552966674621L;
+
+    private final String failedScript;
+
+    public ConditionParsingException(final String message, final String failedScript, final Throwable cause) {
+        super(message, cause);
+        this.failedScript = failedScript;
+    }
+
+    public String getFailedScript() {
+        return this.failedScript;
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/ConditionScript.java b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/ConditionScript.java
new file mode 100644
index 0000000..28acf7d
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/ConditionScript.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition;
+
+import java.util.Map;
+
+/**
+ * Represents a compiled policy condition script.
+ *
+ * @author acs-engineers@ge.com
+ */
+@FunctionalInterface
+public interface ConditionScript {
+    /**
+     * Executes the policy condition script.
+     *
+     * @param boundVariables
+     *            the bound variables used at runtime.
+     * @return true or false
+     */
+    boolean execute(Map<String, Object> boundVariables);
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/ConditionShell.java b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/ConditionShell.java
new file mode 100644
index 0000000..e8c1f6e
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/ConditionShell.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition;
+
+/**
+ * Represents a shell capable of compiling a policy condition.
+ *
+ * @author acs-engineers@ge.com
+ */
+@FunctionalInterface
+public interface ConditionShell {
+
+    /**
+     * Validates the script & generates condition script object.
+     *
+     * @param script
+     *            the policy condition string
+     * @return a Script object instance capable of executing the policy condition.
+     * @throws ConditionParsingException
+     *             on validation error
+     */
+    ConditionScript parse(String script) throws ConditionParsingException;
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/ResourceHandler.java b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/ResourceHandler.java
new file mode 100644
index 0000000..6229fb5
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/ResourceHandler.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.util.StringUtils;
+import org.springframework.web.util.UriTemplate;
+
+import com.ge.predix.acs.model.Attribute;
+
+/**
+ * DTO to represent resource in the groovy condition.
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings("nls")
+public class ResourceHandler extends AbstractHandler {
+
+    private final String resourceURI;
+    private final String resourceURITemplate;
+
+    /**
+     * Creates a resource handler.
+     *
+     * @param attributeSet
+     *            attributes
+     * @param resourceURI
+     *            resourceURI of the requested resource
+     * @param resourceURLTemplate
+     *            resource URI template in the policy
+     */
+    public ResourceHandler(final Set<Attribute> attributeSet, final String resourceURI,
+            final String resourceURLTemplate) {
+        super("Resource", attributeSet);
+        this.resourceURI = resourceURI;
+        this.resourceURITemplate = resourceURLTemplate;
+    }
+
+    /**
+     * @param pathVariable
+     *            name of path parameter in the URL
+     * @return path parameter value
+     */
+    public String uriVariable(final String pathVariable) {
+        if (StringUtils.isEmpty(this.resourceURITemplate) || StringUtils.isEmpty(this.resourceURI)
+                || StringUtils.isEmpty(pathVariable)) {
+            return "";
+        }
+        UriTemplate template = new UriTemplate(this.resourceURITemplate);
+        Map<String, String> match = template.match(this.resourceURI);
+        String pathVariableValue = match.get(pathVariable);
+        return pathVariableValue != null ? pathVariableValue : "";
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/SubjectHandler.java b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/SubjectHandler.java
new file mode 100644
index 0000000..5e149e9
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/SubjectHandler.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition;
+
+import java.util.Set;
+
+import com.ge.predix.acs.model.Attribute;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+public class SubjectHandler extends AbstractHandler {
+
+    /**
+     * @param attributeSet
+     *            Attributes to be bound for this subject.
+     */
+    public SubjectHandler(final Set<Attribute> attributeSet) {
+        super("Subject", attributeSet);
+    }
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/groovy/AttributeMatcher.java b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/groovy/AttributeMatcher.java
new file mode 100644
index 0000000..4703fa0
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/groovy/AttributeMatcher.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition.groovy;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import com.ge.predix.acs.commons.policy.condition.ResourceHandler;
+import com.ge.predix.acs.commons.policy.condition.SubjectHandler;
+
+/**
+ * Support utility match methods.
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings("javadoc")
+public class AttributeMatcher {
+
+    private ResourceHandler resourceHandler;
+    private SubjectHandler subjectHandler;
+
+    public ResourceHandler resource() {
+        return this.resourceHandler;
+    }
+
+    public void setResourceHandler(final ResourceHandler resourceHandler) {
+        this.resourceHandler = resourceHandler;
+    }
+
+    public SubjectHandler subject() {
+        return this.subjectHandler;
+    }
+
+    public void setSubjectHandler(final SubjectHandler subjectHandler) {
+        this.subjectHandler = subjectHandler;
+    }
+
+    /**
+     * @return true if the intersection of sourceAttributes and targetAttributes is non-empty
+     */
+    public boolean any(final Set<String> sourceAttributes, final Set<String> targetAttributes) {
+        if (null == sourceAttributes || null == targetAttributes) {
+            return false;
+        }
+
+        // copy source set
+        Set<String> intersection = new HashSet<>(sourceAttributes);
+        // create intersection of source and target
+        intersection.retainAll(targetAttributes);
+
+        return !intersection.isEmpty();
+    }
+
+    /**
+     * Returns true if attribute value is present in the source attribute values.
+     *
+     * @param sourceAttributes
+     * @param attributeValue
+     * @return
+     */
+    public boolean single(final Set<String> sourceAttributes, final String attributeValue) {
+        boolean match = false;
+        if (sourceAttributes != null && !sourceAttributes.isEmpty()) {
+            match = sourceAttributes.contains(attributeValue);
+        }
+        return match;
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionCache.java b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionCache.java
new file mode 100644
index 0000000..8b8a26f
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionCache.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition.groovy;
+
+import java.util.Map;
+
+import org.springframework.stereotype.Component;
+import org.springframework.util.ConcurrentReferenceHashMap;
+
+import com.ge.predix.acs.commons.policy.condition.ConditionScript;
+
+@Component
+public class GroovyConditionCache {
+
+    private final Map<String, ConditionScript> cache = new ConcurrentReferenceHashMap<>();
+
+    public ConditionScript get(final String script) {
+        return this.cache.get(script);
+    }
+
+    public void put(final String script, final ConditionScript compiledScript) {
+        this.cache.put(script, compiledScript);
+    }
+
+    public void remove(final String script) {
+        this.cache.remove(script);
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionScript.java b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionScript.java
new file mode 100644
index 0000000..d10d4bf
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionScript.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition.groovy;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ge.predix.acs.commons.policy.condition.ConditionScript;
+
+import groovy.lang.Binding;
+import groovy.lang.Script;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings("nls")
+public class GroovyConditionScript implements ConditionScript {
+    private static final Logger LOGGER = LoggerFactory.getLogger(GroovyConditionScript.class);
+
+    private final Script script;
+
+    /**
+     * @param script
+     *            the script object.
+     */
+    public GroovyConditionScript(final Script script) {
+        super();
+        this.script = script;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see com.ge.predix.acs.commons.conditions.ConditionScript#execute(java.util .Map)
+     */
+    @Override
+    public boolean execute(final Map<String, Object> boundVariables) {
+        if (LOGGER.isDebugEnabled()) {
+            StringBuilder msgBuilder = new StringBuilder();
+            msgBuilder.append("The script is bound to the following variables:\n");
+            for (Entry<String, Object> entry : boundVariables.entrySet()) {
+                msgBuilder.append("* ").append(entry.getKey()).append(":").append(entry.getValue()).append("\n");
+            }
+            LOGGER.debug(msgBuilder.toString());
+        }
+
+        Binding binding = new Binding(boundVariables);
+        this.script.setBinding(binding);
+        return (boolean) this.script.run();
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionShell.java b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionShell.java
new file mode 100644
index 0000000..42db027
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionShell.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition.groovy;
+
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.CompilerConfiguration;
+import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer;
+import org.codehaus.groovy.control.customizers.ImportCustomizer;
+import org.codehaus.groovy.control.customizers.SecureASTCustomizer;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.commons.policy.condition.AbstractHandler;
+import com.ge.predix.acs.commons.policy.condition.ConditionParsingException;
+import com.ge.predix.acs.commons.policy.condition.ConditionScript;
+import com.ge.predix.acs.commons.policy.condition.ConditionShell;
+import com.ge.predix.acs.commons.policy.condition.ResourceHandler;
+import com.ge.predix.acs.commons.policy.condition.SubjectHandler;
+
+import groovy.lang.GroovyShell;
+import groovy.lang.Script;
+import groovy.transform.CompileStatic;
+
+/**
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings("nls")
+@Component
+public class GroovyConditionShell implements ConditionShell {
+
+    private final GroovyShell shell;
+
+    public GroovyConditionShell() {
+
+        SecureASTCustomizer secureASTCustomizer = createSecureASTCustomizer();
+        ImportCustomizer importCustomizer = createImportCustomizer();
+        ASTTransformationCustomizer astTransformationCustomizer = createASTTransformationCustomizer();
+
+        CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
+        compilerConfiguration.addCompilationCustomizers(secureASTCustomizer);
+        compilerConfiguration.addCompilationCustomizers(importCustomizer);
+        compilerConfiguration.addCompilationCustomizers(astTransformationCustomizer);
+
+        this.shell = new GroovyShell(compilerConfiguration);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see com.ge.predix.acs.commons.conditions.ConditionShell#parse(java.lang. String )
+     */
+    @Override
+    public ConditionScript parse(final String script) throws ConditionParsingException {
+        if (StringUtils.isEmpty(script)) {
+            throw new IllegalArgumentException("Script is null or empty.");
+        }
+
+        try {
+            Script groovyScript = this.shell.parse(script);
+            return new GroovyConditionScript(groovyScript);
+        } catch (CompilationFailedException e) {
+            throw new ConditionParsingException("Failed to validate the condition script.", script, e);
+        }
+    }
+
+    private static ImportCustomizer createImportCustomizer() {
+        ImportCustomizer importCustomizer = new ImportCustomizer();
+        importCustomizer.addImports(ResourceHandler.class.getCanonicalName());
+        importCustomizer.addImports(SubjectHandler.class.getCanonicalName());
+        importCustomizer.addImports(AbstractHandler.class.getCanonicalName());
+        return importCustomizer;
+    }
+
+    private static SecureASTCustomizer createSecureASTCustomizer() {
+        SecureASTCustomizer secureASTCustomizer = new SecureASTCustomizer();
+        // Allow closures.
+        secureASTCustomizer.setClosuresAllowed(true);
+        // Disallow method definition.
+        secureASTCustomizer.setMethodDefinitionAllowed(false);
+        // Disallow all imports by setting a blank whitelist.
+        secureASTCustomizer.setImportsWhitelist(Arrays.asList(new String[] {}));
+        // Disallow star imports by setting a blank whitelist.
+        secureASTCustomizer.setStarImportsWhitelist(Arrays.asList(
+                new String[] { "org.crsh.command.*", "org.crsh.cli.*", "org.crsh.groovy.*",
+                        "com.ge.predix.acs.commons.policy.condition.*" }));
+        // Set white list for constant type classes.
+        secureASTCustomizer.setConstantTypesClassesWhiteList(Arrays.asList(
+                new Class[] { Boolean.class, boolean.class, Collection.class, Double.class, double.class, Float.class,
+                        float.class, Integer.class, int.class, Long.class, long.class, Object.class, String.class }));
+        secureASTCustomizer.setReceiversClassesWhiteList(Arrays.asList(
+                new Class[] { Boolean.class, Collection.class, Integer.class, Iterable.class, Object.class, Set.class,
+                        String.class }));
+        return secureASTCustomizer;
+    }
+
+    private ASTTransformationCustomizer createASTTransformationCustomizer() {
+
+        return new ASTTransformationCustomizer(singletonMap("extensions",
+                singletonList("com.ge.predix.acs.commons.policy.condition.groovy.GroovySecureExtension")),
+                CompileStatic.class);
+    }
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovySecureExtension.java b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovySecureExtension.java
new file mode 100644
index 0000000..276fc84
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovySecureExtension.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition.groovy;
+
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.transform.stc.AbstractTypeCheckingExtension;
+import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor;
+
+import com.ge.predix.acs.commons.policy.condition.ResourceHandler;
+import com.ge.predix.acs.commons.policy.condition.SubjectHandler;
+
+public class GroovySecureExtension extends AbstractTypeCheckingExtension {
+
+    public GroovySecureExtension(final StaticTypeCheckingVisitor typeCheckingVisitor) {
+        super(typeCheckingVisitor);
+    }
+
+    @Override
+    public void onMethodSelection(final Expression expression, final MethodNode target) {
+        // First the white list.
+        if ((!"com.ge.predix.acs.commons.policy.condition.AbstractHandler".equals(target.getDeclaringClass().getName()))
+                && (!"com.ge.predix.acs.commons.policy.condition.AbstractHandlers"
+                .equals(target.getDeclaringClass().getName()))
+                && (!"com.ge.predix.acs.commons.policy.condition.ResourceHandler"
+                .equals(target.getDeclaringClass().getName()))
+                && (!"com.ge.predix.acs.commons.policy.condition.SubjectHandler"
+                .equals(target.getDeclaringClass().getName()))
+                && (!"com.ge.predix.acs.commons.policy.condition.groovy.AttributeMatcher"
+                .equals(target.getDeclaringClass().getName())) && (!"java.lang.Boolean"
+                .equals(target.getDeclaringClass().getName())) && (!"java.lang.Integer"
+                .equals(target.getDeclaringClass().getName())) && (!"java.lang.Iterable"
+                .equals(target.getDeclaringClass().getName())) && (!"java.lang.Object"
+                .equals(target.getDeclaringClass().getName()))
+                // This means we allow collections of type Object.
+                && (!"[Ljava.lang.Object;".equals(target.getDeclaringClass().getName())) && (!"java.lang.String"
+                .equals(target.getDeclaringClass().getName())) && (!"java.util.Collection"
+                .equals(target.getDeclaringClass().getName())) && (!"java.util.Set"
+                .equals(target.getDeclaringClass().getName()))) {
+            addStaticTypeError("Method call for '" + target.getDeclaringClass().getName() + "' class is not allowed!",
+                    expression);
+        }
+
+        // Then the black list.
+        if ("java.lang.System".equals(target.getDeclaringClass().getName())) {
+            addStaticTypeError("Method call for 'java.lang.System' class is not allowed!", expression);
+        }
+        if ("groovy.util.Eval".equals(target.getDeclaringClass().getName())) {
+            addStaticTypeError("Method call for 'groovy.util.Eval' class is not allowed!", expression);
+        }
+        if ("java.io".equals(target.getDeclaringClass().getName())) {
+            addStaticTypeError("Method call for 'java.io' package is not allowed!", expression);
+        }
+        if ("execute".equals(target.getName())) {
+            addStaticTypeError("Method call 'execute' is not allowed!", expression);
+        }
+    }
+
+    @Override
+    public boolean handleUnresolvedVariableExpression(final VariableExpression vexp) {
+
+        if ("resource".equals(vexp.getName())) {
+            makeDynamic(vexp, ClassHelper.makeCached(ResourceHandler.class));
+            setHandled(true);
+            return true;
+        }
+        if ("subject".equals(vexp.getName())) {
+            makeDynamic(vexp, ClassHelper.makeCached(SubjectHandler.class));
+            setHandled(true);
+            return true;
+        }
+        if ("match".equals(vexp.getName())) {
+            makeDynamic(vexp, ClassHelper.makeCached(AttributeMatcher.class));
+            setHandled(true);
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/ACSWebConstants.java b/commons/src/main/java/com/ge/predix/acs/commons/web/ACSWebConstants.java
new file mode 100644
index 0000000..2eb23c1
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/ACSWebConstants.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+/**
+ * @author acs-engineers@ge.com
+ */
+public final class ACSWebConstants {
+
+    public static final String APP_ROOT_PATH = "/acs";
+
+    private ACSWebConstants() {
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/AcsApiUriTemplates.java b/commons/src/main/java/com/ge/predix/acs/commons/web/AcsApiUriTemplates.java
new file mode 100644
index 0000000..7878db7
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/AcsApiUriTemplates.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+/**
+ * Constants that define the set of URI Templates of the resources exposed by the ACS.
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings({ "nls" })
+public final class AcsApiUriTemplates {
+
+    public static final String V1 = "/v1";
+
+    public static final String POLICY_SETS_URL = "/policy-set";
+
+    public static final String POLICY_EVALUATION_URL = "/policy-evaluation";
+
+    public static final String POLICY_SET_URL = POLICY_SETS_URL + "/{policySetId}";
+
+    public static final String MANAGED_RESOURCES_URL = "/resource";
+
+    public static final String MANAGED_RESOURCE_URL = MANAGED_RESOURCES_URL + "/{resourceIdentifier}";
+
+    public static final String SUBJECTS_URL = "/subject";
+
+    public static final String SUBJECT_URL = SUBJECTS_URL + "/{subjectIdentifier:.+}";
+
+    public static final String MONITORING_URL = "/monitoring";
+
+    public static final String HEARTBEAT_URL = MONITORING_URL + "/heartbeat";
+
+    public static final String ZONE_URL = "/zone/{zoneName}";
+
+    public static final String CONNECTOR_URL = "/connector";
+
+    public static final String RESOURCE_CONNECTOR_URL = CONNECTOR_URL + "/resource";
+
+    public static final String SUBJECT_CONNECTOR_URL = CONNECTOR_URL + "/subject";
+
+    public static final String HEALTH_URL = "/health";
+
+    private AcsApiUriTemplates() {
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/BaseRestApi.java b/commons/src/main/java/com/ge/predix/acs/commons/web/BaseRestApi.java
new file mode 100644
index 0000000..d3f3a5a
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/BaseRestApi.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import javax.annotation.Resource;
+
+import org.springframework.core.env.Environment;
+
+/**
+ * Base class for REST Apis that provides common error handler.
+ *
+ * @author acs-engineers@ge.com
+ *
+ */
+public class BaseRestApi {
+    public static final String PARENTS_ATTR_NOT_SUPPORTED_MSG = "The parents attribute isn't supported yet";
+
+    @Resource
+    private Environment environment;
+
+    public Environment getEnvironment() {
+        return this.environment;
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/BaseRestApiControllerAdvice.java b/commons/src/main/java/com/ge/predix/acs/commons/web/BaseRestApiControllerAdvice.java
new file mode 100644
index 0000000..199a582
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/BaseRestApiControllerAdvice.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import org.slf4j.Logger;
+
+
+public abstract class BaseRestApiControllerAdvice {
+
+    private final Logger logger;
+
+    public BaseRestApiControllerAdvice(final Logger log) {
+        this.logger = log;
+    }
+
+    public RestApiErrorResponse handleException(
+            final Exception e, final String message) {
+        logger.error(e.getMessage(), e);
+        RestApiErrorResponse restApiErrorResponse = new RestApiErrorResponse();
+        if (!message.isEmpty()) {
+            restApiErrorResponse.getErrorDetails().setErrorMessage(message);
+        }
+        return restApiErrorResponse;
+    }
+
+
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/HttpMediaTypeNotAcceptableExceptionHandler.java b/commons/src/main/java/com/ge/predix/acs/commons/web/HttpMediaTypeNotAcceptableExceptionHandler.java
new file mode 100644
index 0000000..e861281
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/HttpMediaTypeNotAcceptableExceptionHandler.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.HttpMediaTypeNotAcceptableException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ControllerAdvice
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class HttpMediaTypeNotAcceptableExceptionHandler extends BaseRestApiControllerAdvice {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpMediaTypeNotAcceptableExceptionHandler.class);
+
+    public HttpMediaTypeNotAcceptableExceptionHandler() {
+        super(LOGGER);
+    }
+
+    @ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
+    @ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
+    @ResponseBody
+    public RestApiErrorResponse handleException(final HttpMediaTypeNotAcceptableException e) {
+        return super.handleException(e, HttpStatus.NOT_ACCEPTABLE.getReasonPhrase());
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/HttpMediaTypeNotSupportedExceptionHandler.java b/commons/src/main/java/com/ge/predix/acs/commons/web/HttpMediaTypeNotSupportedExceptionHandler.java
new file mode 100644
index 0000000..c986273
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/HttpMediaTypeNotSupportedExceptionHandler.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.HttpMediaTypeNotSupportedException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ControllerAdvice
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class HttpMediaTypeNotSupportedExceptionHandler extends BaseRestApiControllerAdvice {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpMediaTypeNotSupportedExceptionHandler.class);
+
+    public HttpMediaTypeNotSupportedExceptionHandler() {
+        super(LOGGER);
+    }
+
+    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
+    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
+    @ResponseBody
+    public RestApiErrorResponse handleException(final HttpMediaTypeNotSupportedException e) {
+        return super.handleException(e,  HttpStatus.UNSUPPORTED_MEDIA_TYPE.getReasonPhrase());
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/HttpMessageNotReadableExceptionHandler.java b/commons/src/main/java/com/ge/predix/acs/commons/web/HttpMessageNotReadableExceptionHandler.java
new file mode 100644
index 0000000..9de3a0f
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/HttpMessageNotReadableExceptionHandler.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ControllerAdvice
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class HttpMessageNotReadableExceptionHandler extends BaseRestApiControllerAdvice {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpMessageNotReadableExceptionHandler.class);
+
+    public HttpMessageNotReadableExceptionHandler() {
+        super(LOGGER);
+    }
+
+    @ExceptionHandler(HttpMessageNotReadableException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ResponseBody
+    public RestApiErrorResponse handleException(final HttpMessageNotReadableException e) {
+        return super.handleException(e,  "Malformed JSON syntax. " + e.getLocalizedMessage());
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/HttpRequestMethodNotSupportedExceptionHandler.java b/commons/src/main/java/com/ge/predix/acs/commons/web/HttpRequestMethodNotSupportedExceptionHandler.java
new file mode 100644
index 0000000..62a3ab0
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/HttpRequestMethodNotSupportedExceptionHandler.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ControllerAdvice
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class HttpRequestMethodNotSupportedExceptionHandler extends BaseRestApiControllerAdvice {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpRequestMethodNotSupportedExceptionHandler.class);
+
+    public HttpRequestMethodNotSupportedExceptionHandler() {
+        super(LOGGER);
+    }
+
+    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
+    @ResponseBody
+    public RestApiErrorResponse handleException(final HttpRequestMethodNotSupportedException e) {
+        return super.handleException(e, HttpStatus.METHOD_NOT_ALLOWED.getReasonPhrase());
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/IllegalArgumentExceptionHandler.java b/commons/src/main/java/com/ge/predix/acs/commons/web/IllegalArgumentExceptionHandler.java
new file mode 100644
index 0000000..329f197
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/IllegalArgumentExceptionHandler.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ControllerAdvice
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class IllegalArgumentExceptionHandler extends BaseRestApiControllerAdvice {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(IllegalArgumentExceptionHandler.class);
+
+    public IllegalArgumentExceptionHandler() {
+        super(LOGGER);
+    }
+
+    @ExceptionHandler(IllegalArgumentException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ResponseBody
+    public RestApiErrorResponse handleException(final IllegalArgumentException e) {
+       return super.handleException(e,  e.getMessage());
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/ResponseEntityBuilder.java b/commons/src/main/java/com/ge/predix/acs/commons/web/ResponseEntityBuilder.java
new file mode 100644
index 0000000..3814b3d
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/ResponseEntityBuilder.java
@@ -0,0 +1,131 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import java.net.URI;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+/**
+ * Convenient ResponseEntity Builder methods to minimize boiler plate code.
+ *
+ * @author acs-engineers@ge.com
+ */
+public final class ResponseEntityBuilder {
+
+    private ResponseEntityBuilder() {
+    }
+
+    /**
+     * Creates a typed ResponseEntity with HTTP status code 201 with no location.
+     *
+     * @return The corresponding ResponseEntity
+     */
+    public static <T> ResponseEntity<T> created() {
+        return created(null, false);
+    }
+
+    /**
+     * Creates a typed ResponseEntity with HTTP status code 201 with a given location.
+     *
+     * @param location The location of the created resource
+     * @return The corresponding ResponseEntity
+     */
+    public static <T> ResponseEntity<T> created(final String location) {
+        // Resource creation by default return 201 "created"
+        return created(location, false);
+    }
+
+    /**
+     * Creates a typed ResponseEntity with HTTP status code 201/204 with a given location.
+     *
+     * @param location  The location of the created resource
+     * @param noContent false means updated resource which returns 204, true means created resource which returns 201
+     * @return The corresponding ResponseEntity
+     */
+    public static <T> ResponseEntity<T> created(final String location, final boolean noContent) {
+        HttpStatus status = noContent ? HttpStatus.NO_CONTENT : HttpStatus.CREATED;
+
+        if (location != null) {
+
+            HttpHeaders headers = new HttpHeaders();
+            headers.setLocation(URI.create(location));
+            return new ResponseEntity<>(headers, status);
+        }
+        return new ResponseEntity<>(status);
+    }
+
+    public static <T> ResponseEntity<T> created(final boolean noContent, final String uriTemplate,
+            final String... keyValues) {
+        URI resourceUri = UriTemplateUtils.expand(uriTemplate, keyValues);
+        return created(resourceUri.getPath(), noContent);
+    }
+
+    /**
+     * Creates a typed ResponseEntity with HTTP status code 200 with no response payload.
+     *
+     * @return The corresponding ResponseEntity
+     */
+    public static <T> ResponseEntity<T> ok() {
+        return ok(null);
+    }
+
+    /**
+     * Creates a typed ResponseEntity with HTTP status code 200 with a given response payload.
+     *
+     * @param response The response payload
+     * @return The corresponding ResponseEntity
+     */
+    public static <T> ResponseEntity<T> ok(final T response) {
+        if (response != null) {
+            return new ResponseEntity<>(response, HttpStatus.OK);
+        }
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * Creates a typed ResponseEntity with HTTP status code 204 with no response payload.
+     *
+     * @return The corresponding ResponseEntity
+     */
+    public static ResponseEntity<Void> noContent() {
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    /**
+     * Creates a typed ResponseEntity with HTTP status code 200 with no response payload.
+     *
+     * @return The corresponding ResponseEntity
+     */
+    public static <T> ResponseEntity<T> notFound() {
+        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+    }
+
+    /**
+     * Response entity usually to reflect semantically invalid input data. Creates a typed ResponseEntity with HTTP
+     * status code 422 with no response payload.
+     *
+     * @return The corresponding ResponseEntity
+     */
+    public static ResponseEntity<Void> unprocessable() {
+        return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY);
+    }
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/RestApiErrorResponse.java b/commons/src/main/java/com/ge/predix/acs/commons/web/RestApiErrorResponse.java
new file mode 100644
index 0000000..a87f7b7
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/RestApiErrorResponse.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Used to provide error payload with code and message.
+ *
+ * @author acs-engineers@ge.com
+ *
+ */
+@SuppressWarnings({ "nls", "javadoc" })
+public class RestApiErrorResponse {
+
+
+
+    private ErrorDetails errorDetails = new ErrorDetails();
+
+    public void setErrorDetails(final ErrorDetails errorDetails) {
+        this.errorDetails = errorDetails;
+    }
+    @JsonProperty("ErrorDetails")
+    public ErrorDetails getErrorDetails() {
+        return this.errorDetails;
+    }
+
+    class ErrorDetails {
+
+        private String errorCode = "FAILED";
+        private String errorMessage = "Operation Failed";
+
+        public String getErrorCode() {
+            return this.errorCode;
+        }
+
+        public void setErrorCode(final String errorCode) {
+            this.errorCode = errorCode;
+        }
+
+        public String getErrorMessage() {
+            return this.errorMessage;
+        }
+
+        public void setErrorMessage(final String errorMessage) {
+            this.errorMessage = errorMessage;
+        }
+
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/RestApiException.java b/commons/src/main/java/com/ge/predix/acs/commons/web/RestApiException.java
new file mode 100644
index 0000000..a937e2a
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/RestApiException.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import org.springframework.http.HttpStatus;
+
+/**
+ * Controllers implementing the restful api of the acs, should throw this kind of exception which is handled by the
+ * error handler to generate a json error response payload.
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings({ "javadoc", "nls" })
+public class RestApiException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+    private static final String FAILURE = "FAILURE";
+
+    private final HttpStatus httpStatusCode;
+    private final String appErrorCode;
+
+    public RestApiException() {
+        super();
+        this.httpStatusCode = HttpStatus.INTERNAL_SERVER_ERROR;
+        this.appErrorCode = FAILURE;
+    }
+
+    public RestApiException(final String message) {
+        super(message);
+        this.httpStatusCode = HttpStatus.INTERNAL_SERVER_ERROR;
+        this.appErrorCode = FAILURE;
+    }
+
+    public RestApiException(final Throwable cause) {
+        super(cause);
+        this.httpStatusCode = HttpStatus.INTERNAL_SERVER_ERROR;
+        this.appErrorCode = FAILURE;
+    }
+
+    public RestApiException(final String message, final Throwable cause) {
+        super(message, cause);
+        this.httpStatusCode = HttpStatus.INTERNAL_SERVER_ERROR;
+        this.appErrorCode = FAILURE;
+    }
+
+    public RestApiException(final String message, final Throwable cause, final boolean enableSuppression,
+            final boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+        this.httpStatusCode = HttpStatus.INTERNAL_SERVER_ERROR;
+        this.appErrorCode = FAILURE;
+    }
+
+    public RestApiException(final HttpStatus httpStatusCode, final String message) {
+        super(message);
+        this.httpStatusCode = httpStatusCode;
+        this.appErrorCode = FAILURE;
+    }
+
+    public RestApiException(final HttpStatus httpStatusCode, final String appErrorCode, final String message) {
+        super(message);
+        this.httpStatusCode = httpStatusCode;
+        this.appErrorCode = appErrorCode;
+    }
+
+    public RestApiException(final HttpStatus httpStatusCode, final Throwable cause) {
+        super(cause);
+        this.httpStatusCode = httpStatusCode;
+        this.appErrorCode = FAILURE;
+    }
+
+    public RestApiException(final HttpStatus httpStatusCode, final String message, final Throwable cause) {
+        super(message, cause);
+        this.httpStatusCode = httpStatusCode;
+        this.appErrorCode = FAILURE;
+    }
+
+    public RestApiException(final HttpStatus httpStatusCode, final String appErrorCode, final String message,
+            final Throwable cause) {
+        super(message, cause);
+        this.httpStatusCode = httpStatusCode;
+        this.appErrorCode = appErrorCode;
+    }
+
+    public HttpStatus getHttpStatusCode() {
+        return this.httpStatusCode;
+    }
+
+    /**
+     * @return the appErrorCode
+     */
+    public String getAppErrorCode() {
+        return this.appErrorCode;
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/RestApiExceptionHandler.java b/commons/src/main/java/com/ge/predix/acs/commons/web/RestApiExceptionHandler.java
new file mode 100644
index 0000000..47e86e2
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/RestApiExceptionHandler.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+@ControllerAdvice
+public class RestApiExceptionHandler extends BaseRestApiControllerAdvice {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(RestApiExceptionHandler.class);
+
+    public RestApiExceptionHandler() {
+        super(LOGGER);
+    }
+
+    @ExceptionHandler(RestApiException.class)
+    public ResponseEntity<RestApiErrorResponse> handleException(final RestApiException e) {
+        LOGGER.error(e.getMessage(), e);
+        RestApiErrorResponse restApiErrorResponse = new RestApiErrorResponse();
+        restApiErrorResponse.getErrorDetails().setErrorMessage(e.getMessage());
+        restApiErrorResponse.getErrorDetails().setErrorCode(e.getAppErrorCode());
+        return ResponseEntity.status(e.getHttpStatusCode().value()).contentType(MediaType.APPLICATION_JSON)
+                .body(restApiErrorResponse);
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/SecurityExceptionHandler.java b/commons/src/main/java/com/ge/predix/acs/commons/web/SecurityExceptionHandler.java
new file mode 100644
index 0000000..211dce7
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/SecurityExceptionHandler.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ControllerAdvice
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class SecurityExceptionHandler extends BaseRestApiControllerAdvice {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SecurityExceptionHandler.class);
+
+    public SecurityExceptionHandler() {
+        super(LOGGER);
+    }
+
+    @ExceptionHandler(SecurityException.class)
+    @ResponseStatus(HttpStatus.UNAUTHORIZED)
+    @ResponseBody
+    public RestApiErrorResponse handleException(final SecurityException e) {
+        return super.handleException(e,  e.getMessage());
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/UntrustedIssuerExceptionHandler.java b/commons/src/main/java/com/ge/predix/acs/commons/web/UntrustedIssuerExceptionHandler.java
new file mode 100644
index 0000000..f93c5b6
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/UntrustedIssuerExceptionHandler.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+import com.ge.predix.acs.commons.exception.UntrustedIssuerException;
+
+@ControllerAdvice
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class UntrustedIssuerExceptionHandler extends BaseRestApiControllerAdvice {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(UntrustedIssuerExceptionHandler.class);
+
+    public UntrustedIssuerExceptionHandler() {
+        super(LOGGER);
+    }
+
+    @ExceptionHandler(UntrustedIssuerException.class)
+    @ResponseStatus(HttpStatus.UNAUTHORIZED)
+    @ResponseBody
+    public RestApiErrorResponse handleException(final UntrustedIssuerException e) {
+        return super.handleException(e,  e.getMessage());
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/UriTemplateUtils.java b/commons/src/main/java/com/ge/predix/acs/commons/web/UriTemplateUtils.java
new file mode 100644
index 0000000..8ceb783
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/UriTemplateUtils.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.web.util.UriTemplate;
+
+/**
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings("nls")
+public final class UriTemplateUtils {
+
+    private UriTemplateUtils() {
+        // Prevents instantiation.
+    }
+
+    /**
+     * Generates an instance of the URI according to the template given by uriTemplate, by expanding the variables
+     * with the values provided by keyValues.
+     *
+     * @param uriTemplate
+     *            The URI template
+     * @param keyValues
+     *            Dynamic list of string of the form "key:value"
+     * @return The corresponding URI instance
+     */
+    public static URI expand(final String uriTemplate, final String... keyValues) {
+
+        UriTemplate template = new UriTemplate(uriTemplate);
+        Map<String, String> uriVariables = new HashMap<>();
+
+        for (String kv : keyValues) {
+            String[] keyValue = kv.split(":");
+            uriVariables.put(keyValue[0], keyValue[1]);
+        }
+        return template.expand(uriVariables);
+    }
+
+    public static boolean isCanonicalMatch(final String uriTemplateDef, final String resourceUri) {
+        String canonicalResourceURI = URI.create(resourceUri).normalize().toString();
+        UriTemplate uriTemplate = new UriTemplate(appendTrailingSlash(uriTemplateDef));
+        return uriTemplate.matches(appendTrailingSlash(canonicalResourceURI));
+    }
+
+    public static String appendTrailingSlash(final String s) {
+        if (!s.endsWith("/")) {
+            return new StringBuilder(s).append("/").toString();
+        }
+        return s;
+    }
+
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/ZoneDoesNotExistException.java b/commons/src/main/java/com/ge/predix/acs/commons/web/ZoneDoesNotExistException.java
new file mode 100644
index 0000000..d74c1c0
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/ZoneDoesNotExistException.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+public class ZoneDoesNotExistException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    public ZoneDoesNotExistException(final String message) {
+        super(message);
+    }
+}
\ No newline at end of file
diff --git a/commons/src/main/java/com/ge/predix/acs/commons/web/ZoneDoesNotExistExceptionHandler.java b/commons/src/main/java/com/ge/predix/acs/commons/web/ZoneDoesNotExistExceptionHandler.java
new file mode 100644
index 0000000..d483ed8
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/commons/web/ZoneDoesNotExistExceptionHandler.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import org.json.simple.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+@ControllerAdvice
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class ZoneDoesNotExistExceptionHandler extends BaseRestApiControllerAdvice {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ZoneDoesNotExistExceptionHandler.class);
+
+    public ZoneDoesNotExistExceptionHandler() {
+        super(LOGGER);
+    }
+
+    @ExceptionHandler(ZoneDoesNotExistException.class)
+    public ResponseEntity<JSONObject> handleException(final ZoneDoesNotExistException e) {
+        LOGGER.error(e.getMessage(), e);
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST).contentType(MediaType.APPLICATION_JSON)
+                .body(new JSONObject() {{
+                    put("error",
+                            HttpStatus.BAD_REQUEST.getReasonPhrase());
+                    put("message",
+                            "Zone not found");
+                }});
+    }
+}
diff --git a/commons/src/main/java/com/ge/predix/acs/utils/JsonUtils.java b/commons/src/main/java/com/ge/predix/acs/utils/JsonUtils.java
new file mode 100644
index 0000000..a63ed4e
--- /dev/null
+++ b/commons/src/main/java/com/ge/predix/acs/utils/JsonUtils.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.utils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.net.URL;
+import java.util.Collection;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.github.fge.jackson.JsonNodeReader;
+
+/**
+ * JSON utility methods convert from/to json files, objects and string.
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings("nls")
+public class JsonUtils {
+    private static final Logger LOGGER = LoggerFactory.getLogger(JsonUtils.class);
+
+    private static final ObjectMapper OBJECT_MAPPER;
+    private static final JsonNodeReader READER = new JsonNodeReader();
+    private static final String UNEXPECTED_EXCEPTION = "Unexpected Exception";
+
+    static {
+        OBJECT_MAPPER = new ObjectMapper();
+        OBJECT_MAPPER.setSerializationInclusion(Include.NON_NULL);
+    }
+
+    public <T> T deserializeFromFile(final String fileName, final Class<T> type) {
+        try (InputStream fis = inputStreamFromFileName(fileName)) {
+            return OBJECT_MAPPER.readValue(fis, type);
+        } catch (Exception e) {
+            LOGGER.error(UNEXPECTED_EXCEPTION, e);
+            return null;
+        }
+    }
+
+    public <T> String serialize(final T object) {
+        try {
+            return OBJECT_MAPPER.writeValueAsString(object);
+        } catch (Exception e) {
+            LOGGER.error(UNEXPECTED_EXCEPTION, e);
+            return null;
+        }
+    }
+
+    public <T> T deserialize(final String jsonString, final Class<T> type) {
+        try {
+            return OBJECT_MAPPER.readValue(jsonString, type);
+        } catch (IOException e) {
+            LOGGER.error(UNEXPECTED_EXCEPTION, e);
+            return null;
+        }
+    }
+
+    public <C extends Collection<T>, T> C deserialize(final String jsonString, final Class<C> collectionType,
+            final Class<T> elementType) {
+        try {
+            final CollectionType javaType = OBJECT_MAPPER.getTypeFactory()
+                    .constructCollectionType(collectionType, elementType);
+
+            return OBJECT_MAPPER.readValue(jsonString, javaType);
+        } catch (IOException e) {
+            LOGGER.error(UNEXPECTED_EXCEPTION, e);
+            return null;
+        }
+    }
+
+    public JsonNode readJsonNodeFromFile(final String jsonFileName) {
+
+        try (InputStream fis = inputStreamFromFileName(jsonFileName)) {
+            return READER.fromInputStream(fis);
+        } catch (Exception e) {
+            LOGGER.error(UNEXPECTED_EXCEPTION, e);
+            return null;
+        }
+    }
+
+    public <T> JsonNode readJsonNodeFromObject(final T object) {
+
+        String serializedObject = serialize(object);
+        try (Reader objectReader = new StringReader(serializedObject)) {
+            return READER.fromReader(objectReader);
+        } catch (Exception e) {
+            LOGGER.error(UNEXPECTED_EXCEPTION, e);
+            return null;
+        }
+    }
+
+    private InputStream inputStreamFromFileName(final String fileName) throws IOException {
+        URL resource = this.getClass().getClassLoader().getResource(fileName);
+        if (resource == null) {
+            LOGGER.error("Could not find file [{}]", fileName);
+            return null;
+        }
+        return resource.openStream();
+    }
+
+}
diff --git a/commons/src/main/resources/SecureExtension.groovy b/commons/src/main/resources/SecureExtension.groovy
new file mode 100644
index 0000000..0015d77
--- /dev/null
+++ b/commons/src/main/resources/SecureExtension.groovy
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+import org.codehaus.groovy.ast.ClassHelper
+import org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport
+import com.ge.predix.acs.commons.policy.condition.groovy.AttributeMatcher
+import com.ge.predix.acs.commons.policy.condition.ResourceHandler;
+import com.ge.predix.acs.commons.policy.condition.SubjectHandler;
+
+class SecureExtension extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL {
+    @Override
+    Object run() {
+        // disallow calls on System
+        onMethodSelection { expr, methodNode ->
+            //System.out.println("***** onMethodSelection *****")
+
+            // First the white list.
+            if ((methodNode.declaringClass.name != 'com.ge.predix.acs.commons.policy.condition.AbstractHandler')
+                && (methodNode.declaringClass.name != 'com.ge.predix.acs.commons.policy.condition.ResourceHandler')
+                && (methodNode.declaringClass.name != 'com.ge.predix.acs.commons.policy.condition.SubjectHandler')
+                && (methodNode.declaringClass.name != 'com.ge.predix.acs.commons.policy.condition.groovy.AttributeMatcher')
+                && (methodNode.declaringClass.name != 'java.lang.Boolean')
+                && (methodNode.declaringClass.name != 'java.lang.Integer')
+                && (methodNode.declaringClass.name != 'java.lang.Iterable')
+                && (methodNode.declaringClass.name != 'java.lang.Object')
+                // This means we allow collections of type Object.
+                && (methodNode.declaringClass.name != '[Ljava.lang.Object;')
+                && (methodNode.declaringClass.name != 'java.lang.String')
+                && (methodNode.declaringClass.name != 'java.util.Collection')
+                && (methodNode.declaringClass.name != 'java.util.Set')) {
+                addStaticTypeError("Method call for '" + methodNode.declaringClass.name + "' class is not allowed!", expr)
+            }
+        
+            // Then the black list.
+            if (methodNode.declaringClass.name == 'java.lang.System') {
+                addStaticTypeError("Method call for 'java.lang.System' class is not allowed!", expr)
+            }
+            if (methodNode.declaringClass.name == 'groovy.util.Eval') {
+                addStaticTypeError("Method call for 'groovy.util.Eval' class is not allowed!", expr)
+            }
+            if (methodNode.declaringClass.name.startsWith('java.io')) {
+                addStaticTypeError("Method call for 'java.io' package is not allowed!", expr)
+            }
+            if (methodNode.name == 'execute') {
+                addStaticTypeError("Method call 'execute' is not allowed!", expr)
+            }
+        }
+
+/*
+        unresolvedVariable { var ->
+            System.out.println("***** unresolvedVariable *****")
+
+            if (var.name == 'resource') {
+                return makeDynamic(var, ClassHelper.makeCached(ResourceHandler.class))
+            }
+            if (var.name == 'subject') {
+                return makeDynamic(var, ClassHelper.makeCached(SubjectHandler.class))
+            }
+            if (var.name == 'match') {
+                return makeDynamic(var, ClassHelper.makeCached(AttributeMatcher.class))
+            }
+        }
+*/
+
+        unresolvedVariable { var ->
+            //System.out.println("***** unresolvedVariable *****")
+
+            if ('resource' == var.name) {
+                storeType(var, classNodeFor(ResourceHandler.class))
+                handled = true
+            }
+            if ('subject' == var.name) {
+                storeType(var, classNodeFor(SubjectHandler.class))
+                handled = true
+            }
+            if ('match' == var.name) {
+                storeType(var, classNodeFor(AttributeMatcher.class))
+                handled = true
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/commons/src/test/java/com/ge/predix/acs/commons/policy/condition/ResourceHandlerTest.java b/commons/src/test/java/com/ge/predix/acs/commons/policy/condition/ResourceHandlerTest.java
new file mode 100644
index 0000000..13166ae
--- /dev/null
+++ b/commons/src/test/java/com/ge/predix/acs/commons/policy/condition/ResourceHandlerTest.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.model.Attribute;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings({ "nls", "javadoc" })
+public class ResourceHandlerTest {
+
+    @Test
+    public void testNullAttributes() {
+        ResourceHandler handler = new ResourceHandler(null, null, null);
+        Assert.assertNotNull(handler);
+        Assert.assertEquals("", handler.uriVariable("site_id"));
+        Assert.assertEquals("", handler.uriVariable(null));
+    }
+
+    @Test
+    public void testPartialNullAttributes() {
+        ResourceHandler handler = new ResourceHandler(null, null, "a");
+        Assert.assertNotNull(handler);
+        Assert.assertEquals("", handler.uriVariable("site_id"));
+        Assert.assertEquals("", handler.uriVariable(null));
+    }
+
+    @Test
+    public void testUriVariableNegative() {
+        HashSet<Attribute> attributes = new HashSet<Attribute>(
+                Arrays.asList(new Attribute("acs", "site", "boston"), new Attribute("acs", "department", "sales")));
+        ResourceHandler handler = new ResourceHandler(attributes, "", "");
+        Assert.assertEquals("", handler.uriVariable(""));
+        Assert.assertEquals("", handler.uriVariable(null));
+    }
+
+    @Test
+    public void testUriVariablePositive() {
+        HashSet<Attribute> attributes = new HashSet<Attribute>(
+                Arrays.asList(new Attribute("acs", "site", "boston"), new Attribute("acs", "department", "sales")));
+        ResourceHandler handler = new ResourceHandler(attributes,
+                "http://assets.predix.io/site/boston/department/sales", "site/{site_id}/department/{department_id}");
+        Assert.assertEquals("boston", handler.uriVariable("site_id"));
+        Assert.assertEquals("sales", handler.uriVariable("department_id"));
+        Assert.assertNotEquals("hr", handler.uriVariable("department_id"));
+    }
+}
diff --git a/commons/src/test/java/com/ge/predix/acs/commons/policy/condition/SubjectHandlerTest.java b/commons/src/test/java/com/ge/predix/acs/commons/policy/condition/SubjectHandlerTest.java
new file mode 100644
index 0000000..3394f6c
--- /dev/null
+++ b/commons/src/test/java/com/ge/predix/acs/commons/policy/condition/SubjectHandlerTest.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.model.Attribute;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings({ "unchecked", "javadoc", "nls" })
+public class SubjectHandlerTest {
+
+    @Test
+    public void testNullAttributes() {
+
+        SubjectHandler subjectHandler = new SubjectHandler(null);
+        Assert.assertNotNull(subjectHandler);
+    }
+
+    @Test
+    public void testEmptyAttributes() {
+        SubjectHandler subjectHandler = new SubjectHandler(Collections.EMPTY_SET);
+        Assert.assertNotNull(subjectHandler);
+        Assert.assertTrue(subjectHandler.attributes("acs", "site").isEmpty());
+    }
+
+    @Test
+    public void testValidAttributes() {
+
+        HashSet<Attribute> attributes = new HashSet<Attribute>(
+                Arrays.asList(new Attribute("acs", "site", "boston"), new Attribute("acs", "department", "sales")));
+        SubjectHandler subjectHandler = new SubjectHandler(attributes);
+        Assert.assertNotNull(subjectHandler);
+        Assert.assertTrue(subjectHandler.attributes("acs", "site").contains("boston"));
+        Assert.assertTrue(subjectHandler.attributes("acs", "department").contains("sales"));
+        Assert.assertFalse(subjectHandler.attributes("acs", "site").contains("newyork"));
+        Assert.assertTrue(subjectHandler.attributes("acs", "region").isEmpty());
+    }
+
+}
diff --git a/commons/src/test/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionCacheTest.java b/commons/src/test/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionCacheTest.java
new file mode 100644
index 0000000..abf4386
--- /dev/null
+++ b/commons/src/test/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionCacheTest.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition.groovy;
+
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.commons.policy.condition.ConditionScript;
+
+public class GroovyConditionCacheTest {
+
+    @Test
+    public void testPutGetAndRemove() {
+        GroovyConditionCache cache = new GroovyConditionCache();
+        ConditionScript compiledScript = Mockito.mock(ConditionScript.class);
+        String testScript = "1 == 1";
+        cache.put(testScript, compiledScript);
+        Assert.assertEquals(cache.get(testScript), compiledScript);
+        cache.remove(testScript);
+        Assert.assertNull(cache.get(testScript));
+    }
+
+}
diff --git a/commons/src/test/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionScriptTest.java b/commons/src/test/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionScriptTest.java
new file mode 100644
index 0000000..5da57eb
--- /dev/null
+++ b/commons/src/test/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionScriptTest.java
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition.groovy;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.commons.policy.condition.ConditionParsingException;
+import com.ge.predix.acs.commons.policy.condition.ConditionScript;
+import com.ge.predix.acs.commons.policy.condition.ConditionShell;
+import com.ge.predix.acs.commons.policy.condition.ResourceHandler;
+import com.ge.predix.acs.commons.policy.condition.SubjectHandler;
+import com.ge.predix.acs.model.Attribute;
+
+/**
+ * Tests for Groovy policy condition script execution.
+ *
+ * @author acs-engineers@ge.com
+ */
+public class GroovyConditionScriptTest {
+    private ConditionShell shell;
+
+    @BeforeClass
+    public void setup() {
+        this.shell = new GroovyConditionShell();
+    }
+
+    /**
+     * Test the execution of a policy condition, which should evaluate to true, using strings constants.
+     *
+     * @throws ConditionParsingException
+     */
+    @Test
+    public void testScriptExecutionPositiveEqualsNoBinding() throws ConditionParsingException {
+        String script = "\"a\".equals(\"a\")"; //$NON-NLS-1$
+        ConditionScript parsedScript = this.shell.parse(script);
+        Assert.assertEquals(parsedScript.execute(new HashMap<String, Object>()), true);
+    }
+
+    /**
+     * Test the execution of a policy condition, which should evaluate to false, using strings constants.
+     *
+     * @throws ConditionParsingException
+     */
+    @Test
+    public void testScriptExecutionNotEqualsNoBinding() throws ConditionParsingException {
+        String script = "\"a\".equals(\"b\")";
+        ConditionScript parsedScript = this.shell.parse(script);
+        Assert.assertEquals(parsedScript.execute(new HashMap<String, Object>()), false);
+    }
+
+    /**
+     * Test the execution of a policy condition which does not result in a boolean value.
+     *
+     * @throws ConditionParsingException
+     */
+    @Test(expectedExceptions = ClassCastException.class)
+    public void testScriptExecutionWithNonBooleanReturnNoBinding() throws ConditionParsingException {
+        String script = "\"a\".concat(\"b\")";
+        ConditionScript parsedScript = this.shell.parse(script);
+        Assert.assertEquals(parsedScript.execute(new HashMap<String, Object>()), false);
+    }
+
+    /**
+     * Test the execution of a policy condition, which should evaluate to true, using strings variable binding.
+     *
+     * @throws ConditionParsingException
+     */
+    @Test
+    public void testScriptExecutionPositiveEqualsSingleBinding() throws ConditionParsingException {
+        String script = "resource == \"b\""; //$NON-NLS-1$
+        ConditionScript parsedScript = this.shell.parse(script);
+        Map<String, Object> parameter = new HashMap<>();
+        parameter.put("resource", new ResourceHandler(new HashSet<Attribute>(), "", ""));
+        Assert.assertEquals(parsedScript.execute(parameter), false);
+    }
+
+    /**
+     * Test the execution of a policy condition, which should evaluate to true, using multiple string variable
+     * bindings.
+     *
+     * @throws ConditionParsingException
+     */
+    @Test
+    public void testScriptExecutionPositiveEqualsMultipleBindings() throws ConditionParsingException {
+        String script = "resource != subject"; //$NON-NLS-1$
+        ConditionScript parsedScript = this.shell.parse(script);
+        Map<String, Object> parameter = new HashMap<>();
+        parameter.put("resource", new ResourceHandler(new HashSet<Attribute>(), "", ""));
+        parameter.put("subject", new SubjectHandler(new HashSet<Attribute>()));
+        Assert.assertEquals(parsedScript.execute(parameter), true);
+    }
+
+    /**
+     * Test the execution of a policy condition, which should evaluate to true, using multiple string variable
+     * bindings.
+     *
+     * @throws ConditionParsingException
+     */
+    @Test
+    public void testScriptExecutionPositiveEqualsMultipleBindingsWithMatcher() throws ConditionParsingException {
+        String script = "match.any(resource.attributes(\"\",\"\"), subject.attributes(\"\",\"\"))"; //$NON-NLS-1$
+        ConditionScript parsedScript = this.shell.parse(script);
+        Map<String, Object> parameter = new HashMap<>();
+        parameter.put("resource", new ResourceHandler(new HashSet<Attribute>(), "", ""));
+        parameter.put("subject", new SubjectHandler(new HashSet<Attribute>()));
+        parameter.put("match", new AttributeMatcher());
+        Assert.assertEquals(parsedScript.execute(parameter), false);
+    }
+
+    /**
+     * Test the execution of a policy condition, which uses reflection.
+     *
+     * @throws ConditionParsingException
+     */
+    @Test(expectedExceptions = ConditionParsingException.class)
+    public void testScriptExecutionUsingReflection() throws ConditionParsingException {
+
+        String script = "new ResourceHandler().getClass().getClassLoader().loadClass(System.class.getName())."
+                + "getMethod(\"exit\");";
+        ConditionScript parsedScript = this.shell.parse(script);
+        Map<String, Object> parameter = new HashMap<>();
+        Assert.assertNotNull(parsedScript.execute(parameter));
+    }
+
+    /**
+     * Test the execution of a policy condition, which tries to assign null, to a variable.
+     *
+     * @throws ConditionParsingException
+     */
+    @Test
+    public void testScriptExecutionUsingAssignment() throws ConditionParsingException {
+        String script = "resource = null; resource == null;";
+        ConditionScript parsedScript = this.shell.parse(script);
+        Map<String, Object> parameter = new HashMap<>();
+        final ResourceHandler resourceHandler = new ResourceHandler(new HashSet<Attribute>(), "", "");
+        parameter.put("resource", resourceHandler);
+        Assert.assertEquals(parsedScript.execute(parameter), true);
+        Assert.assertNotNull(resourceHandler);
+    }
+}
diff --git a/commons/src/test/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionShellTest.java b/commons/src/test/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionShellTest.java
new file mode 100644
index 0000000..0523759
--- /dev/null
+++ b/commons/src/test/java/com/ge/predix/acs/commons/policy/condition/groovy/GroovyConditionShellTest.java
@@ -0,0 +1,169 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.policy.condition.groovy;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.commons.policy.condition.ConditionParsingException;
+import com.ge.predix.acs.commons.policy.condition.ConditionScript;
+import com.ge.predix.acs.commons.policy.condition.ConditionShell;
+
+/**
+ * Tests for Groovy policy condition script parsing and validation.
+ *
+ * @author acs-engineers@ge.com
+ */
+public class GroovyConditionShellTest {
+    private ConditionShell shell;
+
+    @BeforeClass
+    public void setup() {
+        this.shell = new GroovyConditionShell();
+    }
+
+    /**
+     * Test a policy condition parsing and compilation using an allowed policy operations.
+     *
+     * @throws ConditionParsingException
+     *             this test should not throw this exception.
+     */
+    @Test
+    public void testValidateScriptWithValidScript() throws ConditionParsingException {
+        String script = "\"a\".equals(\"a\")";
+        ConditionScript parsedScript = this.shell.parse(script);
+        Assert.assertNotNull(parsedScript);
+    }
+
+    /**
+     * Test a policy condition parsing and compilation for a blank script.
+     *
+     * @throws ConditionParsingException
+     *             this test should not throw exception
+     */
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void testValidateEmptyScript() throws ConditionParsingException {
+        String script = "";
+        this.shell.parse(script);
+    }
+
+    /**
+     * Test a policy condition parsing and compilation for a null script.
+     *
+     * @throws ConditionParsingException
+     *             this test should not throw exception
+     */
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void testValidateNullScript() throws ConditionParsingException {
+        ConditionScript parsedScript = this.shell.parse(null);
+        Assert.assertNotNull(parsedScript);
+    }
+
+    /**
+     * Test a policy condition parsing and compilation for a script with for loop.
+     *
+     * @throws ConditionParsingException
+     *             we expect this exception for this test.
+     */
+    @Test
+    public void testValidateScriptWithForLoop() throws ConditionParsingException {
+        String script = "for (int i = 0; i < 5; i++) {}";
+        this.shell.parse(script);
+    }
+
+    /**
+     * Test a policy condition parsing and compilation for a script trying to invoke System.exit(0).
+     *
+     * @throws ConditionParsingException
+     *             we expect this exception for this test.
+     */
+    @Test(expectedExceptions = ConditionParsingException.class)
+    public void testValidateScriptWithSystemInvocation() throws ConditionParsingException {
+        String script = "System.exit(0)";
+        this.shell.parse(script);
+    }
+
+    /**
+     * Test a policy condition validation for a script trying to use reflection.
+     *
+     * @throws ConditionParsingException
+     *             we expect this exception for this test.
+     */
+    @Test(expectedExceptions = ConditionParsingException.class)
+    public void testValidateScriptWithReflection() throws ConditionParsingException {
+        String script = "((System)(Class.forName(\"java.lang.System\")).newInstance()).exit(0);";
+        this.shell.parse(script);
+    }
+
+    /**
+     * Test policy condition parsing and compilation for a script that uses threads which returns a value.
+     *
+     * @throws ConditionParsingException
+     *             we expect this exception for this test.
+     */
+    @Test(expectedExceptions = ConditionParsingException.class)
+    public void testParseScriptWithThreadInvocation() throws ConditionParsingException {
+        String script = "Thread.currentThread().toString()";
+        this.shell.parse(script);
+    }
+
+    @Test(expectedExceptions = ConditionParsingException.class)
+    public void testParseScriptWithSystemInvocation1() throws ConditionParsingException {
+        String script = "def c = System; c.exit(-1);";
+        this.shell.parse(script);
+    }
+
+    @Test(expectedExceptions = ConditionParsingException.class)
+    public void testParseScriptWithSystemInvocation2() throws ConditionParsingException {
+        String script = "((Object)System).exit(-1);";
+        this.shell.parse(script);
+    }
+
+    @Test(expectedExceptions = ConditionParsingException.class)
+    public void testParseScriptWithSystemInvocation3() throws ConditionParsingException {
+        String script = "Class.forName('java.lang.System').exit(-1);";
+        this.shell.parse(script);
+    }
+
+    @Test(expectedExceptions = ConditionParsingException.class)
+    public void testParseScriptWithSystemInvocation4() throws ConditionParsingException {
+        String script = "('java.lang.System' as Class).exit(-1);";
+        this.shell.parse(script);
+    }
+
+    @Test(expectedExceptions = ConditionParsingException.class)
+    public void testParseScriptWithSystemInvocation5() throws ConditionParsingException {
+        String script = "import static java.lang.System.exit; exit(-1);";
+        this.shell.parse(script);
+    }
+
+    @Test(expectedExceptions = ConditionParsingException.class)
+    public void testParseScriptWithEval() throws ConditionParsingException {
+        String script = "Eval.me(' 2 * 4 + 2');";
+        this.shell.parse(script);
+    }
+
+    @Test(expectedExceptions = ConditionParsingException.class)
+    public void testParseScriptWithStringExecute() throws ConditionParsingException {
+        String script = "'env'.execute();";
+        this.shell.parse(script);
+    }
+
+}
diff --git a/commons/src/test/java/com/ge/predix/acs/commons/web/BaseRestApiControllerAdviceTest.java b/commons/src/test/java/com/ge/predix/acs/commons/web/BaseRestApiControllerAdviceTest.java
new file mode 100644
index 0000000..c0084f2
--- /dev/null
+++ b/commons/src/test/java/com/ge/predix/acs/commons/web/BaseRestApiControllerAdviceTest.java
@@ -0,0 +1,146 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.web.HttpMediaTypeNotAcceptableException;
+import org.springframework.web.HttpMediaTypeNotSupportedException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.commons.exception.UntrustedIssuerException;
+
+@SuppressWarnings({ "javadoc", "nls" })
+public class BaseRestApiControllerAdviceTest {
+
+    @Test
+    public void testBaseRestApiControllerAdviceException() {
+
+        RestApiExceptionHandler restApiExceptionHandler = new RestApiExceptionHandler();
+        RestApiException e = new RestApiException("Internal server error");
+
+        ResponseEntity<RestApiErrorResponse> actualErrorResponse = restApiExceptionHandler.handleException(e);
+
+        Assert.assertNotNull(actualErrorResponse);
+        Assert.assertTrue(actualErrorResponse.getStatusCode().toString().equals("500"));
+        Assert.assertTrue(
+                actualErrorResponse.getBody().getErrorDetails().getErrorMessage().equals("Internal server error"));
+    }
+
+    @Test
+    public void testBaseRestApiControllerAdviceHttpMediaTypeNotAcceptableException() {
+
+        HttpMediaTypeNotAcceptableExceptionHandler httpMediaTypeNotAcceptableExceptionHandler = new
+                HttpMediaTypeNotAcceptableExceptionHandler();
+
+        HttpMediaTypeNotAcceptableException e = new HttpMediaTypeNotAcceptableException("Media Type Not Supported");
+
+        RestApiErrorResponse actualErrorResponse = httpMediaTypeNotAcceptableExceptionHandler.handleException(e);
+
+        Assert.assertNotNull(actualErrorResponse);
+        Assert.assertTrue(actualErrorResponse.getErrorDetails().getErrorMessage().equals("Not Acceptable"));
+    }
+
+    @Test
+    public void testBaseRestApiControllerAdviceHttpRequestMethodNotSupportedException() {
+
+        HttpRequestMethodNotSupportedExceptionHandler httpRequestMethodNotSupportedExceptionHandler = new
+                HttpRequestMethodNotSupportedExceptionHandler();
+
+        HttpRequestMethodNotSupportedException e = new HttpRequestMethodNotSupportedException("GET");
+
+        RestApiErrorResponse actualErrorResponse = httpRequestMethodNotSupportedExceptionHandler.handleException(e);
+
+        Assert.assertNotNull(actualErrorResponse);
+        Assert.assertTrue(actualErrorResponse.getErrorDetails().getErrorMessage().equals("Method Not Allowed"));
+    }
+
+    @Test
+    public void testBaseRestApiControllerAdviceIllegalArgumentException() {
+
+        IllegalArgumentExceptionHandler illegalArgumentExceptionHandler = new IllegalArgumentExceptionHandler();
+
+        IllegalArgumentException e = new IllegalArgumentException("Arguments passed are invalid");
+
+        RestApiErrorResponse actualErrorResponse = illegalArgumentExceptionHandler.handleException(e);
+
+        Assert.assertNotNull(actualErrorResponse);
+        Assert.assertTrue(
+                actualErrorResponse.getErrorDetails().getErrorMessage().equals("Arguments passed are invalid"));
+    }
+
+    @Test
+    public void testBaseRestApiControllerAdviceUntrustedIssuerException() {
+
+        UntrustedIssuerExceptionHandler untrustedIssuerExceptionHandler = new UntrustedIssuerExceptionHandler();
+
+        UntrustedIssuerException e = new UntrustedIssuerException("Not a trusted Issuer");
+
+        RestApiErrorResponse actualErrorResponse = untrustedIssuerExceptionHandler.handleException(e);
+
+        Assert.assertNotNull(actualErrorResponse);
+        Assert.assertTrue(actualErrorResponse.getErrorDetails().getErrorMessage().equals("Not a trusted Issuer"));
+
+    }
+
+    @Test
+    public void testBaseRestApiControllerAdviceSecurityException() {
+
+        SecurityExceptionHandler securityExceptionHandler = new SecurityExceptionHandler();
+
+        SecurityException e = new SecurityException("Not a trusted Issuer");
+
+        RestApiErrorResponse actualErrorResponse = securityExceptionHandler.handleException(e);
+
+        Assert.assertNotNull(actualErrorResponse);
+        Assert.assertTrue(actualErrorResponse.getErrorDetails().getErrorMessage().equals("Not a trusted Issuer"));
+
+    }
+
+    @Test
+    public void testBaseRestApiControllerAdviceHttpMessageNotReadableException() {
+
+        HttpMessageNotReadableExceptionHandler httpMessageNotReadableExceptionHandler = new
+                HttpMessageNotReadableExceptionHandler();
+        HttpMessageNotReadableException e = new HttpMessageNotReadableException("{JSON}");
+
+        RestApiErrorResponse actualErrorResponse = httpMessageNotReadableExceptionHandler.handleException(e);
+
+        Assert.assertNotNull(actualErrorResponse);
+        Assert.assertTrue(actualErrorResponse.getErrorDetails().getErrorCode().equals("FAILED"));
+        Assert.assertTrue(
+                actualErrorResponse.getErrorDetails().getErrorMessage().equals("Malformed JSON syntax. {JSON}"));
+    }
+
+    @Test
+    public void testBaseRestApiControllerAdviceHttpMediaTypeNotSupportedException() {
+
+        HttpMediaTypeNotSupportedExceptionHandler httpMediaTypeNotSupportedExceptionHandler = new
+                HttpMediaTypeNotSupportedExceptionHandler();
+
+        HttpMediaTypeNotSupportedException e = new HttpMediaTypeNotSupportedException("JSON");
+
+        RestApiErrorResponse actualErrorResponse = httpMediaTypeNotSupportedExceptionHandler.handleException(e);
+
+        Assert.assertNotNull(actualErrorResponse);
+        Assert.assertTrue(actualErrorResponse.getErrorDetails().getErrorMessage().equals("Unsupported Media Type"));
+    }
+}
diff --git a/commons/src/test/java/com/ge/predix/acs/commons/web/ResponseEntityBuilderTest.java b/commons/src/test/java/com/ge/predix/acs/commons/web/ResponseEntityBuilderTest.java
new file mode 100644
index 0000000..07daeee
--- /dev/null
+++ b/commons/src/test/java/com/ge/predix/acs/commons/web/ResponseEntityBuilderTest.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import java.net.URI;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+@SuppressWarnings({ "javadoc", "nls" })
+public class ResponseEntityBuilderTest {
+
+    @Test
+    public void testCreated() {
+        ResponseEntity<Object> created = ResponseEntityBuilder.created();
+        Assert.assertNotNull(created);
+        Assert.assertNull(created.getBody());
+        Assert.assertEquals(created.getStatusCode(), HttpStatus.CREATED);
+    }
+
+    @Test
+    public void testCreatedWithLocation() {
+        ResponseEntity<Object> created = ResponseEntityBuilder.created("/report/1", Boolean.FALSE);
+
+        Assert.assertNotNull(created);
+        Assert.assertNull(created.getBody());
+        Assert.assertEquals(created.getStatusCode(), HttpStatus.CREATED);
+
+        Assert.assertNotNull(created.getHeaders());
+        URI location = created.getHeaders().getLocation();
+        Assert.assertEquals(location.getPath(), "/report/1");
+    }
+
+    @Test
+    public void testUpdatedWithLocation() {
+        ResponseEntity<Object> created = ResponseEntityBuilder.created("/report/1", Boolean.TRUE);
+
+        Assert.assertNotNull(created);
+        Assert.assertNull(created.getBody());
+        Assert.assertEquals(created.getStatusCode(), HttpStatus.NO_CONTENT);
+
+        Assert.assertNotNull(created.getHeaders());
+        URI location = created.getHeaders().getLocation();
+        Assert.assertEquals(location.getPath(), "/report/1");
+    }
+
+    @Test
+    public void testOk() {
+        ResponseEntity<Object> ok = ResponseEntityBuilder.ok();
+        Assert.assertNotNull(ok);
+        Assert.assertNull(ok.getBody());
+        Assert.assertEquals(ok.getStatusCode(), HttpStatus.OK);
+    }
+
+    @Test
+    public void testOkWithContent() {
+        String content = "PredixRocks";
+        ResponseEntity<String> ok = ResponseEntityBuilder.ok(content);
+        Assert.assertNotNull(ok);
+        Assert.assertEquals(ok.getStatusCode(), HttpStatus.OK);
+        Assert.assertEquals(ok.getBody(), "PredixRocks");
+
+    }
+
+    @Test
+    public void testDeleted() {
+        ResponseEntity<Void> deleted = ResponseEntityBuilder.noContent();
+        Assert.assertNotNull(deleted);
+        Assert.assertNull(deleted.getBody());
+        Assert.assertEquals(deleted.getStatusCode(), HttpStatus.NO_CONTENT);
+    }
+
+}
diff --git a/commons/src/test/java/com/ge/predix/acs/commons/web/RestApiExceptionTest.java b/commons/src/test/java/com/ge/predix/acs/commons/web/RestApiExceptionTest.java
new file mode 100644
index 0000000..766cdb9
--- /dev/null
+++ b/commons/src/test/java/com/ge/predix/acs/commons/web/RestApiExceptionTest.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.commons.web;
+
+import org.springframework.http.HttpStatus;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+@SuppressWarnings({ "javadoc", "nls" })
+public class RestApiExceptionTest {
+
+    @Test
+    public void testRestApiExceptionDefaultConstructor() {
+        RestApiException apiException = new RestApiException();
+        Assert.assertEquals(apiException.getMessage(), null);
+        Assert.assertEquals(apiException.getAppErrorCode(), "FAILURE");
+        Assert.assertEquals(apiException.getHttpStatusCode(), HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+
+    @Test
+    public void testRestApiExceptionWithMessage() {
+        RestApiException apiException = new RestApiException("message");
+        Assert.assertEquals(apiException.getMessage(), "message");
+        Assert.assertEquals(apiException.getAppErrorCode(), "FAILURE");
+        Assert.assertEquals(apiException.getHttpStatusCode(), HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+
+    @Test
+    public void testRestApiExceptionWithException() {
+        RuntimeException runtimeException = new RuntimeException("Runtime exception message");
+        RestApiException apiException = new RestApiException(runtimeException);
+
+        Assert.assertEquals(apiException.getCause(), runtimeException);
+        Assert.assertEquals(apiException.getMessage(), "java.lang.RuntimeException: Runtime exception message");
+        Assert.assertEquals(apiException.getAppErrorCode(), "FAILURE");
+        Assert.assertEquals(apiException.getHttpStatusCode(), HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+
+    @Test
+    public void testRestApiExceptionWithMessageException() {
+        RuntimeException runtimeException = new RuntimeException();
+        RestApiException apiException = new RestApiException("message", runtimeException);
+
+        Assert.assertEquals(apiException.getCause(), runtimeException);
+        Assert.assertEquals(apiException.getMessage(), "message");
+        Assert.assertEquals(apiException.getAppErrorCode(), "FAILURE");
+        Assert.assertEquals(apiException.getHttpStatusCode(), HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+
+    @Test
+    public void testRestApiExceptionWithMessageExceptionBooleans() {
+        RuntimeException runtimeException = new RuntimeException();
+        RestApiException apiException = new RestApiException("message", runtimeException, true, true);
+
+        Assert.assertEquals(apiException.getCause(), runtimeException);
+        Assert.assertEquals(apiException.getMessage(), "message");
+        Assert.assertEquals(apiException.getAppErrorCode(), "FAILURE");
+        Assert.assertEquals(apiException.getHttpStatusCode(), HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+
+    @Test
+    public void testRestApiExceptionWithStatusMessage() {
+        RestApiException apiException = new RestApiException(HttpStatus.OK, "code", "message");
+
+        Assert.assertEquals(apiException.getAppErrorCode(), "code");
+        Assert.assertEquals(apiException.getHttpStatusCode(), HttpStatus.OK);
+        Assert.assertEquals(apiException.getMessage(), "message");
+        Assert.assertEquals(apiException.getCause(), null);
+    }
+
+    @Test
+    public void testRestApiExceptionWithStatusCodeMessage() {
+        RestApiException apiException = new RestApiException(HttpStatus.OK, "code", "message");
+
+        Assert.assertEquals(apiException.getAppErrorCode(), "code");
+        Assert.assertEquals(apiException.getHttpStatusCode(), HttpStatus.OK);
+        Assert.assertEquals(apiException.getMessage(), "message");
+        Assert.assertEquals(apiException.getCause(), null);
+    }
+
+    @Test
+    public void testRestApiExceptionWithStatusCause() {
+        RuntimeException runtimeException = new RuntimeException("Runtime exception message");
+        RestApiException apiException = new RestApiException(HttpStatus.OK, runtimeException);
+
+        Assert.assertEquals(apiException.getAppErrorCode(), "FAILURE");
+        Assert.assertEquals(apiException.getHttpStatusCode(), HttpStatus.OK);
+        Assert.assertEquals(apiException.getMessage(), "java.lang.RuntimeException: Runtime exception message");
+        Assert.assertEquals(apiException.getCause(), runtimeException);
+    }
+
+    @Test
+    public void testRestApiExceptionWithStatusMessageCause() {
+        RuntimeException runtimeException = new RuntimeException();
+        RestApiException apiException = new RestApiException(HttpStatus.OK, "code", "message", runtimeException);
+
+        Assert.assertEquals(apiException.getAppErrorCode(), "code");
+        Assert.assertEquals(apiException.getHttpStatusCode(), HttpStatus.OK);
+        Assert.assertEquals(apiException.getMessage(), "message");
+        Assert.assertEquals(apiException.getCause(), runtimeException);
+    }
+
+}
diff --git a/commons/src/test/java/com/ge/predix/acs/utils/JsonUtilsTest.java b/commons/src/test/java/com/ge/predix/acs/utils/JsonUtilsTest.java
new file mode 100644
index 0000000..379754d
--- /dev/null
+++ b/commons/src/test/java/com/ge/predix/acs/utils/JsonUtilsTest.java
@@ -0,0 +1,170 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.utils;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.PolicySet;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+public class JsonUtilsTest {
+
+    private static final int STUDENT_AGE = 15;
+    private final JsonUtils jsonUtils = new JsonUtils();
+
+    @Test
+    public void testDeserialize() {
+        Student student = this.jsonUtils.deserialize(
+                "{\"id\":\"S00001\",\"age\":15,\"name\":\"Joe\",\"courses\":[\"Math\",\"Arts\"]}", Student.class);
+        assertStudent(student);
+    }
+
+    @Test
+    public void testDeserializeCollectionOfTypedItems() {
+        String studentsAsJson = "[{\"issuer\":\"acs\",\"name\":\"role\",\"value\":\"analyst\"},"
+                + "{\"issuer\":\"acs\",\"name\":\"role\",\"value\":\"admin\"}]";
+
+        @SuppressWarnings("unchecked")
+        Set<Attribute> deserialize = this.jsonUtils.deserialize(studentsAsJson, Set.class, Attribute.class);
+
+        Assert.assertNotNull(deserialize);
+        Assert.assertEquals(deserialize.size(), 2);
+
+        Iterator<Attribute> iterator = deserialize.iterator();
+        String value1 = iterator.next().getValue();
+        String value2 = iterator.next().getValue();
+        Assert.assertTrue(value1.equals("analyst") || value1.equals("admin"));
+        Assert.assertTrue(value2.equals("analyst") || value2.equals("admin"));
+    }
+
+    @Test
+    public void testDeserializeFromFile() {
+        Student student = this.jsonUtils.deserializeFromFile("student.json", Student.class);
+        assertStudent(student);
+    }
+
+    @Test
+    public void testDeserializeFromNotFoundFile() {
+        Assert.assertNull(this.jsonUtils.deserializeFromFile("student-no-found.json", Student.class));
+    }
+
+    @Test
+    public void testReadJsonNodeFromFile() {
+        JsonNode studentJsonNode = this.jsonUtils.readJsonNodeFromFile("student.json");
+        assertStudentJsonNode(studentJsonNode);
+    }
+
+    @Test
+    public void testReadJsonNodeFromObject() {
+        JsonNode studentJsonNode = this.jsonUtils.readJsonNodeFromObject(createStudent());
+        assertStudentJsonNode(studentJsonNode);
+    }
+
+    @Test
+    public void testSerialize() {
+        Student student = createStudent();
+        String serializedStudent = this.jsonUtils.serialize(student);
+        Assert.assertNotNull(serializedStudent);
+    }
+
+    @Test
+    public void testDoNotSerializaNullProperties() {
+        PolicySet ps = new PolicySet();
+        String serializedObject = this.jsonUtils.serialize(ps);
+        Assert.assertNotNull(serializedObject);
+        Assert.assertFalse(serializedObject.contains("null"));
+    }
+
+    private void assertStudent(final Student student) {
+        Assert.assertNotNull(student);
+        Assert.assertEquals(student.getId(), "S00001");
+        Assert.assertEquals(student.getName(), "Joe");
+        Assert.assertEquals(student.getAge(), STUDENT_AGE);
+        Assert.assertEquals(student.getCourses().size(), 2);
+        Assert.assertEquals(student.getCourses().get(0), "Math");
+        Assert.assertEquals(student.getCourses().get(1), "Arts");
+    }
+
+    private void assertStudentJsonNode(final JsonNode studentJsonNode) {
+        Assert.assertNotNull(studentJsonNode);
+        Assert.assertEquals(studentJsonNode.findValuesAsText("id").get(0), "S00001");
+        Assert.assertEquals(studentJsonNode.findValuesAsText("name").get(0), "Joe");
+        Assert.assertEquals(studentJsonNode.findValuesAsText("age").get(0), "15");
+        Assert.assertEquals(studentJsonNode.findValue("courses").get(0).asText(), "Math");
+        Assert.assertEquals(studentJsonNode.findValue("courses").get(1).asText(), "Arts");
+    }
+
+    private Student createStudent() {
+        Student s = new Student();
+        s.setAge(STUDENT_AGE);
+        s.setName("Joe");
+        s.setId("S00001");
+        s.setCourses(Arrays.asList("Math", "Arts"));
+        return s;
+    }
+
+    private static class Student {
+        private String id;
+        private int age;
+        private String name;
+        private List<String> courses;
+
+        Student() {
+        }
+
+        public String getId() {
+            return this.id;
+        }
+
+        public void setId(final String id) {
+            this.id = id;
+        }
+
+        public int getAge() {
+            return this.age;
+        }
+
+        public void setAge(final int age) {
+            this.age = age;
+        }
+
+        public String getName() {
+            return this.name;
+        }
+
+        public void setName(final String name) {
+            this.name = name;
+        }
+
+        public List<String> getCourses() {
+            return this.courses;
+        }
+
+        public void setCourses(final List<String> courses) {
+            this.courses = courses;
+        }
+
+    }
+}
diff --git a/commons/src/test/resources/log4j.properties b/commons/src/test/resources/log4j.properties
new file mode 100644
index 0000000..b75ffc9
--- /dev/null
+++ b/commons/src/test/resources/log4j.properties
@@ -0,0 +1,33 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+#
+# The logging properties used for testing
+#
+log4j.rootLogger=INFO, out
+
+# CONSOLE appender not used by default
+log4j.appender.out=org.apache.log4j.ConsoleAppender
+log4j.appender.out.layout=org.apache.log4j.PatternLayout
+log4j.appender.out.layout.ConversionPattern=[%30.30t] %-30.30c{1} %-5p %m%n
+
+# File appender
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d %-5p %c{1} - %m %n
+log4j.appender.file.file=target/camel-test.log
diff --git a/commons/src/test/resources/policy-1.json b/commons/src/test/resources/policy-1.json
new file mode 100644
index 0000000..c5e6ac7
--- /dev/null
+++ b/commons/src/test/resources/policy-1.json
@@ -0,0 +1,187 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uri.placeHolder('site_id'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read an asset if they are a member of the asset group.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can modify a site if they are assigned to the site and they are a manager.",
+            "target" : {
+                "name" : "When an operator modifies a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "PUT",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "role" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uri.placeHolder('site_id'))" },
+                { "name" : "is a manager",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'role'), 'manager')" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can create a case if they own the alarm and they are members of the asset group.",
+            "target" : {
+                "name" : "When an operator creates an case for an asset alarm",
+                "resource" : {
+                    "name" : "Asset alarm cases",
+                    "uriTemplate" : "/assets/{asset_id}/alarms/{alarm_id}/cases",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "owner" }
+                    ]
+                },
+                "action" : "POST",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "name_id" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" },
+                { "name" : "is owner of",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'name_id'), resource.attributes('https://acs.attributes.int', 'owner'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can see all sites that they are assigned to",
+            "target" : {
+                "name" : "When an operator reads all sites",
+                "resource" : {
+                    "name" : "Sites",
+                    "uriTemplate" : "/sites"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can see all assets that are assigned to their asset groups",
+            "target" : {
+                "name" : "When an operator reads all assets",
+                "resource" : {
+                    "name" : "Assets",
+                    "uriTemplate" : "/assets"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Administrators can get anything",
+            "target" : {
+                "name" : "When an administrator reads something",
+                "action" : "GET",
+                "subject" : {
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "role" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is an administrator",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'role'), 'admin')" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Everyone can read public assets",
+            "target" : {
+                "name" : "When anyone reads public assets",
+                "resource" : {
+                    "name" : "Assets",
+                    "uriTemplate" : "/public"
+                },
+                "action" : "GET"
+            },
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Deny all other operations by default",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/commons/src/test/resources/policy-no-match.json b/commons/src/test/resources/policy-no-match.json
new file mode 100644
index 0000000..c86c88e
--- /dev/null
+++ b/commons/src/test/resources/policy-no-match.json
@@ -0,0 +1,153 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uri.placeHolder('site_id'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read an asset if they are a member of the asset group.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can modify a site if they are assigned to the site and they are a manager.",
+            "target" : {
+                "name" : "When an operator modifies a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "PUT",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "role" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uri.placeHolder('site_id'))" },
+                { "name" : "is a manager",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'role'), 'manager')" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can create a case if they own the alarm and they are members of the asset group.",
+            "target" : {
+                "name" : "When an operator creates an case for an asset alarm",
+                "resource" : {
+                    "name" : "Asset alarm cases",
+                    "uriTemplate" : "/assets/{asset_id}/alarms/{alarm_id}/cases",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "owner" }
+                    ]
+                },
+                "action" : "POST",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "name_id" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" },
+                { "name" : "is owner of",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'name_id'), resource.attributes('https://acs.attributes.int', 'owner'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can see all sites that they are assigned to",
+            "target" : {
+                "name" : "When an operator reads all sites",
+                "resource" : {
+                    "name" : "Sites",
+                    "uriTemplate" : "/sites"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can see all assets that are assigned to their asset groups",
+            "target" : {
+                "name" : "When an operator reads all assets",
+                "resource" : {
+                    "name" : "Assets",
+                    "uriTemplate" : "/assets"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/commons/src/test/resources/student.json b/commons/src/test/resources/student.json
new file mode 100644
index 0000000..1d83bff
--- /dev/null
+++ b/commons/src/test/resources/student.json
@@ -0,0 +1 @@
+{"id":"S00001","age":15,"name":"Joe","courses":["Math","Arts"]}
\ No newline at end of file
diff --git a/commons/src/test/resources/subject.json b/commons/src/test/resources/subject.json
new file mode 100644
index 0000000..20677c7
--- /dev/null
+++ b/commons/src/test/resources/subject.json
@@ -0,0 +1,33 @@
+[
+	{
+		"uri" : "/tenant/ge/subject/dave",
+		"subjectIdentifier" : "dave",
+		"attributes" : [
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "group",
+		      "value": "admin" 
+		    }
+		    ,
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "department",
+		       "value": "sales" 
+		    }
+		]
+	}
+	,
+	{
+		"uri" : "/tenant/ge/subject/vineet",
+		"subjectIdentifier" : "vineet",
+		"attributes" : [
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "group",
+		      "value": "admin" 
+		    }
+		    ,
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "department",
+		       "value": "sales" 
+		    }
+		]
+	}
+]
diff --git a/copyrights.sh b/copyrights.sh
new file mode 100755
index 0000000..5f11621
--- /dev/null
+++ b/copyrights.sh
@@ -0,0 +1,219 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+#!/usr/bin/env bash
+
+{ set -e; } 2> /dev/null
+
+function usage {
+    echo "Usage: $( python -c "import os; print os.path.abspath('${BASH_SOURCE[0]}')" ) [--debug] [(-d | --delete-copyright-headers) | (-u | --upsert-copyright-headers)] [(-f | --file) <filename>]"
+}
+
+function read_args {
+    while (( "$#" )); do
+        case "$1" in
+            -d|--delete-copyright-headers)
+                DELETE_COPYRIGHTS='true'
+                ;;
+            -u|--upsert-copyright-headers)
+                UPSERT_COPYRIGHTS='true'
+                ;;
+            -a|--normalize-authors)
+                NORMALIZE_AUTHORS='true'
+                ;;
+            --debug)
+                DEBUG='true'
+                ;;
+            -f|--file)
+                shift
+                SRC_FILE="$1"
+                echo -e "Modifying source file: ${SRC_FILE}\n"
+                ;;
+            *)
+                echo "Unknown option: ${1}"
+                usage
+                exit 2
+                ;;
+        esac
+        shift
+    done
+}
+
+NEWLINE='
+'
+COPYRIGHT_HEADER_TITLE="Copyright $( date +'%Y' ) General Electric Company"
+COPYRIGHT_HEADER_BODY="${COPYRIGHT_HEADER_TITLE}
+\n
+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
+\n
+    http://www.apache.org/licenses/LICENSE-2.0
+\n
+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.
+\n
+SPDX-License-Identifier: Apache-2.0
+"
+
+function generate_copyright_header {
+    if [[ "$1" == 'java' || "$1" == 'groovy' ]]; then
+        BEGINNING_COMMENT_MARKER='/'
+        CONTINUING_COMMENT_MARKER='*'
+        ENDING_COMMENT_MARKER='/'
+        BEGINNING_COMMENT_LINE="$( printf -- "${CONTINUING_COMMENT_MARKER}%.0s" $( seq 1 79 ) )"
+    elif [[ "$1" == 'xml' ]]; then
+        BEGINNING_COMMENT_MARKER='<!'
+        CONTINUING_COMMENT_MARKER='-'
+        ENDING_COMMENT_MARKER='>'
+        BEGINNING_COMMENT_LINE="$( printf -- "${CONTINUING_COMMENT_MARKER}%.0s" $( seq 1 2 ) )"
+    elif [[ "$1" == 'sh' || "$1" == 'properties' ]]; then
+        BEGINNING_COMMENT_MARKER='#'
+        CONTINUING_COMMENT_MARKER="$BEGINNING_COMMENT_MARKER"
+        ENDING_COMMENT_MARKER="$CONTINUING_COMMENT_MARKER"
+        BEGINNING_COMMENT_LINE="$( printf -- "${CONTINUING_COMMENT_MARKER}%.0s" $( seq 1 79 ) )"
+    else
+        return
+    fi
+
+    ENDING_COMMENT_LINE="$BEGINNING_COMMENT_LINE"
+
+    COPYRIGHT_HEADER="$BEGINNING_COMMENT_MARKER"
+    COPYRIGHT_HEADER="${COPYRIGHT_HEADER}${BEGINNING_COMMENT_LINE}${NEWLINE}"
+
+    IFS="$NEWLINE"
+    for l in $COPYRIGHT_HEADER_BODY; do
+        if [[ "$BEGINNING_COMMENT_MARKER" != "$CONTINUING_COMMENT_MARKER" ]]; then
+            COPYRIGHT_HEADER="${COPYRIGHT_HEADER} "
+        fi
+        COPYRIGHT_HEADER="${COPYRIGHT_HEADER}${CONTINUING_COMMENT_MARKER}"
+        if [[ "$l" == '\n' ]]; then
+            COPYRIGHT_HEADER="${COPYRIGHT_HEADER}${NEWLINE}"
+        else
+            COPYRIGHT_HEADER="${COPYRIGHT_HEADER} ${l}${NEWLINE}"
+        fi
+    done
+
+    if [[ "$BEGINNING_COMMENT_MARKER" != "$CONTINUING_COMMENT_MARKER" ]]; then
+        COPYRIGHT_HEADER="${COPYRIGHT_HEADER} "
+    fi
+    COPYRIGHT_HEADER="${COPYRIGHT_HEADER}${ENDING_COMMENT_LINE}${ENDING_COMMENT_MARKER}${NEWLINE}"
+
+    echo "$COPYRIGHT_HEADER"
+}
+
+function delete_copyright {
+    if [[ "$1" == 'java' || "$1" == 'groovy' ]]; then
+        perl -i -pe 'BEGIN{undef $/;} s/\/\*{79}?\s*?\*\s*?Copyright.*?(\n\s*?\*.*?)+?\n\s*?\*{79}?\/\n{1,}//' "$2"
+    elif [[ "$1" == 'xml' ]]; then
+        perl -i -pe 'BEGIN{undef $/;} s/<!-{2}?\s*?-\s*?Copyright.*?(\n\s*?-.*?)+?\n\s*?-{2}?>\n{1,}//' "$2"
+    elif [[ "$1" == 'sh' || "$1" == 'properties' ]]; then
+        perl -i -pe 'BEGIN{undef $/;} s/#{80}?\s*?#\s*?Copyright.*?(\n\s*?#.*?)+?\n\s*?#{80}?\n{1,}//' "$2"
+    fi
+}
+
+function upsert_copyright {
+    delete_copyright "$1" "$2"
+
+    local TMP_FILE="${2}.__tmp__"
+
+    if [[ "$1" == 'xml' ]]; then
+        echo -e '<?xml version="1.0" encoding="UTF-8"?>\n' > "$TMP_FILE"
+    fi
+
+    echo "$( generate_copyright_header "$1" )" >> "$TMP_FILE"
+    echo '' >> "$TMP_FILE"
+
+    if [[ "$1" == 'xml' ]]; then
+        cat "$2" | awk '/<\?xml/{y=1;next}y' | sed '/./,$!d' >> "$TMP_FILE"
+    else
+        cat "$2" >> "$TMP_FILE"
+    fi
+
+    mv "$TMP_FILE" "$2"
+
+    if [[ "$1" == 'sh' ]]; then
+        chmod a+x "$2"
+    fi
+}
+
+function normalize_authors {
+    perl -i -pe 's/(\@author\s*)\d{1,}/$1acs-engineers\@ge.com/' "$2"
+}
+
+function modify_copyright_in_file {
+    FILENAME="$( basename "$1" )"
+    EXTENSION="${FILENAME##*.}"
+    if [[ -n "$DELETE_COPYRIGHTS" ]]; then
+        echo "Deleting copyrights from file: ${1} with extension: .${EXTENSION}"
+        delete_copyright "$EXTENSION" "$1"
+    elif [[ -n "$UPSERT_COPYRIGHTS" ]]; then
+        echo "Upserting copyrights in file: ${1} with extension: .${EXTENSION}"
+        upsert_copyright "$EXTENSION" "$1"
+    fi
+
+    if [[ -n "$NORMALIZE_AUTHORS" ]]; then
+        echo "Normalizing author information in file: ${1} with extension: .${EXTENSION}"
+        normalize_authors "$EXTENSION" "$1"
+    fi
+}
+
+unset DELETE_COPYRIGHTS
+unset UPSERT_COPYRIGHTS
+unset NORMALIZE_AUTHORS
+unset DEBUG
+unset SRC_FILE
+
+read_args "$@"
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+if [[ -n "$DEBUG" ]]; then
+    echo 'The following options are set:'
+    echo "  DELETE_COPYRIGHTS: ${DELETE_COPYRIGHTS}"
+    echo "  UPSERT_COPYRIGHTS: ${UPSERT_COPYRIGHTS}"
+    echo "  NORMALIZE_AUTHORS: ${NORMALIZE_AUTHORS}"
+    echo "  DEBUG: ${DEBUG}"
+    echo "  SRC_FILE: ${SRC_FILE}"
+    echo "  DIR: ${DIR}"
+    echo ''
+fi
+
+if [[ -n "$DELETE_COPYRIGHTS" && -n "$UPSERT_COPYRIGHTS" ]]; then
+    echo "Can't specify the deletion and upsert options at the same time"
+    usage
+    exit 2
+fi
+
+if [[ -n "$DEBUG" ]]; then
+    generate_copyright_header 'java'
+    generate_copyright_header 'groovy'
+    generate_copyright_header 'sh'
+    generate_copyright_header 'xml'
+fi
+
+if [[ -z "$SRC_FILE" ]]; then
+    for f in $( find "$DIR" \( -not -path '*/\.*' -and -not -path '*/failsafe*' -and -not -path '*/surefire*' \) -type f \( -iname '*.groovy' -or -iname '*.java' -or -iname '*.sh' -or -iname '*.properties' -or -iname '*.xml' \) ); do
+        modify_copyright_in_file "$f"
+    done
+else
+    modify_copyright_in_file "$SRC_FILE"
+fi
diff --git a/docs/.emacs.d/config.org b/docs/.emacs.d/config.org
new file mode 100644
index 0000000..9af1387
--- /dev/null
+++ b/docs/.emacs.d/config.org
@@ -0,0 +1,574 @@
+#+TITLE: Emacs Configuration
+#+AUTHOR: Daniel Mai
+#+AUTHOR: Dariush Amiri
+
+* Overview
+  This emacs setup includes the bulk of [[https://github.com/danielmai/.emacs.d][Daniel Mai's emacs configuration]]. I have tweaked some of the key bindings because I don't use a Dvorak keyboard layout. I've also added some additional babel handling for =http= and =restclient= code blocks. To install emacs and use this configuration:
+  - Copy this folder in your home directory
+  - Install emacs:
+    - OSX you can find emacs here: https://emacsformacosx.com/ or you can use brew for the installation
+    - Ubuntu =sudo apt-get install emacs24=
+  - Fire 'er up. If you like running emacs in a terminal you can create an alias to =emacs -nw=.
+* Personal Information
+
+#+begin_src emacs-lisp
+(setq user-full-name "your name"
+      user-mail-address "you@whatever.example.org")
+#+end_src
+
+* Theme
+** Cyberpunk theme
+
+The [[https://github.com/n3mo/cyberpunk-theme.el][cyberpunk theme]] is dark and colorful. However, I don't like the
+boxes around the mode line.
+
+#+begin_src emacs-lisp
+(use-package cyberpunk-theme
+  :if (window-system)
+  :ensure t
+  :init
+  (progn
+    (load-theme 'cyberpunk t)
+    (set-face-attribute `mode-line nil
+                        :box nil)
+    (set-face-attribute `mode-line-inactive nil
+                        :box nil)))
+#+end_src
+
+I tend to switch themes more often than normal. For example, switching
+to a lighter theme (such as the default) or to a different theme
+depending on the time of day or my mood. Normally, switching themes is
+a multi-step process with ~disable-theme~ and ~load-theme~. The
+~switch-theme~ function will do that in one swoop. I just choose which
+theme I want to go to.
+
+** Solarized theme
+
+Here's some configuration for [[https://github.com/bbatsov/solarized-emacs/][bbatsov's solarized themes]].
+
+#+begin_src emacs-lisp
+(use-package solarized-theme
+  :defer 10
+  :init
+  (setq solarized-use-variable-pitch nil)
+  :ensure t)
+#+end_src
+
+** Monokai theme
+
+#+begin_src emacs-lisp :tangle no
+(use-package monokai-theme
+  :if (window-system)
+  :ensure t
+  :init
+  (setq monokai-use-variable-pitch nil))
+#+end_src
+
+** Waher theme
+
+#+begin_src emacs-lisp :tangle no
+(use-package waher-theme
+  if (window-system)
+  :ensure t
+  :init
+  (load-theme 'waher))
+#+end_src
+
+** Convenient theme functions
+
+#+begin_src emacs-lisp
+(defun switch-theme (theme)
+  "Disables any currently active themes and loads THEME."
+  ;; This interactive call is taken from `load-theme'
+  (interactive
+   (list
+    (intern (completing-read "Load custom theme: "
+                             (mapc 'symbol-name
+                                   (custom-available-themes))))))
+  (let ((enabled-themes custom-enabled-themes))
+    (mapc #'disable-theme custom-enabled-themes)
+    (load-theme theme t)))
+
+(defun disable-active-themes ()
+  "Disables any currently active themes listed in `custom-enabled-themes'."
+  (interactive)
+  (mapc #'disable-theme custom-enabled-themes))
+
+(bind-key "C-c t s" 'switch-theme)
+(bind-key "C-c t d" 'disable-active-themes)
+#+end_src
+
+* Font
+
+[[http://adobe-fonts.github.io/source-code-pro/][Source Code Pro]] is a nice monospaced font.
+
+To install it on OS X, you can use Homebrew with [[http://caskroom.io/][Homebrew Cask]].
+
+#+begin_src sh :tangle no
+# You may need to run these two lines if you haven't set up Homebrew
+# Cask and its fonts formula.
+brew install caskroom/cask/brew-cask
+brew tap caskroom/fonts
+
+brew cask install font-source-code-pro
+#+end_src
+
+And here's how we tell Emacs to use the font we want to use.
+
+#+begin_src emacs-lisp
+(add-to-list 'default-frame-alist
+             '(font . "Source Code Pro-14"))
+#+end_src
+
+* Sane defaults
+
+Let's start with some sane defaults, shall we?
+
+Sources for this section include [[https://github.com/magnars/.emacs.d/blob/master/settings/sane-defaults.el][Magnars Sveen]] and [[http://pages.sachachua.com/.emacs.d/Sacha.html][Sacha Chua]].
+
+#+begin_src emacs-lisp
+;; These functions are useful. Activate them.
+(put 'downcase-region 'disabled nil)
+(put 'upcase-region 'disabled nil)
+(put 'narrow-to-region 'disabled nil)
+(put 'dired-find-alternate-file 'disabled nil)
+
+;; Answering just 'y' or 'n' will do
+(defalias 'yes-or-no-p 'y-or-n-p)
+
+;; Keep all backup and auto-save files in one directory
+(setq backup-directory-alist '(("." . "~/.emacs.d/backups")))
+(setq auto-save-file-name-transforms '((".*" "~/.emacs.d/auto-save-list/" t)))
+
+;; UTF-8 please
+(setq locale-coding-system 'utf-8) ; pretty
+(set-terminal-coding-system 'utf-8) ; pretty
+(set-keyboard-coding-system 'utf-8) ; pretty
+(set-selection-coding-system 'utf-8) ; please
+(prefer-coding-system 'utf-8) ; with sugar on top
+(setq-default indent-tabs-mode nil)
+
+;; Turn off the blinking cursor
+(blink-cursor-mode -1)
+
+(setq-default indent-tabs-mode nil)
+(setq-default indicate-empty-lines t)
+
+;; Don't count two spaces after a period as the end of a sentence.
+;; Just one space is needed.
+(setq sentence-end-double-space nil)
+
+;; delete the region when typing, just like as we expect nowadays.
+(delete-selection-mode t)
+
+(show-paren-mode t)
+
+(column-number-mode t)
+
+(global-visual-line-mode)
+(diminish 'visual-line-mode)
+
+(setq uniquify-buffer-name-style 'forward)
+
+;; -i gets alias definitions from .bash_profile
+(setq shell-command-switch "-ic")
+
+;; Don't beep at me
+(setq visible-bell t)
+#+end_src
+
+The following function for ~occur-dwim~ is taken from [[https://github.com/abo-abo][Oleh Krehel]] from
+[[http://oremacs.com/2015/01/26/occur-dwim/][his blog post at (or emacs]]. It takes the current region or the symbol
+at point as the default value for occur.
+
+#+begin_src emacs-lisp
+(defun occur-dwim ()
+  "Call `occur' with a sane default."
+  (interactive)
+  (push (if (region-active-p)
+            (buffer-substring-no-properties
+             (region-beginning)
+             (region-end))
+          (thing-at-point 'symbol))
+        regexp-history)
+  (call-interactively 'occur))
+
+(bind-key "M-s o" 'occur-dwim)
+#+end_src
+
+Here we make page-break characters look pretty, instead of appearing
+as =^L= in Emacs. [[http://ericjmritz.name/2015/08/29/using-page-breaks-in-gnu-emacs/][Here's an informative article called "Using
+Page-Breaks in GNU Emacs" by Eric J. M. Ritz.]]
+
+#+begin_src emacs-lisp
+(use-package page-break-lines
+  :ensure t)
+#+end_src
+
+* List buffers
+
+ibuffer is the improved version of list-buffers.
+
+#+begin_src emacs-lisp
+;; make ibuffer the default buffer lister.
+(defalias 'list-buffers 'ibuffer)
+#+end_src
+
+
+source: http://ergoemacs.org/emacs/emacs_buffer_management.html
+
+#+begin_src emacs-lisp
+(add-hook 'dired-mode-hook 'auto-revert-mode)
+
+;; Also auto refresh dired, but be quiet about it
+(setq global-auto-revert-non-file-buffers t)
+(setq auto-revert-verbose nil)
+#+end_src
+
+source: [[http://whattheemacsd.com/sane-defaults.el-01.html][Magnars Sveen]]
+
+* Recentf
+
+#+begin_src emacs-lisp
+(use-package recentf
+  :bind ("C-x C-r" . helm-recentf)
+  :config
+  (recentf-mode t)
+  (setq recentf-max-saved-items 200))
+#+end_src
+
+* Org mode
+
+Truly the way to [[http://orgmode.org/][live life in plain text]]. I mainly use it to take
+notes and save executable source blocks. I'm also starting to make use
+of its agenda, timestamping, and capturing features.
+
+It goes without saying that I also use it to manage my Emacs config.
+
+** Org activation bindings
+
+Set up some global key bindings that integrate with Org Mode features.
+
+#+begin_src emacs-lisp
+(bind-key "C-c l" 'org-store-link)
+(bind-key "C-c c" 'org-capture)
+(bind-key "C-c a" 'org-agenda)
+#+end_src
+
+*** Org agenda
+
+Learned about [[https://github.com/sachac/.emacs.d/blob/83d21e473368adb1f63e582a6595450fcd0e787c/Sacha.org#org-agenda][this =delq= and =mapcar= trick from Sacha Chua's config]].
+
+#+begin_src emacs-lisp
+(setq org-agenda-files
+      (delq nil
+            (mapcar (lambda (x) (and (file-exists-p x) x))
+                    '("~/Dropbox/Agenda"))))
+#+end_src
+
+*** Org capture
+
+#+begin_src emacs-lisp
+(bind-key "C-c c" 'org-capture)
+(setq org-default-notes-file "~/Dropbox/Notes/notes.org")
+#+end_src
+
+** Org setup
+
+Speed commands are a nice and quick way to perform certain actions
+while at the beginning of a heading. It's not activated by default.
+
+See the doc for speed keys by checking out [[elisp:(info%20"(org)%20speed%20keys")][the documentation for
+speed keys in Org mode]].
+
+#+begin_src emacs-lisp
+(setq org-use-speed-commands t)
+#+end_src
+
+#+begin_src emacs-lisp
+(setq org-image-actual-width 550)
+#+end_src
+
+#+BEGIN_SRC emacs-lisp
+(setq org-highlight-latex-and-related '(latex script entities))
+#+END_SRC
+
+** Org tags
+
+The default value is -77, which is weird for smaller width windows.
+I'd rather have the tags align horizontally with the header. 45 is a
+good column number to do that.
+
+#+begin_src emacs-lisp
+(setq org-tags-column 45)
+#+end_src
+
+** Org babel languages
+
+#+begin_src emacs-lisp
+(org-babel-do-load-languages
+ 'org-babel-load-languages
+ '((python . t)
+   (C . t)
+   (calc . t)
+   (python . t)
+   (ditaa . t)
+   (latex . t)
+   (java . t)
+   (ruby . t)
+   (lisp . t)
+   (scheme . t)
+   (sh . t)
+   (sqlite . t)
+   (js . t)))
+
+(defun my-org-confirm-babel-evaluate (lang body)
+  "Do not confirm evaluation for these languages."
+  (not (or (string= lang "C")
+           (string= lang "java")
+           (string= lang "python")
+           (string= lang "emacs-lisp")
+           (string= lang "sqlite"))))
+(setq org-confirm-babel-evaluate 'my-org-confirm-babel-evaluate)
+#+end_src
+
+** Org babel/source blocks
+
+I like to have source blocks properly syntax highlighted and with the
+editing popup window staying within the same window so all the windows
+don't jump around. Also, having the top and bottom trailing lines in
+the block is a waste of space, so we can remove them.
+
+I noticed that fontification doesn't work with markdown mode when the
+block is indented after editing it in the org src buffer---the leading
+#s for headers don't get fontified properly because they appear as Org
+comments. Setting ~org-src-preserve-indentation~ makes things
+consistent as it doesn't pad source blocks with leading spaces.
+
+#+begin_src emacs-lisp
+(setq org-src-fontify-natively t
+      org-src-window-setup 'current-window
+      org-src-strip-leading-and-trailing-blank-lines t
+      org-src-preserve-indentation t
+      org-src-tab-acts-natively t)
+#+end_src
+
+** Org exporting
+
+*** Pandoc exporter
+
+Pandoc converts between a huge number of different file formats. 
+
+#+begin_src emacs-lisp
+(use-package ox-pandoc
+  :no-require t
+  :defer 10
+  :ensure t)
+#+end_src
+*** LaTeX exporting
+
+I've had issues with getting BiBTeX to work correctly with the LaTeX exporter for PDF exporting. By changing the command to `latexmk` references appear in the PDF output like they should. Source: http://tex.stackexchange.com/a/161619.
+
+#+BEGIN_SRC emacs-lisp
+(setq org-latex-pdf-process (list "latexmk -pdf %f"))
+#+END_SRC
+* Tramp
+
+#+begin_src emacs-lisp :tangle no
+(use-package tramp)
+#+end_src
+
+* Locate
+
+Using OS X Spotlight within Emacs by modifying the ~locate~ function.
+
+I usually use [[*Helm][~helm-locate~]], which does live updates the spotlight
+search list as you type a query.
+
+#+begin_src emacs-lisp
+;; mdfind is the command line interface to Spotlight
+(setq locate-command "mdfind")
+#+end_src
+
+* Ido
+
+#+begin_src emacs-lisp
+(use-package ido
+  :init
+  (setq ido-enable-flex-matching t)
+  (setq ido-everywhere t)
+  (ido-mode t)
+  (use-package ido-vertical-mode
+    :ensure t
+    :defer t
+    :init (ido-vertical-mode 1)
+    (setq ido-vertical-define-keys 'C-n-and-C-p-only)))
+#+end_src
+
+* Whitespace mode
+
+#+begin_src emacs-lisp
+(use-package whitespace
+  :bind ("C-c w" . whitespace-mode))
+#+end_src
+
+* ELPA packages
+These are the packages that are not built into Emacs.
+
+** Restclient Mode
+
+See [[http://emacsrocks.com/e15.html][Emacs Rocks! Episode 15]] to learn how restclient can help out with
+testing APIs from within Emacs. The HTTP calls you make in the buffer
+aren't constrainted within Emacs; there's the
+=restclient-copy-curl-command= to get the equivalent =curl= call
+string to keep things portable.
+
+#+begin_src emacs-lisp
+(use-package restclient
+  :ensure t
+  :mode ("\\.restclient\\'" . restclient-mode))
+#+end_src
+
+#+begin_src emacs-lisp
+(use-package ob-restclient
+  :ensure t)
+
+(org-babel-do-load-languages
+ 'org-babel-load-languages
+ '((restclient . t)))
+#+end_src
+
+Key command for base64 encoding a text region.
+
+#+begin_src emacs-lisp
+(bind-key "C-c e b" 'base64-encode-region)
+(bind-key "C-c d b" 'base64-decode-region)
+#+end_src
+
+** Shell Pop
+
+#+BEGIN_SRC emacs-lisp
+(use-package shell-pop
+  :ensure t
+  :bind ("C-c s" . shell-pop))
+#+END_SRC
+
+** Smartparens Mode
+
+#+begin_src emacs-lisp
+(use-package smartparens
+  :ensure t
+  :defer t
+  :diminish smartparens-mode
+  :config
+  (add-to-list 'sp--lisp-modes 'racket-mode)
+  (add-to-list 'sp--lisp-modes 'geiser-mode)
+  (require 'smartparens-config)
+
+  ;; Set up some pairings for org mode markup. These pairings won't
+  ;; activate by default; they'll only apply for wrapping regions.
+  (sp-local-pair 'org-mode "~" "~" :actions '(wrap))
+  (sp-local-pair 'org-mode "/" "/" :actions '(wrap))
+  (sp-local-pair 'org-mode "*" "*" :actions '(wrap)))
+#+end_src
+
+* Languages
+** C/Java
+
+I don't like the default way that Emacs handles indentation. For instance,
+
+#+begin_src C
+int main(int argc, char *argv[])
+{
+  /* What's with the brace alignment? */
+  if (check)
+    {
+    }
+  return 0;
+}
+#+end_src
+
+#+begin_src java
+switch (number)
+    {
+    case 1:
+        doStuff();
+        break;
+    case 2:
+        doStuff();
+        break;
+    default:
+        break;
+    }
+#+end_src
+
+Luckily, I can modify the way Emacs formats code with this configuration.
+
+#+begin_src emacs-lisp
+(defun my-c-mode-hook ()
+  (setq c-basic-offset 4)
+  (c-set-offset 'substatement-open 0)   ; Curly braces alignment
+  (c-set-offset 'case-label 4))         ; Switch case statements alignment
+
+(add-hook 'c-mode-hook 'my-c-mode-hook)
+(add-hook 'java-mode-hook 'my-c-mode-hook)
+#+end_src
+
+* Misc
+** Display Time
+
+When displaying the time with =display-time-mode=, I don't care about
+the load average.
+
+#+begin_src emacs-lisp
+(setq display-time-default-load-average nil)
+#+end_src
+
+** Display Battery Mode
+
+See the documentation for =battery-mode-line-format= for the format
+characters.
+
+#+begin_src emacs-lisp
+(setq battery-mode-line-format "[%b%p%% %t]")
+#+end_src
+
+** Docview keybindings
+
+Convenience bindings to use doc-view with the arrow keys.
+
+#+begin_src emacs-lisp
+(use-package doc-view
+  :commands doc-view-mode
+  :config
+  (define-key doc-view-mode-map (kbd "<right>") 'doc-view-next-page)
+  (define-key doc-view-mode-map (kbd "<left>") 'doc-view-previous-page))
+#+end_src
+
+** OS X scrolling
+
+#+begin_src emacs-lisp
+(setq mouse-wheel-scroll-amount (quote (0.01)))
+#+end_src
+
+** Emacsclient
+
+#+begin_src emacs-lisp
+(use-package server
+  :config
+  (server-start))
+#+end_src
+
+#+begin_src emacs-lisp
+#+end_src
+* HTTP
+** babel HTTP
+
+#+begin_src emacs-lisp
+(use-package ob-http
+  :ensure t)
+
+(org-babel-do-load-languages
+ 'org-babel-load-languages
+ '((http . t)))
+#+end_src
diff --git a/docs/.emacs.d/custom.el b/docs/.emacs.d/custom.el
new file mode 100644
index 0000000..7d58b67
--- /dev/null
+++ b/docs/.emacs.d/custom.el
@@ -0,0 +1,39 @@
+(custom-set-variables
+ ;; custom-set-variables was added by Custom.
+ ;; If you edit it by hand, you could mess it up, so be careful.
+ ;; Your init file should contain only one such instance.
+ ;; If there is more than one, they won't work right.
+ '(column-number-mode t)
+ '(custom-safe-themes
+   (quote
+    ("f0a99f53cbf7b004ba0c1760aa14fd70f2eabafe4e62a2b3cf5cabae8203113b" default)))
+ '(vc-annotate-background "#2B2B2B")
+ '(vc-annotate-color-map
+   (quote
+    ((20 . "#BC8383")
+     (40 . "#CC9393")
+     (60 . "#DFAF8F")
+     (80 . "#D0BF8F")
+     (100 . "#E0CF9F")
+     (120 . "#F0DFAF")
+     (140 . "#5F7F5F")
+     (160 . "#7F9F7F")
+     (180 . "#8FB28F")
+     (200 . "#9FC59F")
+     (220 . "#AFD8AF")
+     (240 . "#BFEBBF")
+     (260 . "#93E0E3")
+     (280 . "#6CA0A3")
+     (300 . "#7CB8BB")
+     (320 . "#8CD0D3")
+     (340 . "#94BFF3")
+     (360 . "#DC8CC3"))))
+ '(vc-annotate-very-old-color "#DC8CC3"))
+(custom-set-faces
+ ;; custom-set-faces was added by Custom.
+ ;; If you edit it by hand, you could mess it up, so be careful.
+ ;; Your init file should contain only one such instance.
+ ;; If there is more than one, they won't work right.
+ '(helm-bookmark-directory ((t (:inherit helm-ff-directory))))
+ '(helm-buffer-directory ((t (:inherit helm-ff-directory))))
+ '(helm-ff-directory ((t (:foreground "HotPink")))))
diff --git a/docs/.emacs.d/init.el b/docs/.emacs.d/init.el
new file mode 100644
index 0000000..60cf234
--- /dev/null
+++ b/docs/.emacs.d/init.el
@@ -0,0 +1,39 @@
+
+(setq gc-cons-threshold 400000000)
+
+;;; Begin initialization
+;; Turn off mouse interface early in startup to avoid momentary display
+(when window-system
+  (menu-bar-mode -1)
+  (tool-bar-mode -1)
+  (scroll-bar-mode -1)
+  (tooltip-mode -1))
+
+(setq inhibit-startup-message t)
+(setq initial-scratch-message "")
+
+;;; Set up package
+(require 'package)
+(add-to-list 'package-archives
+             '("melpa" . "http://melpa.org/packages/") t)
+(package-initialize)
+
+;;; Bootstrap use-package
+;; Install use-package if it's not already installed.
+;; use-package is used to configure the rest of the packages.
+(unless (package-installed-p 'use-package)
+  (package-refresh-contents)
+  (package-install 'use-package))
+
+;; From use-package README
+(eval-when-compile
+  (require 'use-package))
+(require 'diminish)                ;; if you use :diminish
+(require 'bind-key)
+;; (setq use-package-verbose t)
+(server-start)
+
+;;; Load the config
+(org-babel-load-file (concat user-emacs-directory "config.org"))
+
+(setq gc-cons-threshold 800000)
diff --git a/docs/acs-demo.org b/docs/acs-demo.org
new file mode 100644
index 0000000..88f9d2c
--- /dev/null
+++ b/docs/acs-demo.org
@@ -0,0 +1,785 @@
+#+TITLE: ACS Demo
+#+AUTHOR: Dario Amiri
+
+* Overview
+  This document provides basic information intended to get users started with Access Control Service (ACS). We will explore two use cases:
+  - Access control based on group membership.
+  - Access control based on dynamic roles. The roles are dynamic in the sense that a user's role will depend on the resources they are attempting to access. E.g. the user tom@ge.com is an analyst only when accessing resources that belong to a specific site.
+  
+  In order to use this document as the source for demonstration, you will have to install emacs and configure it as described [[file:.emacs.d/config.org][here]].
+
+** Technology Stack
+   The ACS technology makes heavy use of various Spring frameworks including:
+   - [[http://projects.spring.io/spring-boot/][Spring Boot]]
+   - [[http://projects.spring.io/spring-data-jpa/][Spring Data JPA]]
+   - [[http://projects.spring.io/spring-security-oauth/][Spring Security OAuth]]
+   
+   The ACS attribute store is a graph database built on:
+   - [[http://tinkerpop.apache.org/][Apache TinkerPop]]
+   - [[http://titan.thinkaurelius.com/][Titan]]
+
+   Persistence storage includes:
+   - [[https://www.postgresql.org/][PostgreSQL]]
+   - [[http://cassandra.apache.org/][Apache Cassandra]]
+
+#+begin_src ditaa :exports results :file architecture.png :cmdline -r
+                 /---------------------------------------\
+                 | cGRE     spring security oauth        |
+                 +---------------------------------------+
+                 | cGRE          spring mvc              |
++----------+     +-------------------+-------------------+     +----------+
+|{s}       |     | cGRE              | cBLU              |     |{s}       |
+| postgres +-----+ spring data JPA   | apache tinkerpop  +-----+ cassandra|
+|          |     +-------------------+-------------------+     |          |
++----------+     | cGRE              | cBLU              |     +----------+
+                 | spring boot       | titan db          |
+                 +-------------------+-------------------+
+                 | cPNK             java                 |
+                 \---------------------------------------/
+#+end_src
+
+#+RESULTS:
+[[file:architecture.png]]
+
+** Documentation
+   Comprehensive documentation for ACS is available [[https://www.predix.io/docs#IGyNp2eM][here]].
+
+
+* Setting up UAA
+** Getting and running UAA
+  - Clone UAA next to ACS project
+#+BEGIN_SRC sh
+git clone https://github.com/cloudfoundry/uaa ../uaa
+#+END_SRC
+
+  - Copy UAA config file. ACS requires a UAA configuration that uses asymmetric signing of tokens instead of the default symmetric singing.
+#+BEGIN_SRC sh
+cp acs-integration-tests/uaa/config/uaa.yml ../uaa/uaa/src/main/resources/uaa.yml
+#+END_SRC
+
+  - Start UAA
+#+BEGIN_SRC sh
+../uaa/gradlew run --info
+#+END_SRC
+
+** Use OAuth 2.0 client credentials to get admin token
+
+We'll use the UAA admin client to further configure UAA for our demo. You can get an admin access token by using the OAuth 2.0 client credentials grant type with the client id and secret submitted using HTTP basic auth.
+
+#+NAME: get_admin_token
+#+BEGIN_SRC http :exports both :pretty :noproxy
+POST http://localhost:8080/uaa/oauth/token
+Authorization: Basic YWRtaW46YWRtaW5zZWNyZXQ=
+
+grant_type=client_credentials
+#+END_SRC
+
+#+RESULTS: get_admin_token
+: {
+:   "jti": "ea09297877b54225b01c5a739d48b850",
+:   "scope": "clients.read clients.secret clients.write uaa.admin clients.admin scim.write scim.read",
+:   "expires_in": 43199,
+:   "token_type": "bearer",
+:   "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiJlYTA5Mjk3ODc3YjU0MjI1YjAxYzVhNzM5ZDQ4Yjg1MCIsInN1YiI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiY2xpZW50cy5yZWFkIiwiY2xpZW50cy5zZWNyZXQiLCJjbGllbnRzLndyaXRlIiwidWFhLmFkbWluIiwiY2xpZW50cy5hZG1pbiIsInNjaW0ud3JpdGUiLCJzY2ltLnJlYWQiXSwic2NvcGUiOlsiY2xpZW50cy5yZWFkIiwiY2xpZW50cy5zZWNyZXQiLCJjbGllbnRzLndyaXRlIiwidWFhLmFkbWluIiwiY2xpZW50cy5hZG1pbiIsInNjaW0ud3JpdGUiLCJzY2ltLnJlYWQiXSwiY2xpZW50X2lkIjoiYWRtaW4iLCJjaWQiOiJhZG1pbiIsImF6cCI6ImFkbWluIiwiZ3JhbnRfdHlwZSI6ImNsaWVudF9jcmVkZW50aWFscyIsInJldl9zaWciOiI5N2M1MDBiYiIsImlhdCI6MTQ2OTU2MzU0NiwiZXhwIjoxNDY5NjA2NzQ2LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvdWFhL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImFkbWluIiwiY2xpZW50cyIsInVhYSIsInNjaW0iXX0.SZvhSsc0qmquQU4ojkUJTiEt_HCkUFPbTPE233AX4jmuTXUkfAhyCToeL2huEF4qrPlZ5pPJ_pvNYwhKfu0xXmMnMDDce6XQ_e_FP3fJWuhyMQ8T2ZlUdn-W9d4O89RecPoSFpr8dp6ttnLbIr8d3_LWQQ80HsRdom8Z5ek6lr5hcNfCyxmSgKLa7rbb8fxd0NkIiDAkgxrGuT6Thr8hh0wqC5p5P8xv3Ncxaenado4GIZeQf7Ek9UTmMuarnD0BT6GYu3NRLlB9ZKy8j2MhoCr5fqTeA13ywm1Secph1L7wc2IaZMlFG9eo0njz7QamLLcBJWmsagENXMztOiI6NQ"
+: }
+
+Use Ruby, Python, or similar to parse the OAuth 2.0 JSON response and extract the JWT access token.
+
+#+NAME: admin_token
+#+BEGIN_SRC ruby :exports both :var access_token=get_admin_token
+  require 'json'
+  JSON.parse(access_token)['access_token']
+#+END_SRC
+
+#+RESULTS: admin_token
+: eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiJiMDkzYmYzZmVkYjY0YzU0OWM5MTBiNmJiMmNlYjg2NSIsInN1YiI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiY2xpZW50cy5yZWFkIiwiY2xpZW50cy5zZWNyZXQiLCJjbGllbnRzLndyaXRlIiwidWFhLmFkbWluIiwiY2xpZW50cy5hZG1pbiIsInNjaW0ud3JpdGUiLCJzY2ltLnJlYWQiXSwic2NvcGUiOlsiY2xpZW50cy5yZWFkIiwiY2xpZW50cy5zZWNyZXQiLCJjbGllbnRzLndyaXRlIiwidWFhLmFkbWluIiwiY2xpZW50cy5hZG1pbiIsInNjaW0ud3JpdGUiLCJzY2ltLnJlYWQiXSwiY2xpZW50X2lkIjoiYWRtaW4iLCJjaWQiOiJhZG1pbiIsImF6cCI6ImFkbWluIiwiZ3JhbnRfdHlwZSI6ImNsaWVudF9jcmVkZW50aWFscyIsInJldl9zaWciOiI5N2M1MDBiYiIsImlhdCI6MTQ2OTU2MzU3MiwiZXhwIjoxNDY5NjA2NzcyLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvdWFhL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImFkbWluIiwiY2xpZW50cyIsInVhYSIsInNjaW0iXX0.iwI9YI6I2CK-B706GazOsYfdM83E5DfUIW5nm0NGu0c6XjNgLZzFjwuMp_AQfZwcG15xU-QTeg6tITyiqml29mZin6R_VuxJPVva4HXuB2tpuNX7wGHQs6iVNSMQZKngfUuOBx-3VdnDmPiNfo9z9bKC9uxKuHYfFNGZe4KlWWk8yLcwSZL7Wai-hrfGkaPfKe7GpqeCR4wr7dOn-9BcVmfRgFF8si7asXAiRvOs5ESH74w_0AW5-yZYvFNHs8g77R6GxP_CURaqofgufrcfPAV3x9MnSRDPy19BC3d4BSBug8Nm8Aq6Zla1dylevOHi2aSY_-eLeNl-d1zQXJnLHw
+
+** Create ACS admin
+
+Create an OAuth client that we can use to consume the ACS RESTful API.
+
+#+NAME: create_acs_admin
+#+BEGIN_SRC http :exports both :pretty :var access_token=admin_token
+POST http://localhost:8080/uaa/oauth/clients
+Authorization: bearer ${access_token}
+Accept: application/json
+Content-Type: application/json
+
+{
+  "name" : "ACS admin",
+  "authorities" : [
+    "acs.attributes.read",
+    "acs.attributes.write",
+    "acs.policies.read",
+    "acs.policies.write",
+    "acs.zones.admin",
+    "predix-acs.zones.demo.admin",
+    "predix-acs.zones.demo.user" ],
+  "authorized_grant_types" : [ "client_credentials" ],
+  "client_id" : "acs_admin",
+  "client_secret" : "acs_admin_secret",
+  "scope" : [ "uaa.none" ]
+}
+#+END_SRC
+
+#+RESULTS: create_acs_admin
+#+begin_example
+{
+  "lastModified": 1469563591343,
+  "name": "ACS admin",
+  "authorities": [
+    "acs.policies.read",
+    "acs.policies.write",
+    "acs.attributes.read",
+    "predix-acs.zones.demo.user",
+    "acs.attributes.write",
+    "acs.zones.admin",
+    "predix-acs.zones.demo.admin"
+  ],
+  "action": "none",
+  "autoapprove": [],
+  "authorized_grant_types": [
+    "client_credentials"
+  ],
+  "resource_ids": [
+    "none"
+  ],
+  "client_id": "acs_admin",
+  "scope": [
+    "uaa.none"
+  ]
+}
+#+end_example
+
+
+* Setting up ACS
+** Running ACS
+  - Start ACS
+#+BEGIN_SRC sh
+service/start-acs-public-titan.sh
+#+END_SRC
+
+** Creating an ACS zone
+  - Get ACS admin token
+#+NAME: get_acs_admin_token
+#+BEGIN_SRC http :pretty :noproxy :exports both
+POST http://localhost:8080/uaa/oauth/token
+Authorization: Basic YWNzX2FkbWluOmFjc19hZG1pbl9zZWNyZXQ=
+
+grant_type=client_credentials
+#+END_SRC
+
+#+RESULTS: get_acs_admin_token
+: {
+:   "jti": "d937401207364668a83a2ed506f70ce0",
+:   "scope": "acs.policies.read acs.policies.write acs.attributes.read predix-acs.zones.demo.user acs.attributes.write acs.zones.admin predix-acs.zones.demo.admin",
+:   "expires_in": 43199,
+:   "token_type": "bearer",
+:   "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiJkOTM3NDAxMjA3MzY0NjY4YTgzYTJlZDUwNmY3MGNlMCIsInN1YiI6ImFjc19hZG1pbiIsImF1dGhvcml0aWVzIjpbImFjcy5wb2xpY2llcy5yZWFkIiwiYWNzLnBvbGljaWVzLndyaXRlIiwiYWNzLmF0dHJpYnV0ZXMucmVhZCIsInByZWRpeC1hY3Muem9uZXMuZGVtby51c2VyIiwiYWNzLmF0dHJpYnV0ZXMud3JpdGUiLCJhY3Muem9uZXMuYWRtaW4iLCJwcmVkaXgtYWNzLnpvbmVzLmRlbW8uYWRtaW4iXSwic2NvcGUiOlsiYWNzLnBvbGljaWVzLnJlYWQiLCJhY3MucG9saWNpZXMud3JpdGUiLCJhY3MuYXR0cmlidXRlcy5yZWFkIiwicHJlZGl4LWFjcy56b25lcy5kZW1vLnVzZXIiLCJhY3MuYXR0cmlidXRlcy53cml0ZSIsImFjcy56b25lcy5hZG1pbiIsInByZWRpeC1hY3Muem9uZXMuZGVtby5hZG1pbiJdLCJjbGllbnRfaWQiOiJhY3NfYWRtaW4iLCJjaWQiOiJhY3NfYWRtaW4iLCJhenAiOiJhY3NfYWRtaW4iLCJncmFudF90eXBlIjoiY2xpZW50X2NyZWRlbnRpYWxzIiwicmV2X3NpZyI6ImQ4NTlkYThmIiwiaWF0IjoxNDY5MTQ3OTE5LCJleHAiOjE0NjkxOTExMTksImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC91YWEvb2F1dGgvdG9rZW4iLCJ6aWQiOiJ1YWEiLCJhdWQiOlsiYWNzX2FkbWluIiwiYWNzLnBvbGljaWVzIiwiYWNzLmF0dHJpYnV0ZXMiLCJwcmVkaXgtYWNzLnpvbmVzLmRlbW8iLCJhY3Muem9uZXMiXX0.wLH3RTAEGP4MR6F6C53y02s3VtsR_DNXfSs0W3BMT4LkR2jMUJD5HBK7n2hXDhTgaRMtlsQQFWbGYTGfefjpFkmpwHsLfd1_yCxuqisDANF98ee9QbtU2ZOzstsoFkjopd-YeKphDTCLuccl19ToRMYkNTEGV1DswEJjMAU3bnF9VQOleHyS308k-jOEOXiCZnhHihoZbUDv1kn6j8RZusfFZqVm6zowpKaKg8EewdA1duPKlhn_5UWVPhmSYdYTCFyEYG9--RpPmc16L0hojpbUwDDsuTUgyVbR87USfMxBmrBGANiT-Dz8QBysfBhwweA1EFpBjNrOnO2rA8Sz9w"
+: }
+
+#+NAME: acs_admin_token
+#+BEGIN_SRC ruby :exports both :var access_token=get_acs_admin_token
+  require 'json'
+  JSON.parse(access_token)['access_token']
+#+END_SRC
+
+#+RESULTS: acs_admin_token
+: eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiJkZTJlYjU4Y2VhMGU0ODJlODE2NTRmNzZiYmJiNzA3MCIsInN1YiI6ImFjc19hZG1pbiIsImF1dGhvcml0aWVzIjpbImFjcy5wb2xpY2llcy5yZWFkIiwiYWNzLnBvbGljaWVzLndyaXRlIiwiYWNzLmF0dHJpYnV0ZXMucmVhZCIsInByZWRpeC1hY3Muem9uZXMuZGVtby51c2VyIiwiYWNzLmF0dHJpYnV0ZXMud3JpdGUiLCJhY3Muem9uZXMuYWRtaW4iLCJwcmVkaXgtYWNzLnpvbmVzLmRlbW8uYWRtaW4iXSwic2NvcGUiOlsiYWNzLnBvbGljaWVzLnJlYWQiLCJhY3MucG9saWNpZXMud3JpdGUiLCJhY3MuYXR0cmlidXRlcy5yZWFkIiwicHJlZGl4LWFjcy56b25lcy5kZW1vLnVzZXIiLCJhY3MuYXR0cmlidXRlcy53cml0ZSIsImFjcy56b25lcy5hZG1pbiIsInByZWRpeC1hY3Muem9uZXMuZGVtby5hZG1pbiJdLCJjbGllbnRfaWQiOiJhY3NfYWRtaW4iLCJjaWQiOiJhY3NfYWRtaW4iLCJhenAiOiJhY3NfYWRtaW4iLCJncmFudF90eXBlIjoiY2xpZW50X2NyZWRlbnRpYWxzIiwicmV2X3NpZyI6ImQ4NTlkYThmIiwiaWF0IjoxNDY5MTQ3OTQxLCJleHAiOjE0NjkxOTExNDEsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC91YWEvb2F1dGgvdG9rZW4iLCJ6aWQiOiJ1YWEiLCJhdWQiOlsiYWNzX2FkbWluIiwiYWNzLnBvbGljaWVzIiwiYWNzLmF0dHJpYnV0ZXMiLCJwcmVkaXgtYWNzLnpvbmVzLmRlbW8iLCJhY3Muem9uZXMiXX0.lNUF-oLqCq0EAHRmgY-zXunVkMnNewarq2PC0KrC_SbD3NGozDEDdKaW9oCIeGJTy92M3LEk6CuF96F7Jyn4VJtwKGsounkaKB9WHpyZVYDa7H9eMBasCOO75YKqum2Pi288wbV5anUdzmVF136QNtr3asPyEfFT6qTins3-6Tm1GjxzbYLCzbVW53W-RsbQuXV750AnMf4AU1Odo5UJIQZgomqQnvWofoLPndaXK0lYL_k_d3Fhntflmk0qvYXlUS6U_FfX8GsqjKnlaL2gbsRVKNM9JdMU4np6WDT53b6USotuHC2HTIDZ36tnT8AhkcycFqZzd7nblcST_G5hjQ
+
+  - Create ACS zone. ACS is a multi-tenant service. For /mysterious/ reasons, we've adopted the term "zone" for each separate ACS tenancy. Before you can start using ACS you have to create a zone. In this example, we create a zone called "demo".
+
+#+NAME: create_acs_zone
+#+BEGIN_SRC http :exports both :var access_token=acs_admin_token
+PUT http://localhost:8181/v1/zone/demo
+Authorization: bearer ${access_token}
+Accept: application/json
+Content-Type: application/json
+
+{
+  "name" : "demo",
+  "description" : "demo",
+  "subdomain" : "demo"
+}
+#+END_SRC
+
+#+RESULTS: create_acs_zone
+#+begin_example
+HTTP/1.1 201 Created

+Date: Fri, 22 Jul 2016 00:54:02 GMT

+Cache-Control: no-cache, no-store, max-age=0, must-revalidate

+Pragma: no-cache

+Expires: 0

+X-XSS-Protection: 1; mode=block

+X-Frame-Options: DENY

+X-Content-Type-Options: nosniff

+Correlation-Id: 7c4b28d6-c252-4a7e-bf8f-1594e9a9efe3

+X-Application-Context: application:h2,public,simple-cache,titan:8181

+Content-Length: 0

+Server: Jetty(9.2.15.v20160210)
+
+#+end_example
+
+
+* Simple group-based access control example
+** Create a subject and set the subject attributes
+
+#+NAME: create_subject
+#+BEGIN_SRC http :exports both :var access_token=acs_admin_token
+PUT http://localhost:8181/v1/subject/tom%40ge.com
+Authorization: bearer ${access_token}
+Accept: application/json
+Content-Type: application/json
+Predix-Zone-Id: demo
+
+{
+  "subjectIdentifier" : "tom@ge.com",
+  "attributes" : [
+    {
+      "issuer" : "https://acs.predix.io",
+      "name"   : "org",
+      "value"  : "ge"
+    },
+    {
+      "issuer" : "https://acs.predix.io",
+      "name"   : "group",
+      "value"  : "research"
+    },
+    {
+      "issuer" : "https://acs.predix.io",
+      "name"   : "role",
+      "value"  : "analyst"
+    }
+  ]
+}
+#+END_SRC
+
+#+RESULTS: create_subject
+#+begin_example
+HTTP/1.1 201 Created

+Date: Fri, 22 Jul 2016 00:54:11 GMT

+Cache-Control: no-cache, no-store, max-age=0, must-revalidate

+Pragma: no-cache

+Expires: 0

+X-XSS-Protection: 1; mode=block

+X-Frame-Options: DENY

+X-Content-Type-Options: nosniff

+Correlation-Id: 623880ec-dc83-4616-8aed-d82c48504ebe

+X-Application-Context: application:h2,public,simple-cache,titan:8181

+Location: /subject/tom@ge.com

+Content-Length: 0

+Server: Jetty(9.2.15.v20160210)
+
+#+end_example
+
+** Create a resource and set the resource attributes
+
+#+NAME: create_resource
+#+BEGIN_SRC http :exports both :var access_token=acs_admin_token
+PUT http://localhost:8181/v1/resource/%2fengines%2f9
+Authorization: bearer ${access_token}
+Accept: application/json
+Content-Type: application/json
+Predix-Zone-Id: demo
+
+{
+  "subjectIdentifier" : "/engines/9",
+  "attributes" : [
+    {
+      "issuer" : "https://acs.predix.io",
+      "name"   : "org",
+      "value"  : "ge"
+    },
+    {
+      "issuer" : "https://acs.predix.io",
+      "name"   : "group",
+      "value"  : "research"
+    }
+  ]
+}
+#+END_SRC
+
+
+#+RESULTS: create_resource
+#+begin_example
+HTTP/1.1 201 Created

+Date: Fri, 22 Jul 2016 00:54:21 GMT

+Cache-Control: no-cache, no-store, max-age=0, must-revalidate

+Pragma: no-cache

+Expires: 0

+X-XSS-Protection: 1; mode=block

+X-Frame-Options: DENY

+X-Content-Type-Options: nosniff

+Correlation-Id: 8371bd64-9562-430e-b60a-8ef7292154f5

+X-Application-Context: application:h2,public,simple-cache,titan:8181

+Location: /resource//engines/9

+Content-Length: 0

+Server: Jetty(9.2.15.v20160210)
+
+#+end_example
+
+** Create a policy set
+
+#+NAME: create_policy_set
+#+BEGIN_SRC http :exports both :var access_token=acs_admin_token
+PUT http://localhost:8181/v1/policy-set/default
+Authorization: bearer ${access_token}
+Accept: application/json
+Content-Type: application/json
+Predix-Zone-Id: demo
+
+{
+  "name" : "default",
+  "policies" : [
+    {
+      "name" : "Analysts can access engines if they belong to the same group.",
+      "target" : {
+        "resource" : {
+          "name" : "Engine",
+          "uriTemplate" : "/engines/{engine_id}"
+        },
+        "action" : "GET",
+        "subject" : {
+          "name" : "Analysts",
+          "attributes" : [
+            {
+              "issuer" : "https://acs.predix.io",
+              "name"   : "role",
+              "value"  : "analyst"
+            }
+          ]
+        }
+      },
+      "conditions" : [
+        { 
+          "name"      : "is a member of the same group",
+          "condition" : "resource.and(subject).haveSame('https://acs.predix.io', 'group').result()" 
+        }
+      ],
+      "effect" : "PERMIT"
+    },
+    {
+      "name" : "Deny all other requests.",
+      "effect" : "DENY"
+    }
+  ]
+}
+#+END_SRC
+
+#+RESULTS: create_policy_set
+#+begin_example
+HTTP/1.1 201 Created

+Date: Fri, 22 Jul 2016 00:54:33 GMT

+Cache-Control: no-cache, no-store, max-age=0, must-revalidate

+Pragma: no-cache

+Expires: 0

+X-XSS-Protection: 1; mode=block

+X-Frame-Options: DENY

+X-Content-Type-Options: nosniff

+Correlation-Id: 14a07d7b-41e7-43c0-b79d-77e3927d0294

+X-Application-Context: application:h2,public,simple-cache,titan:8181

+Location: /policy-set/default

+Content-Length: 0

+Server: Jetty(9.2.15.v20160210)
+
+#+end_example
+
+** Submit a authorization request for policy evaluation
+
+#+NAME: evaluate_authz_request
+#+BEGIN_SRC http :exports both :pretty :var access_token=acs_admin_token
+POST http://localhost:8181/v1/policy-evaluation
+Authorization: bearer ${access_token}
+Accept: application/json
+Content-Type: application/json
+Predix-Zone-Id: demo
+
+{
+  "action" : "GET",
+  "resourceIdentifier" : "/engines/9",
+  "subjectIdentifier" : "tom@ge.com"
+}
+#+END_SRC
+
+
+#+RESULTS: evaluate_authz_request
+#+begin_example
+{
+  "timestamp": 0,
+  "resolvedResourceUris": [
+    "\/engines\/9"
+  ],
+  "resourceAttributes": [
+    {
+      "value": "ge",
+      "name": "org",
+      "issuer": "https:\/\/acs.predix.io"
+    },
+    {
+      "value": "research",
+      "name": "group",
+      "issuer": "https:\/\/acs.predix.io"
+    }
+  ],
+  "subjectAttributes": [
+    {
+      "value": "analyst",
+      "name": "role",
+      "issuer": "https:\/\/acs.predix.io"
+    },
+    {
+      "value": "ge",
+      "name": "org",
+      "issuer": "https:\/\/acs.predix.io"
+    },
+    {
+      "value": "research",
+      "name": "group",
+      "issuer": "https:\/\/acs.predix.io"
+    }
+  ],
+  "effect": "PERMIT"
+}
+#+end_example
+
+
+* Hierarchical attributes with dynamic role example
+** Create a subject role
+
+This subject represents the =analyst= role.
+
+#+NAME: create_subject_analyst
+#+BEGIN_SRC http :exports both :var access_token=acs_admin_token
+PUT http://localhost:8181/v1/subject/role-analyst
+Authorization: bearer ${access_token}
+Accept: application/json
+Content-Type: application/json
+Predix-Zone-Id: demo
+
+{
+  "subjectIdentifier" : "role-analyst",
+  "attributes" : [
+    {
+      "issuer" : "https://acs.predix.io",
+      "name"   : "role",
+      "value"  : "analyst"
+    }
+  ]
+}
+#+END_SRC
+
+#+RESULTS: create_subject_analyst
+#+begin_example
+HTTP/1.1 201 Created

+Date: Fri, 22 Jul 2016 00:43:24 GMT

+Cache-Control: no-cache, no-store, max-age=0, must-revalidate

+Pragma: no-cache

+Expires: 0

+X-XSS-Protection: 1; mode=block

+X-Frame-Options: DENY

+X-Content-Type-Options: nosniff

+Correlation-Id: 1d06af08-3d01-4892-8957-5d455002441e

+X-Application-Context: application:h2,public,simple-cache,titan:8181

+Location: /subject/role-analyst

+Content-Length: 0

+Server: Jetty(9.2.15.v20160210)
+
+#+end_example
+
+** Create a subject user
+
+This user will inherit the =analyst= role only when accessing engines in the =san-ramon= site.
+
+#+NAME: create_subject_user
+#+BEGIN_SRC http :exports both :var access_token=acs_admin_token
+PUT http://localhost:8181/v1/subject/tom%40ge.com
+Authorization: bearer ${access_token}
+Accept: application/json
+Content-Type: application/json
+Predix-Zone-Id: demo
+
+{
+  "subjectIdentifier" : "tom@ge.com",
+  "parents" : [
+    {
+      "identifier" : "role-analyst",
+      "scopes" : [
+        {
+          "issuer" : "https://acs.predix.io",
+          "name"   : "site",
+          "value"  : "san-ramon"
+        }
+      ]
+    }
+  ]
+}
+#+END_SRC
+
+#+RESULTS: create_subject_user
+#+begin_example
+HTTP/1.1 204 No Content

+Date: Fri, 22 Jul 2016 00:43:43 GMT

+Cache-Control: no-cache, no-store, max-age=0, must-revalidate

+Pragma: no-cache

+Expires: 0

+X-XSS-Protection: 1; mode=block

+X-Frame-Options: DENY

+X-Content-Type-Options: nosniff

+Correlation-Id: 92053b82-9458-49ca-8b0e-aba3ba18c84c

+X-Application-Context: application:h2,public,simple-cache,titan:8181

+Location: /subject/tom@ge.com

+Server: Jetty(9.2.15.v20160210)
+
+#+end_example
+
+** Create a resource site
+
+#+NAME: create_resource_site
+#+BEGIN_SRC http :exports both :var access_token=acs_admin_token
+PUT http://localhost:8181/v1/resource/%2fsites%2fsan-ramon
+Authorization: bearer ${access_token}
+Accept: application/json
+Content-Type: application/json
+Predix-Zone-Id: demo
+
+{
+  "subjectIdentifier" : "/sites/san-ramon",
+  "attributes" : [
+    {
+      "issuer" : "https://acs.predix.io",
+      "name"   : "site",
+      "value"  : "san-ramon"
+    }
+  ]
+}
+#+END_SRC
+
+
+#+RESULTS: create_resource_site
+#+begin_example
+HTTP/1.1 201 Created

+Date: Fri, 22 Jul 2016 00:44:06 GMT

+Cache-Control: no-cache, no-store, max-age=0, must-revalidate

+Pragma: no-cache

+Expires: 0

+X-XSS-Protection: 1; mode=block

+X-Frame-Options: DENY

+X-Content-Type-Options: nosniff

+Correlation-Id: 03febd4b-ef28-43b7-9c76-37663cd393b1

+X-Application-Context: application:h2,public,simple-cache,titan:8181

+Location: /resource//sites/san-ramon

+Content-Length: 0

+Server: Jetty(9.2.15.v20160210)
+
+#+end_example
+
+** Create a resource engine in the san-ramon site
+
+#+NAME: create_resource_engine_san_ramon
+#+BEGIN_SRC http :exports both :var access_token=acs_admin_token
+PUT http://localhost:8181/v1/resource/%2fengines%2f9
+Authorization: bearer ${access_token}
+Accept: application/json
+Content-Type: application/json
+Predix-Zone-Id: demo
+
+{
+  "subjectIdentifier" : "/engines/9",
+  "parents" : [
+    {
+      "identifier" : "/sites/san-ramon"
+    }
+  ]
+}
+#+END_SRC
+
+
+#+RESULTS: create_resource_engine_san_ramon
+#+begin_example
+HTTP/1.1 204 No Content

+Date: Fri, 22 Jul 2016 00:44:23 GMT

+Cache-Control: no-cache, no-store, max-age=0, must-revalidate

+Pragma: no-cache

+Expires: 0

+X-XSS-Protection: 1; mode=block

+X-Frame-Options: DENY

+X-Content-Type-Options: nosniff

+Correlation-Id: ca036e30-2f17-4287-981c-0ca5731999bc

+X-Application-Context: application:h2,public,simple-cache,titan:8181

+Location: /resource//engines/9

+Server: Jetty(9.2.15.v20160210)
+
+#+end_example
+
+** Create a resource engine that is not in the san-ramon site
+
+#+NAME: create_resource_engine_other
+#+BEGIN_SRC http :exports both :var access_token=acs_admin_token
+PUT http://localhost:8181/v1/resource/%2fengines%2f11
+Authorization: bearer ${access_token}
+Accept: application/json
+Content-Type: application/json
+Predix-Zone-Id: demo
+
+{
+  "subjectIdentifier" : "/engines/11"
+}
+#+END_SRC
+
+
+#+RESULTS: create_resource_engine_other
+#+begin_example
+HTTP/1.1 201 Created

+Date: Fri, 22 Jul 2016 00:44:39 GMT

+Cache-Control: no-cache, no-store, max-age=0, must-revalidate

+Pragma: no-cache

+Expires: 0

+X-XSS-Protection: 1; mode=block

+X-Frame-Options: DENY

+X-Content-Type-Options: nosniff

+Correlation-Id: 2fcb9168-ee4f-4953-aa3d-fcd944de1a44

+X-Application-Context: application:h2,public,simple-cache,titan:8181

+Location: /resource//engines/11

+Content-Length: 0

+Server: Jetty(9.2.15.v20160210)
+
+#+end_example
+
+** Create a policy set
+
+#+NAME: create_policy_set_1
+#+BEGIN_SRC http :exports both :var access_token=acs_admin_token
+PUT http://localhost:8181/v1/policy-set/default
+Authorization: bearer ${access_token}
+Accept: application/json
+Content-Type: application/json
+Predix-Zone-Id: demo
+
+{
+  "name" : "default",
+  "policies" : [
+    {
+      "name" : "Analysts can access engines.",
+      "target" : {
+        "resource" : {
+          "name" : "Engine",
+          "uriTemplate" : "/engines/{engine_id}"
+        },
+        "action" : "GET",
+        "subject" : {
+          "name" : "Analysts",
+          "attributes" : [
+            {
+              "issuer" : "https://acs.predix.io",
+              "name"   : "role",
+              "value"  : "analyst"
+            }
+          ]
+        }
+      },
+      "conditions" : [
+        { 
+          "name"      : "is an analyst",
+          "condition" : "match.single(subject.attributes('https://acs.predix.io', 'role'), 'analyst')" 
+        }
+      ],
+      "effect" : "PERMIT"
+    },
+    {
+      "name" : "Deny all other requests.",
+      "effect" : "DENY"
+    }
+  ]
+}
+#+END_SRC
+
+#+RESULTS: create_policy_set_1
+#+begin_example
+HTTP/1.1 201 Created

+Date: Fri, 22 Jul 2016 00:44:57 GMT

+Cache-Control: no-cache, no-store, max-age=0, must-revalidate

+Pragma: no-cache

+Expires: 0

+X-XSS-Protection: 1; mode=block

+X-Frame-Options: DENY

+X-Content-Type-Options: nosniff

+Correlation-Id: c22d3f09-4f0a-4b3c-a38d-0286f4eb4780

+X-Application-Context: application:h2,public,simple-cache,titan:8181

+Location: /policy-set/default

+Content-Length: 0

+Server: Jetty(9.2.15.v20160210)
+
+#+end_example
+
+** Submit a permitted authorization request for policy evaluation
+
+#+NAME: evaluate_authz_request_1
+#+BEGIN_SRC http :exports both :pretty :var access_token=acs_admin_token
+POST http://localhost:8181/v1/policy-evaluation
+Authorization: bearer ${access_token}
+Accept: application/json
+Content-Type: application/json
+Predix-Zone-Id: demo
+
+{
+  "action" : "GET",
+  "resourceIdentifier" : "/engines/9",
+  "subjectIdentifier" : "tom@ge.com"
+}
+#+END_SRC
+
+
+#+RESULTS: evaluate_authz_request_1
+#+begin_example
+{
+  "timestamp": 0,
+  "resolvedResourceUris": [
+    "\/engines\/9"
+  ],
+  "resourceAttributes": [
+    {
+      "value": "san-ramon",
+      "name": "site",
+      "issuer": "https:\/\/acs.predix.io"
+    }
+  ],
+  "subjectAttributes": [
+    {
+      "value": "analyst",
+      "name": "role",
+      "issuer": "https:\/\/acs.predix.io"
+    }
+  ],
+  "effect": "PERMIT"
+}
+#+end_example
+
+** Submit a denied authorization request for policy evaluation
+
+#+NAME: evaluate_authz_request_2
+#+BEGIN_SRC http :exports both :pretty :var access_token=acs_admin_token
+POST http://localhost:8181/v1/policy-evaluation
+Authorization: bearer ${access_token}
+Accept: application/json
+Content-Type: application/json
+Predix-Zone-Id: demo
+
+{
+  "action" : "GET",
+  "resourceIdentifier" : "/engines/11",
+  "subjectIdentifier" : "tom@ge.com"
+}
+#+END_SRC
+
+
+#+RESULTS: evaluate_authz_request_2
+: {
+:   "timestamp": 0,
+:   "resolvedResourceUris": [
+:     "\/engines\/11"
+:   ],
+:   "resourceAttributes": [],
+:   "subjectAttributes": [],
+:   "effect": "DENY"
+: }
+
diff --git a/docs/architecture.png b/docs/architecture.png
new file mode 100644
index 0000000..84e170d
--- /dev/null
+++ b/docs/architecture.png
Binary files differ
diff --git a/download-dependency-sources.sh b/download-dependency-sources.sh
new file mode 100755
index 0000000..13d2b71
--- /dev/null
+++ b/download-dependency-sources.sh
@@ -0,0 +1,201 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+#!/usr/bin/env bash
+
+{ set -e; } 2> /dev/null
+
+function usage {
+    echo "Usage: $( python -c "import os; print os.path.abspath('${BASH_SOURCE[0]}')" ) [-h | -help | --help] [--debug] [-s <path_to_mvn_settings_file>]"
+}
+
+function read_args {
+    while (( "$#" )); do
+        case "$1" in
+            -h|-help|--help)
+                usage
+                exit 0
+                ;;
+            --debug)
+                DEBUG='true'
+                ;;
+            -s)
+                shift
+                MVN_SETTINGS_FILE_PATH="$1"
+                ;;
+            *)
+                echo "Unknown option: ${1}"
+                usage
+                exit 2
+                ;;
+        esac
+        shift
+    done
+}
+
+function archive_artifact_sources {
+    MVN_COMMAND="mvn dependency:get -D groupId=$1 -D artifactId=$2 -D packaging=$3 -D version=$4 -D transitive=false -D maven.repo.local=$5"
+
+    local GRAB_SOURCES="$6"
+    if [[ "$GRAB_SOURCES" == 'true' ]]; then
+        MVN_COMMAND="${MVN_COMMAND} -D classifier=sources"
+    fi
+
+    if [[ -n "$MVN_SETTINGS_FILE_PATH" ]]; then
+        MVN_COMMAND="${MVN_COMMAND} -s ${MVN_SETTINGS_FILE_PATH}"
+    fi
+
+    { set +e; } 2> /dev/null
+    eval "$MVN_COMMAND"
+    { set -e; } 2> /dev/null
+}
+
+function build_source_artifact {
+    MVN_COMMAND='mvn source:jar'
+    if [[ -n "$MVN_SETTINGS_FILE_PATH" ]]; then
+        MVN_COMMAND="${MVN_COMMAND} -s ${MVN_SETTINGS_FILE_PATH}"
+    fi
+    eval "$MVN_COMMAND"
+}
+
+function install_artifact_file {
+    MVN_COMMAND="mvn install:install-file -D groupId=$1 -D artifactId=$2 -D packaging=$3 -D version=$4 -D classifier=sources -D localRepositoryPath=$5 -D file=${6}"
+    if [[ -n "$MVN_SETTINGS_FILE_PATH" ]]; then
+        MVN_COMMAND="${MVN_COMMAND} -s ${MVN_SETTINGS_FILE_PATH}"
+    fi
+    eval "$MVN_COMMAND"
+}
+
+unset DEBUG
+unset MVN_SETTINGS_FILE_PATH
+
+read_args "$@"
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+if [[ -n "$MVN_SETTINGS_FILE_PATH" ]]; then
+    MVN_SETTINGS_FILE_PATH="$( python -c "import os; print os.path.abspath('${MVN_SETTINGS_FILE_PATH}')" )"
+fi
+
+if [[ -n "$DEBUG" ]]; then
+    echo 'The following options are set:'
+    echo "  DEBUG: ${DEBUG}"
+    echo "  DIR: ${DIR}"
+    echo "  MVN_SETTINGS_FILE_PATH: ${MVN_SETTINGS_FILE_PATH}"
+    echo ''
+fi
+
+LOCAL_MAVEN_REPO="${DIR}/dependency_sources"
+mkdir -p "$LOCAL_MAVEN_REPO"
+
+TITAN_ARTIFACT_PREFIX='com.thinkaurelius.titan:titan'
+THRIFT_ARTIFACT_PREFIX='org.apache.thrift:libthrift'
+SWAGGER_UI_ARTIFACT_PREFIX='io.springfox:springfox-swagger-ui'
+
+# NOTE: These dependencies either don't have a corresponding sources JAR or are not found in the standard location in Maven Central so they need to be manually downloaded
+JSON_LIB_ARTIFACT_PREFIX='net.sf.json-lib:json-lib'
+SERVICEMIX_ARTIFACT_PREFIX='org.apache.servicemix.bundles:org.apache.servicemix.bundles.commons-csv'
+BSH_ARTIFACT_PREFIX='org.beanshell:bsh'
+
+MVN_DEPENDENCY_TREE="$( mvn dependency:tree | grep '^\[INFO\] [|+]' | \sed 's/\[INFO\] [-\+| ]*//' | sort -u )"
+for a in $( echo "$MVN_DEPENDENCY_TREE" | grep -v "com\.ge\.predix:acs-\|org\.springframework\.boot:spring-boot-starter-\|${TITAN_ARTIFACT_PREFIX}\|${THRIFT_ARTIFACT_PREFIX}\|${SWAGGER_UI_ARTIFACT_PREFIX}\|${SERVICEMIX_ARTIFACT_PREFIX}\|${BSH_ARTIFACT_PREFIX}\|${JSON_LIB_ARTIFACT_PREFIX}" ); do
+    GROUP_ID=$( echo "$a" | awk -F ':' '{print $1}' )
+    ARTIFACT_ID=$( echo "$a" | awk -F ':' '{print $2}' )
+    PACKAGING=$( echo "$a" | awk -F ':' '{print $3}' )
+
+    if [[ -n "$( echo "$a" | awk -F ':' '{print $6}' )" ]]; then
+        VERSION=$( echo "$a" | awk -F ':' '{print $5}' )
+    else
+        VERSION=$( echo "$a" | awk -F ':' '{print $4}' )
+    fi
+
+    archive_artifact_sources "$GROUP_ID" "$ARTIFACT_ID" "$PACKAGING" "$VERSION" "$LOCAL_MAVEN_REPO" 'true'
+
+    if [[ $( find "${LOCAL_MAVEN_REPO}" -name "${ARTIFACT_ID}-${VERSION}-sources.jar" | grep -q '.'; echo "$?" ) -ne 0 ]]; then
+        ARTIFACTS_WITHOUT_SOURCES="${ARTIFACTS_WITHOUT_SOURCES} ${a}"
+        echo -e "\nCouldn't download sources for artifact: ${a}\n"
+    else
+        echo -e "\nSuccessfully downloaded sources for artifact: ${a}\n"
+    fi
+done
+
+# Download Thrift-related source JARs
+for a in $( echo "$MVN_DEPENDENCY_TREE" | grep "$THRIFT_ARTIFACT_PREFIX" ); do
+    GROUP_ID=$( echo "$a" | awk -F ':' '{print $1}' )
+    ARTIFACT_ID=$( echo "$a" | awk -F ':' '{print $2}' )
+    PACKAGING=$( echo "$a" | awk -F ':' '{print $3}' )
+    VERSION=$( echo "$a" | awk -F ':' '{print $4}' )
+
+    curl -L "https://repo.maven.apache.org/maven2/$( echo "$GROUP_ID" | tr '.' '/' )/${ARTIFACT_ID}/${VERSION}/${ARTIFACT_ID}-${VERSION}-sources.src" -o "${ARTIFACT_ID}-${VERSION}-sources.jar"
+    install_artifact_file "$GROUP_ID" "$ARTIFACT_ID" "$PACKAGING" "$VERSION" "$LOCAL_MAVEN_REPO" "${ARTIFACT_ID}-${VERSION}-sources.jar"
+
+    if [[ $( find "${LOCAL_MAVEN_REPO}" -name "${ARTIFACT_ID}-${VERSION}-sources.jar" | grep -q '.'; echo "$?" ) -ne 0 ]]; then
+        echo -e "\nCouldn't download sources for artifact: ${a}\n"
+    else
+        echo -e "\nSuccessfully downloaded sources for artifact: ${a}\n"
+    fi
+
+    rm -f "${ARTIFACT_ID}-${VERSION}-sources.jar"
+done
+
+# Download Titan-related source JARs
+git clone https://github.com/thinkaurelius/titan.git
+cd titan
+git checkout titan11
+
+for a in $( echo "$MVN_DEPENDENCY_TREE" | grep "$TITAN_ARTIFACT_PREFIX" ); do
+    GROUP_ID=$( echo "$a" | awk -F ':' '{print $1}' )
+    ARTIFACT_ID=$( echo "$a" | awk -F ':' '{print $2}' )
+    PACKAGING=$( echo "$a" | awk -F ':' '{print $3}' )
+    VERSION=$( echo "$a" | awk -F ':' '{print $4}' )
+
+    cd "$ARTIFACT_ID"
+    build_source_artifact
+    install_artifact_file "$GROUP_ID" "$ARTIFACT_ID" "$PACKAGING" "$VERSION" "$LOCAL_MAVEN_REPO" "target/${ARTIFACT_ID}-${VERSION}-sources.jar"
+    cd ..
+
+    if [[ $( find "${LOCAL_MAVEN_REPO}" -name "${ARTIFACT_ID}-${VERSION}-sources.jar" | grep -q '.'; echo "$?" ) -ne 0 ]]; then
+        echo -e "\nCouldn't download sources for artifact: ${a}\n"
+    else
+        echo -e "\nSuccessfully downloaded sources for artifact: ${a}\n"
+    fi
+done
+
+cd ..
+rm -rf titan
+
+ARTIFACTS_WITHOUT_SOURCES="${ARTIFACTS_WITHOUT_SOURCES} $( echo "$MVN_DEPENDENCY_TREE" | grep "$SWAGGER_UI_ARTIFACT_PREFIX" )"
+
+for a in $ARTIFACTS_WITHOUT_SOURCES; do
+    GROUP_ID=$( echo "$a" | awk -F ':' '{print $1}' )
+    ARTIFACT_ID=$( echo "$a" | awk -F ':' '{print $2}' )
+    PACKAGING=$( echo "$a" | awk -F ':' '{print $3}' )
+    VERSION=$( echo "$a" | awk -F ':' '{print $4}' )
+
+    archive_artifact_sources "$GROUP_ID" "$ARTIFACT_ID" "$PACKAGING" "$VERSION" "$LOCAL_MAVEN_REPO"
+
+    if [[ $( find "${LOCAL_MAVEN_REPO}" -name "${ARTIFACT_ID}-${VERSION}.jar" | grep -q '.'; echo "$?" ) -ne 0 ]]; then
+        echo -e "\nCouldn't download artifact: ${a} . Please check that it exists in any Maven repository.\n"
+    else
+        JAR_FILE="$( find "${LOCAL_MAVEN_REPO}" -name "${ARTIFACT_ID}-${VERSION}.jar" | head -1 )"
+        JAR_FILE_DIRNAME="$( echo "$JAR_FILE" | xargs dirname )"
+        SOURCES_JAR_FILE="${JAR_FILE_DIRNAME}/${ARTIFACT_ID}-${VERSION}-sources.jar"
+        cp -a "$JAR_FILE" "$SOURCES_JAR_FILE"
+        echo -e "\nSuccessfully downloaded artifact: ${a} . A copy of the artifact with sources is located at ${SOURCES_JAR_FILE}\n"
+    fi
+done
diff --git a/eclipse-config/README.md b/eclipse-config/README.md
new file mode 100644
index 0000000..cdb00b6
--- /dev/null
+++ b/eclipse-config/README.md
@@ -0,0 +1,27 @@
+Setting Up Your Eclipse Environment
+================================================================================
+* Install Spring Tool Suite (these instructions are for STS 3.7.0)
+* Create a new workspace.
+* Select Spring Tool Suite->Preferences->General->Editors->Text Editor
+  * Check "Insert Spaces for Tab"
+  * Check "Show Print Margin" and set it to 120
+  * Check "Show Line Numbers"
+  * Check "Show Whitespace Characters"
+* Select Spring Tool Suite->Preferences->XML->XML Files->Editor
+  * Check "Indent Using Spaces" set "Indentation Size" to 4
+* Import the eclipse preferences file.
+  * In eclipse select File->Import->General->Preferences
+  * Select the eclipse_config/eclipse.epf file
+* Import the ACS maven project.
+* The following steps are necessary to work around an Eclipse bug that prevents the formatter settings from taking effect.
+  * Select Spring Tool Suite->Preferences->Java->Code Style->Formatter
+   * Select the "Eclipse" formatter then click OK
+   * Open a java file from your projects
+   * Right click then select Source->Format
+   * Save the file.
+  * Select Spring Tool Suite->Preferences->Java->Code Style->Formatter
+   * Select the "Guardians" formatter then click OK
+   * Open the same java file that you opened before
+   * Right click then select Source->Format
+   * Save the file.
+
diff --git a/eclipse-config/clean-up.xml b/eclipse-config/clean-up.xml
new file mode 100644
index 0000000..5dce1be
--- /dev/null
+++ b/eclipse-config/clean-up.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<profiles version="2">
+<profile kind="CleanUpProfile" name="Guardians Clean Up based on Eclipse" version="2">
+<setting id="cleanup.qualify_static_method_accesses_with_declaring_class" value="false"/>
+<setting id="cleanup.always_use_this_for_non_static_method_access" value="false"/>
+<setting id="cleanup.organize_imports" value="true"/>
+<setting id="cleanup.remove_trailing_whitespaces_ignore_empty" value="false"/>
+<setting id="cleanup.use_type_arguments" value="false"/>
+<setting id="cleanup.format_source_code_changes_only" value="false"/>
+<setting id="cleanup.qualify_static_field_accesses_with_declaring_class" value="false"/>
+<setting id="cleanup.add_generated_serial_version_id" value="false"/>
+<setting id="cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class" value="true"/>
+<setting id="cleanup.remove_redundant_type_arguments" value="true"/>
+<setting id="cleanup.remove_unused_imports" value="true"/>
+<setting id="cleanup.insert_inferred_type_arguments" value="false"/>
+<setting id="cleanup.make_private_fields_final" value="true"/>
+<setting id="cleanup.use_lambda" value="true"/>
+<setting id="cleanup.always_use_blocks" value="true"/>
+<setting id="cleanup.use_this_for_non_static_field_access_only_if_necessary" value="false"/>
+<setting id="cleanup.sort_members_all" value="false"/>
+<setting id="cleanup.remove_trailing_whitespaces_all" value="true"/>
+<setting id="cleanup.add_missing_annotations" value="true"/>
+<setting id="cleanup.always_use_this_for_non_static_field_access" value="true"/>
+<setting id="cleanup.make_parameters_final" value="true"/>
+<setting id="cleanup.sort_members" value="false"/>
+<setting id="cleanup.remove_private_constructors" value="true"/>
+<setting id="cleanup.always_use_parentheses_in_expressions" value="false"/>
+<setting id="cleanup.remove_unused_local_variables" value="false"/>
+<setting id="cleanup.convert_to_enhanced_for_loop" value="false"/>
+<setting id="cleanup.remove_unused_private_fields" value="true"/>
+<setting id="cleanup.never_use_blocks" value="false"/>
+<setting id="cleanup.add_missing_deprecated_annotations" value="true"/>
+<setting id="cleanup.use_this_for_non_static_field_access" value="true"/>
+<setting id="cleanup.remove_unnecessary_nls_tags" value="true"/>
+<setting id="cleanup.qualify_static_member_accesses_through_instances_with_declaring_class" value="true"/>
+<setting id="cleanup.add_missing_nls_tags" value="false"/>
+<setting id="cleanup.remove_unnecessary_casts" value="true"/>
+<setting id="cleanup.use_blocks_only_for_return_and_throw" value="false"/>
+<setting id="cleanup.format_source_code" value="true"/>
+<setting id="cleanup.convert_functional_interfaces" value="false"/>
+<setting id="cleanup.add_default_serial_version_id" value="true"/>
+<setting id="cleanup.remove_unused_private_methods" value="true"/>
+<setting id="cleanup.remove_trailing_whitespaces" value="true"/>
+<setting id="cleanup.make_type_abstract_if_missing_method" value="false"/>
+<setting id="cleanup.add_serial_version_id" value="false"/>
+<setting id="cleanup.use_this_for_non_static_method_access" value="false"/>
+<setting id="cleanup.use_this_for_non_static_method_access_only_if_necessary" value="true"/>
+<setting id="cleanup.use_anonymous_class_creation" value="false"/>
+<setting id="cleanup.add_missing_override_annotations_interface_methods" value="true"/>
+<setting id="cleanup.remove_unused_private_members" value="false"/>
+<setting id="cleanup.make_local_variable_final" value="false"/>
+<setting id="cleanup.add_missing_methods" value="false"/>
+<setting id="cleanup.never_use_parentheses_in_expressions" value="true"/>
+<setting id="cleanup.qualify_static_member_accesses_with_declaring_class" value="true"/>
+<setting id="cleanup.use_parentheses_in_expressions" value="false"/>
+<setting id="cleanup.add_missing_override_annotations" value="true"/>
+<setting id="cleanup.use_blocks" value="false"/>
+<setting id="cleanup.make_variable_declarations_final" value="true"/>
+<setting id="cleanup.correct_indentation" value="true"/>
+<setting id="cleanup.remove_unused_private_types" value="true"/>
+</profile>
+</profiles>
diff --git a/eclipse-config/compiler-prefs.epf b/eclipse-config/compiler-prefs.epf
new file mode 100644
index 0000000..887f7f0
--- /dev/null
+++ b/eclipse-config/compiler-prefs.epf
@@ -0,0 +1,11 @@
+#Tue Jul 21 15:00:24 PDT 2015
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
+@org.eclipse.jdt.core=3.11.0.v20150602-1242
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore
+\!/=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
+file_export_version=3.0
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=warning
diff --git a/eclipse-config/eclipse.epf b/eclipse-config/eclipse.epf
new file mode 100644
index 0000000..3528bdd
--- /dev/null
+++ b/eclipse-config/eclipse.epf
@@ -0,0 +1,94 @@
+#Tue Jul 21 15:08:14 PDT 2015
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFieldPrefixes=
+/instance/org.eclipse.jdt.ui/cleanup.make_parameters_final=true
+/instance/org.eclipse.jdt.ui/cleanup.always_use_parentheses_in_expressions=false
+/instance/org.eclipse.jdt.ui/cleanup.add_serial_version_id=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.argumentSuffixes=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.argumentPrefixes=
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.ondemandthreshold=99
+/instance/org.eclipse.jdt.ui/cleanup.use_lambda=true
+/instance/org.eclipse.jdt.ui/cleanup.use_anonymous_class_creation=false
+/instance/org.eclipse.jdt.ui/cleanup.always_use_blocks=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_trailing_whitespaces=true
+/instance/org.eclipse.jdt.ui/cleanup.make_private_fields_final=true
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_override_annotations_interface_methods=true
+@org.eclipse.jdt.ui=3.11.0.v20150527-0925
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_nls_tags=false
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.ignorelowercasenames=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_private_constructors=true
+/instance/org.eclipse.jdt.ui/cleanup.add_default_serial_version_id=true
+/instance/org.eclipse.jdt.ui/cleanup.use_blocks=false
+/instance/org.eclipse.jdt.ui/cleanup.convert_to_enhanced_for_loop=false
+file_export_version=3.0
+/instance/org.eclipse.jdt.ui/formatter_settings_version=12
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.exception.name=e
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore
+/instance/org.eclipse.jdt.ui/cleanup.qualify_static_field_accesses_with_declaring_class=false
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.cleanupprofiles=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\n<profiles version\="2">\n<profile kind\="CleanUpProfile" name\="Guardians Clean Up based on Eclipse" version\="2">\n<setting id\="cleanup.qualify_static_method_accesses_with_declaring_class" value\="false"/>\n<setting id\="cleanup.always_use_this_for_non_static_method_access" value\="false"/>\n<setting id\="cleanup.organize_imports" value\="true"/>\n<setting id\="cleanup.remove_trailing_whitespaces_ignore_empty" value\="false"/>\n<setting id\="cleanup.use_type_arguments" value\="false"/>\n<setting id\="cleanup.format_source_code_changes_only" value\="false"/>\n<setting id\="cleanup.qualify_static_field_accesses_with_declaring_class" value\="false"/>\n<setting id\="cleanup.add_generated_serial_version_id" value\="false"/>\n<setting id\="cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class" value\="true"/>\n<setting id\="cleanup.remove_redundant_type_arguments" value\="true"/>\n<setting id\="cleanup.remove_unused_imports" value\="true"/>\n<setting id\="cleanup.insert_inferred_type_arguments" value\="false"/>\n<setting id\="cleanup.make_private_fields_final" value\="true"/>\n<setting id\="cleanup.use_lambda" value\="true"/>\n<setting id\="cleanup.always_use_blocks" value\="true"/>\n<setting id\="cleanup.use_this_for_non_static_field_access_only_if_necessary" value\="false"/>\n<setting id\="cleanup.sort_members_all" value\="false"/>\n<setting id\="cleanup.remove_trailing_whitespaces_all" value\="true"/>\n<setting id\="cleanup.add_missing_annotations" value\="true"/>\n<setting id\="cleanup.always_use_this_for_non_static_field_access" value\="true"/>\n<setting id\="cleanup.make_parameters_final" value\="true"/>\n<setting id\="cleanup.sort_members" value\="false"/>\n<setting id\="cleanup.remove_private_constructors" value\="true"/>\n<setting id\="cleanup.always_use_parentheses_in_expressions" value\="false"/>\n<setting id\="cleanup.remove_unused_local_variables" value\="false"/>\n<setting id\="cleanup.convert_to_enhanced_for_loop" value\="false"/>\n<setting id\="cleanup.remove_unused_private_fields" value\="true"/>\n<setting id\="cleanup.never_use_blocks" value\="false"/>\n<setting id\="cleanup.add_missing_deprecated_annotations" value\="true"/>\n<setting id\="cleanup.use_this_for_non_static_field_access" value\="true"/>\n<setting id\="cleanup.remove_unnecessary_nls_tags" value\="true"/>\n<setting id\="cleanup.qualify_static_member_accesses_through_instances_with_declaring_class" value\="true"/>\n<setting id\="cleanup.add_missing_nls_tags" value\="false"/>\n<setting id\="cleanup.remove_unnecessary_casts" value\="true"/>\n<setting id\="cleanup.use_blocks_only_for_return_and_throw" value\="false"/>\n<setting id\="cleanup.format_source_code" value\="true"/>\n<setting id\="cleanup.convert_functional_interfaces" value\="false"/>\n<setting id\="cleanup.add_default_serial_version_id" value\="true"/>\n<setting id\="cleanup.remove_unused_private_methods" value\="true"/>\n<setting id\="cleanup.remove_trailing_whitespaces" value\="true"/>\n<setting id\="cleanup.make_type_abstract_if_missing_method" value\="false"/>\n<setting id\="cleanup.add_serial_version_id" value\="false"/>\n<setting id\="cleanup.use_this_for_non_static_method_access" value\="false"/>\n<setting id\="cleanup.use_this_for_non_static_method_access_only_if_necessary" value\="true"/>\n<setting id\="cleanup.use_anonymous_class_creation" value\="false"/>\n<setting id\="cleanup.add_missing_override_annotations_interface_methods" value\="true"/>\n<setting id\="cleanup.remove_unused_private_members" value\="false"/>\n<setting id\="cleanup.make_local_variable_final" value\="false"/>\n<setting id\="cleanup.add_missing_methods" value\="false"/>\n<setting id\="cleanup.never_use_parentheses_in_expressions" value\="true"/>\n<setting id\="cleanup.qualify_static_member_accesses_with_declaring_class" value\="true"/>\n<setting id\="cleanup.use_parentheses_in_expressions" value\="false"/>\n<setting id\="cleanup.add_missing_override_annotations" value\="true"/>\n<setting id\="cleanup.use_blocks" value\="false"/>\n<setting id\="cleanup.make_variable_declarations_final" value\="true"/>\n<setting id\="cleanup.correct_indentation" value\="true"/>\n<setting id\="cleanup.remove_unused_private_types" value\="true"/>\n</profile>\n</profiles>\n
+/instance/org.eclipse.jdt.ui/cleanup_profile=_Guardians Clean Up based on Eclipse
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.importorder=java;javax;org;com;
+/instance/org.eclipse.jdt.ui/cleanup.qualify_static_member_accesses_with_declaring_class=true
+/instance/org.eclipse.jdt.ui/cleanup.convert_functional_interfaces=false
+/instance/org.eclipse.jdt.ui/cleanup.use_parentheses_in_expressions=false
+\!/=
+/instance/org.eclipse.jdt.ui/cleanup.sort_members=false
+/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_field_access_only_if_necessary=false
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.staticondemandthreshold=99
+/instance/org.eclipse.jdt.ui/cleanup.never_use_parentheses_in_expressions=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.fieldSuffixes=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.localSuffixes=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.fieldPrefixes=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.localPrefixes=
+/instance/org.eclipse.jdt.ui/cleanup.make_variable_declarations_final=true
+/instance/org.eclipse.jdt.ui/cleanup.use_type_arguments=false
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.formatterprofiles=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\n<profiles version\="12">\n<profile kind\="CodeFormatterProfile" name\="Guardians Formatter based on Eclipse" version\="12">\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value\="1"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.indentation.size" value\="4"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.disabling_tag" value\="@formatter\:off"/>\n<setting id\="org.eclipse.jdt.core.formatter.continuation_indentation" value\="2"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value\="0"/>\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value\="1"/>\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_after_package" value\="1"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.enabling_tag" value\="@formatter\:on"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value\="false"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value\="1"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value\="false"/>\n<setting id\="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value\="error"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value\="end_of_line"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.line_length" value\="118"/>\n<setting id\="org.eclipse.jdt.core.formatter.use_on_off_tags" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value\="false"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value\="end_of_line"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value\="0"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value\="false"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_block" value\="end_of_line"/>\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value\="end_of_line"/>\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value\="end_of_line"/>\n<setting id\="org.eclipse.jdt.core.formatter.compact_else_if" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value\="16"/>\n<setting id\="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value\="error"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value\="false"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value\="false"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value\="1"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_line_comments" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value\="false"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_assignment" value\="0"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value\="0"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value\="80"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value\="false"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value\="end_of_line"/>\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value\="end_of_line"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_header" value\="false"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value\="enabled"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value\="0"/>\n<setting id\="org.eclipse.jdt.core.formatter.join_wrapped_lines" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value\="end_of_line"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value\="80"/>\n<setting id\="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value\="false"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value\="false"/>\n<setting id\="org.eclipse.jdt.core.compiler.source" value\="1.8"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.tabulation.size" value\="4"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_source_code" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_field" value\="0"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value\="2"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_method" value\="1"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value\="1.8"/>\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_switch" value\="end_of_line"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_html" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.indent_empty_lines" value\="false"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value\="48"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value\="false"/>\n<setting id\="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value\="false"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value\="1"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value\="1"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_block_comments" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value\="false"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value\="16"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value\="end_of_line"/>\n<setting id\="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.compiler.compliance" value\="1.8"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value\="end_of_line"/>\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value\="end_of_line"/>\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_package" value\="0"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.join_lines_in_comments" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value\="insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value\="true"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.tabulation.char" value\="space"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value\="1"/>\n<setting id\="org.eclipse.jdt.core.formatter.lineSplit" value\="120"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value\="do not insert"/>\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value\="insert"/>\n</profile>\n</profiles>\n
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_override_annotations=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_unnecessary_nls_tags=true
+/instance/org.eclipse.jdt.ui/formatter_profile=_Guardians Formatter based on Eclipse
+/instance/org.eclipse.jdt.ui/cleanup.organize_imports=true
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.formatterprofiles.version=12
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+@org.eclipse.jdt.core=3.11.0.v20150602-1242
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_types=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_imports=true
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_methods=false
+/instance/org.eclipse.jdt.ui/cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+/instance/org.eclipse.jdt.ui/cleanup.always_use_this_for_non_static_field_access=true
+/instance/org.eclipse.jdt.ui/cleanup.always_use_this_for_non_static_method_access=false
+/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_field_access=true
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_annotations=true
+/instance/org.eclipse.jdt.ui/cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_trailing_whitespaces_all=true
+/instance/org.eclipse.jdt.ui/cleanup.never_use_blocks=false
+/instance/org.eclipse.jdt.ui/cleanup.remove_redundant_type_arguments=true
+/instance/org.eclipse.jdt.ui/cleanup.insert_inferred_type_arguments=false
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.gettersetter.use.is=true
+/instance/org.eclipse.jdt.ui/cleanup.format_source_code_changes_only=false
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_methods=true
+/instance/org.eclipse.jdt.ui/cleanup.add_generated_serial_version_id=false
+/instance/org.eclipse.jdt.ui/cleanup.remove_trailing_whitespaces_ignore_empty=false
+/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_method_access=false
+/instance/org.eclipse.jdt.ui/cleanup.sort_members_all=false
+/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_fields=true
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.overrideannotation=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes=
+/instance/org.eclipse.jdt.ui/cleanup.qualify_static_method_accesses_with_declaring_class=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning
+/instance/org.eclipse.jdt.ui/cleanup_settings_version=2
+/instance/org.eclipse.jdt.ui/cleanup.make_type_abstract_if_missing_method=false
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_local_variables=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
+/instance/org.eclipse.jdt.ui/cleanup.make_local_variable_final=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=warning
+/instance/org.eclipse.jdt.ui/cleanup.format_source_code=true
+/instance/org.eclipse.jdt.ui/cleanup.correct_indentation=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_unnecessary_casts=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_members=false
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_deprecated_annotations=true
+/instance/org.eclipse.jdt.ui/cleanup.use_blocks_only_for_return_and_throw=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFieldSuffixes=
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
diff --git a/eclipse-config/formatter.xml b/eclipse-config/formatter.xml
new file mode 100644
index 0000000..321a133
--- /dev/null
+++ b/eclipse-config/formatter.xml
@@ -0,0 +1,314 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<profiles version="12">
+<profile kind="CodeFormatterProfile" name="Guardians Formatter based on Eclipse" version="12">
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="118"/>
+<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
+<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
+<setting id="org.eclipse.jdt.core.compiler.source" value="1.8"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.8"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="48"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.8"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="120"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
+</profile>
+</profiles>
diff --git a/merge-to-master.sh b/merge-to-master.sh
new file mode 100755
index 0000000..dc69624
--- /dev/null
+++ b/merge-to-master.sh
@@ -0,0 +1,294 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+#!/usr/bin/env bash
+
+YES='yes'
+SKIP='skip'
+NO='n'
+CONFIRMATION_MESSAGE="Type '${YES}' to continue, '${SKIP}' to skip or '${NO}' to abort"
+
+unset STEP_SKIPPED
+
+function prompt {
+    echo -e "\n\n${1}\n\n"
+    read -p "${CONFIRMATION_MESSAGE}. [${YES}/${SKIP}/${NO}]: " ANSWER
+
+    if [[ "$ANSWER" == "$SKIP" ]]; then
+        STEP_SKIPPED='true'
+    elif [[ "$ANSWER" == "$YES" ]]; then
+        unset STEP_SKIPPED
+    else
+        echo -e '\nExiting...'
+        exit 1
+    fi
+}
+
+function check_internet {
+    { set +e; } 2> /dev/null
+    curl -k "$1" > /dev/null 2>&1
+    if [[ "$?" -ne 0 ]]; then
+        if [[ -n "$http_proxy" || -n "$https_proxy" ]]; then
+            unset http_proxy
+            unset HTTP_PROXY
+            unset https_proxy
+            unset HTTPS_PROXY
+        else
+            export http_proxy='http://proxy-src.research.ge.com:8080'
+            export HTTP_PROXY="$http_proxy"
+            export https_proxy="$http_proxy"
+            export HTTPS_PROXY="$https_proxy"
+        fi
+        curl -k "$1" > /dev/null 2>&1
+        if [[ "$?" -ne 0 ]]; then
+            exit 1
+        fi
+    fi
+    { set -e; } 2> /dev/null
+}
+
+function run_versioning_script {
+    check_internet 'https://downloads.sourceforge.net'
+    ./versioning.sh "$1"
+    check_internet "https://${GIT_API_URI_PATH}"
+}
+
+function disable_pull_request_reviews_on_development_branch {
+    { set -x; } 2>/dev/null
+    local DISABLE_PROTECTION_RESPONSE="$(curl "https://${GIT_API_URI_PATH}/repos/${REPOSITORY_URI_PATH}/branches/${1}/protection/required_pull_request_reviews" -sI -X DELETE \
+    -H "Authorization: token ${GIT_ACCESS_TOKEN}")"
+    { set +x; } 2>/dev/null
+
+    if [[ $(echo "$DISABLE_PROTECTION_RESPONSE" | grep -q '^HTTP.*204'; echo "$?") -ne 0 ]]; then
+        echo -e "\nCouldn't disable protection on the ${1} branch. Exiting..."
+        exit 1
+    fi
+}
+
+function enable_pull_request_reviews_on_development_branch {
+    { set -x; } 2>/dev/null
+    local ENABLE_PROTECTION_RESPONSE="$(curl "https://${GIT_API_URI_PATH}/repos/${REPOSITORY_URI_PATH}/branches/${1}/protection/required_pull_request_reviews" -s -D - -X PATCH \
+    -H "Authorization: token ${GIT_ACCESS_TOKEN}" \
+    -H 'Content-Type: application/json' \
+    -d '{ "dismiss_stale_reviews" : false, "require_code_owner_reviews" : false }')"
+    { set +x; } 2>/dev/null
+
+    if [[ $(echo "$ENABLE_PROTECTION_RESPONSE" | grep -q '^HTTP.*200'; echo "$?") -ne 0 ]]; then
+        echo -e "\nCouldn't enable protection on the ${1} branch. Exiting..."
+        exit 1
+    fi
+}
+
+function update_version {
+    if [[ -n "$1" ]]; then
+        local SNAPSHOT_VERSION="${1}-SNAPSHOT"
+        local DEVELOPMENT_BRANCH='develop'
+        disable_pull_request_reviews_on_development_branch "$DEVELOPMENT_BRANCH"
+        run_versioning_script "$SNAPSHOT_VERSION"
+        git commit -am "Bumped up the version to ${SNAPSHOT_VERSION}"
+        git push
+        enable_pull_request_reviews_on_development_branch "$DEVELOPMENT_BRANCH"
+        RELEASE_VERSION="$1"
+    fi
+}
+
+{ set -ex; } 2>/dev/null
+
+REPOSITORY_URI_PATH=$(git ls-remote --get-url | sed -n 's|.*[:/]\(.*\)/\(.*\)\.git|\1/\2|p')
+
+# Set the GitHub API domain/URI path:
+GIT_DOMAIN=$(git ls-remote --get-url | sed -n 's|\(.*\)[:/].*/.*\.git|\1|;s|.*[/@]\(.*\)|\1|p')
+if [[ $(echo "$GIT_DOMAIN" | grep -q 'github\.com'; echo "$?") -eq 0 ]]; then
+    GIT_API_URI_PATH="api.${GIT_DOMAIN}"
+else
+    GIT_API_URI_PATH="${GIT_DOMAIN}/api/v3"
+fi
+
+check_internet "https://${GIT_API_URI_PATH}"
+
+# Get the personal access token necessary for using the GitHub API:
+{ set +x; } 2>/dev/null
+read -p "Please go to https://${GIT_DOMAIN}/settings/tokens, click the 'Edit' button next to your personal access token, click the 'Regenerate token' button towards the top, and enter the token shown: " GIT_ACCESS_TOKEN
+
+if [[ -z "$GIT_ACCESS_TOKEN" ]]; then
+    echo -e '\nYour personal access token is required before running this script. Exiting...'
+    exit 1
+else
+    { set -x; } 2>/dev/null
+    PR_GET_ALL="$(curl "https://${GIT_API_URI_PATH}/repos/${REPOSITORY_URI_PATH}/pulls" -sI -X GET \
+    -H "Authorization: token ${GIT_ACCESS_TOKEN}")"
+    { set +x; } 2>/dev/null
+    if [[ $(echo "$PR_GET_ALL" | grep -q '^HTTP.*200'; echo "$?") -ne 0 ]]; then
+        echo -e '\nYour personal access token is invalid. Please try again. Exiting...'
+        exit 1
+    fi
+fi
+
+prompt "About to merge changes from the develop to the master branch. Please stash any changes now via 'git stash save -u <stash_message>'."
+{ set -x; } 2>/dev/null
+
+# Checkout the develop branch as follows (note that any local and untracked changes will be removed so they should be stashed prior to running these commands):
+git clean -fd
+git checkout -f develop
+git pull --all --prune --tags --verbose
+
+# Cut a new release branch from develop, grabbing the current version from the top-level pom.xml file as follows:
+RELEASE_VERSION=$(xmllint --xpath "//*[local-name()='project']/*[local-name()='version']/text()" pom.xml | sed 's/-SNAPSHOT//')
+
+if [[ -z "$RELEASE_VERSION" ]]; then
+    echo -e "\nThe release version can't be empty. Exiting..."
+    exit 1
+fi
+
+if [[ -z "$STEP_SKIPPED" ]]; then
+    { set +x; } 2>/dev/null
+    echo -e "\n\nThe current version that will be used in the 'release-${RELEASE_VERSION}' branch is '${RELEASE_VERSION}'.\n\n"
+    read -p "If you would like to update the version, please enter the new version (without the '-SNAPSHOT' suffix) or press Enter to continue using the existing version: " UPDATED_RELEASE_VERSION
+    { set -x; } 2>/dev/null
+
+    update_version "$UPDATED_RELEASE_VERSION"
+
+    if [[ $(git branch -a | grep -q "remotes/origin/release-${RELEASE_VERSION}"; echo "$?") -ne 0 ]]; then
+        git checkout -B "release-${RELEASE_VERSION}" develop
+
+        # Remove the -SNAPSHOT suffix from all versions, verify the changes and commit them as follows:
+        run_versioning_script "$RELEASE_VERSION"
+        git commit -am "Changed the version to ${RELEASE_VERSION} (removes the '-SNAPSHOT' suffix)"
+
+        # Merge the master branch into the release branch as follows:
+        git checkout -f master
+        git pull --all --prune --tags --verbose
+        git checkout -
+        { set +e; } 2>/dev/null
+        git merge -X ours --no-edit master
+        { set -e; } 2>/dev/null
+
+        # Push the release branch upstream as follows:
+        git push -u origin "release-${RELEASE_VERSION}"
+    else
+        { set +x; } 2>/dev/null
+        echo -e '\nRelease branch has already been pushed.\n\n'
+        { set -x; } 2>/dev/null
+    fi
+fi
+
+{ set +x; } 2>/dev/null
+prompt "About to create a pull request from this release branch and merge it in."
+{ set -x; } 2>/dev/null
+
+if [[ -z "$STEP_SKIPPED" ]]; then
+    PR_GET_RESPONSE="$(curl "https://${GIT_API_URI_PATH}/repos/${REPOSITORY_URI_PATH}/pulls?state=open" -s -X GET \
+    -H "Authorization: token ${GIT_ACCESS_TOKEN}")"
+
+    RELEASE_TITLE="Release ${RELEASE_VERSION}"
+
+    if [[ -z $(echo "$PR_GET_RESPONSE" | python -c "import sys, json; pull_requests = json.load(sys.stdin); title = [pr['title'] for pr in pull_requests if pr['title'] == '${RELEASE_TITLE}']; print title[0] if title else '';") ]]; then
+        # Create a pull request for the release branch:
+        PR_CREATE_RESPONSE="$(curl "https://${GIT_API_URI_PATH}/repos/${REPOSITORY_URI_PATH}/pulls" -s -X POST \
+        -H "Authorization: token ${GIT_ACCESS_TOKEN}" \
+        -H 'Content-Type: application/json' \
+        -d '{ "title": "'"${RELEASE_TITLE}"'", "head": "release-'"$RELEASE_VERSION"'", "base": "master" }')"
+
+        PR_JSON_RESPONSE="$PR_CREATE_RESPONSE"
+    else
+        PR_JSON_RESPONSE="$PR_GET_RESPONSE"
+
+        { set +x; } 2>/dev/null
+        echo -e '\nPull request was already created.\n\n'
+        { set -x; } 2>/dev/null
+    fi
+
+    { set +x; } 2>/dev/null
+    PULL_REQUEST_NUMBER=$(echo "$PR_JSON_RESPONSE" | python -c "import sys, json; print json.load(sys.stdin)['number']")
+    PULL_REQUEST_TITLE=$(echo "$PR_JSON_RESPONSE" | python -c "import sys, json; print json.load(sys.stdin)['title']")
+    PULL_REQUEST_HEAD_LABEL=$(echo "$PR_JSON_RESPONSE" | python -c "import sys, json; print json.load(sys.stdin)['head']['label']" | tr ':' '/')
+    PULL_REQUEST_HEAD_SHA=$(echo "$PR_JSON_RESPONSE" | python -c "import sys, json; print json.load(sys.stdin)['head']['sha']")
+    PULL_REQUEST_URL=$(echo "$PR_JSON_RESPONSE" | python -c "import sys, json; print json.load(sys.stdin)['url']")
+
+    prompt "About to merge the pull request located at '${PULL_REQUEST_URL}'."
+    { set -x; } 2>/dev/null
+
+    if [[ -z "$STEP_SKIPPED" ]]; then
+        PR_GET_MERGE_RESPONSE="$(curl "${PULL_REQUEST_URL}/merge" -sI -X GET \
+        -H "Authorization: token ${GIT_ACCESS_TOKEN}")"
+
+        if [[ $(echo "$PR_GET_MERGE_RESPONSE" | grep -q '^HTTP.*204'; echo "$?") -ne 0 ]]; then
+            # Merge the pull request to master as follows:
+            PR_MERGE_RESPONSE="$(curl "${PULL_REQUEST_URL}/merge" -s -X PUT \
+            -H "Authorization: token ${GIT_ACCESS_TOKEN}" \
+            -H 'Content-Type: application/json' \
+            -d '{ "commit_title": "Merge pull request #'"$PULL_REQUEST_NUMBER"' from '"$PULL_REQUEST_HEAD_LABEL"'", "commit_message": "'"$PULL_REQUEST_TITLE"'", "sha": "'"$PULL_REQUEST_HEAD_SHA"'", "merge_method": "merge" }')"
+        else
+            { set +x; } 2>/dev/null
+            echo -e '\nPull request was already merged.\n\n'
+            { set -x; } 2>/dev/null
+        fi
+    fi
+fi
+
+{ set +x; } 2>/dev/null
+prompt "About to delete the release branch remotely and locally."
+{ set -x; } 2>/dev/null
+
+if [[ -z "$STEP_SKIPPED" ]]; then
+    # Delete the release branch remotely and locally as follows:
+    git checkout -f master
+    git pull --all --prune --tags --verbose
+
+    if [[ $(git branch -a | grep -q "remotes/origin/release-${RELEASE_VERSION}"; echo "$?") -eq 0 ]]; then
+        git push --delete origin "release-${RELEASE_VERSION}"
+        git branch -D "release-${RELEASE_VERSION}"
+    else
+        { set +x; } 2>/dev/null
+        echo -e '\nRelease branch has already been deleted.\n\n'
+        { set -x; } 2>/dev/null
+    fi
+fi
+
+{ set +x; } 2>/dev/null
+prompt "About to tag the release."
+{ set -x; } 2>/dev/null
+
+if [[ -z "$STEP_SKIPPED" ]]; then
+    # Pull the merged changes and tag the merge commit on master for future reference:
+    git checkout -f master
+    git pull --all --prune --tags --verbose
+
+    if [[ $(git ls-remote --tags | grep -q "refs/tags/$RELEASE_VERSION"; echo "$?") -ne 0 ]]; then
+        { set +e; } 2>/dev/null
+        git tag -d "$RELEASE_VERSION"
+        { set -e; } 2>/dev/null
+        git tag "$RELEASE_VERSION"
+        git show "$RELEASE_VERSION"
+        git push origin "$RELEASE_VERSION"
+    else
+        { set +x; } 2>/dev/null
+        echo -e '\nRelease has already been tagged.\n\n'
+        { set -x; } 2>/dev/null
+    fi
+fi
+
+git checkout -f develop
+git pull --all --prune --tags --verbose
+
+{ set +x; } 2>/dev/null
+echo -e "\n\nThe current version that will be used in the 'develop' branch is '${RELEASE_VERSION}-SNAPSHOT'.\n\n"
+read -p "If you would like to update the version, please enter the new version (without the '-SNAPSHOT' suffix) or press Enter to continue using the existing version: " UPDATED_RELEASE_VERSION
+{ set -x; } 2>/dev/null
+
+update_version "$UPDATED_RELEASE_VERSION"
diff --git a/model/.gitignore b/model/.gitignore
new file mode 100644
index 0000000..5ccf145
--- /dev/null
+++ b/model/.gitignore
@@ -0,0 +1,14 @@
+/build
+/bin
+/target
+.classpath
+.project
+.settings
+target
+/test-output/
+.DS_Store
+.springBeans
+.idea
+*.ipr
+*.iml
+.checkstyle
diff --git a/model/pom.xml b/model/pom.xml
new file mode 100644
index 0000000..623d6d5
--- /dev/null
+++ b/model/pom.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+<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/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.ge.predix</groupId>
+        <artifactId>acs</artifactId>
+        <version>5.0.3-SNAPSHOT</version>
+        <relativePath>../</relativePath>
+    </parent>
+
+    <artifactId>acs-model</artifactId>
+    <name>Predix Access Control Service API</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.6</version>
+        </dependency>
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-joda</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/model/src/main/java/com/ge/predix/acs/model/Attribute.java b/model/src/main/java/com/ge/predix/acs/model/Attribute.java
new file mode 100644
index 0000000..59ec2b1
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/model/Attribute.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.model;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@ApiModel(
+        description = "Attribute represents a characteristic of a subject or resource in context of a resource"
+                + " request. The attribute values are evaluated against any attributes declared in a access"
+                + " control policy during policy evaluation. For example, a subject may be assigned a 'group'"
+                + "attribute which would be matched against any 'group' attribute conditions in a policy.")
+@SuppressWarnings({ "javadoc", "nls" })
+public class Attribute {
+    private String issuer;
+    private String name;
+    private String value;
+
+    public Attribute() {
+        // Default constructor.
+    }
+
+    public Attribute(final String issuer, final String name, final String value) {
+        this(issuer, name);
+        this.value = value;
+    }
+
+    public Attribute(final String issuer, final String name) {
+        this.issuer = issuer;
+        this.name = name;
+    }
+
+    /**
+     * @return the issuer
+     */
+    @ApiModelProperty(value = "The entity vouching for this attribute.", required = true)
+    public String getIssuer() {
+        return this.issuer;
+    }
+
+    /**
+     * @param issuer
+     *            the issuer to set
+     */
+    public void setIssuer(final String issuer) {
+        this.issuer = issuer;
+    }
+
+    /**
+     * @return the name
+     */
+    @ApiModelProperty(value = "The unique name of this attribute.", required = true)
+    public String getName() {
+        return this.name;
+    }
+
+    /**
+     * @param name
+     *            the name to set
+     */
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    @ApiModelProperty(
+            value = "The value of this attribute. Optional when used in context of a Policy Target.",
+            required = true)
+    public String getValue() {
+        return this.value;
+    }
+
+    public void setValue(final String value) {
+        this.value = value;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(this.issuer).append(this.name).append(this.value).toHashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+
+        if (obj instanceof Attribute) {
+            final Attribute other = (Attribute) obj;
+            return new EqualsBuilder().append(this.issuer, other.issuer).append(this.name, other.name)
+                    .append(this.value, other.value).isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "Attribute [issuer=" + this.issuer + ", name=" + this.name + ", value=" + this.value + "]";
+    }
+}
diff --git a/model/src/main/java/com/ge/predix/acs/model/Condition.java b/model/src/main/java/com/ge/predix/acs/model/Condition.java
new file mode 100644
index 0000000..a3c085c
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/model/Condition.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.model;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings({ "javadoc", "nls" })
+@ApiModel(
+        description = "A boolean groovy expression which determines whether the policy effect applies to the"
+                + " access control request.")
+public class Condition {
+    private String name;
+    private String condition;
+
+    public Condition() {
+        // Default constructor.
+    }
+
+    public Condition(final String condition) {
+        this.condition = condition;
+    }
+
+    public Condition(final String name, final String condition) {
+        this.condition = condition;
+        this.name = name;
+    }
+
+    /**
+     * @return the name
+     */
+    public String getName() {
+        return this.name;
+    }
+
+    /**
+     * @param name
+     *            the name to set
+     */
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    /**
+     * @return the condition
+     */
+    @ApiModelProperty(required = true)
+    public String getCondition() {
+        return this.condition;
+    }
+
+    /**
+     * @param condition
+     *            the condition to set
+     */
+    public void setCondition(final String condition) {
+        this.condition = condition;
+    }
+
+    @Override
+    public String toString() {
+        return "Condition [name=" + this.name + ", condition=" + this.condition + "]";
+    }
+
+}
diff --git a/model/src/main/java/com/ge/predix/acs/model/Effect.java b/model/src/main/java/com/ge/predix/acs/model/Effect.java
new file mode 100644
index 0000000..0c6b1d7
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/model/Effect.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.model;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings("javadoc")
+public enum Effect {
+    PERMIT, DENY, NOT_APPLICABLE, INDETERMINATE
+}
diff --git a/model/src/main/java/com/ge/predix/acs/model/Policy.java b/model/src/main/java/com/ge/predix/acs/model/Policy.java
new file mode 100644
index 0000000..854f50b
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/model/Policy.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.model;
+
+import java.util.Collections;
+import java.util.List;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@ApiModel(description = "An access control policy.")
+@SuppressWarnings("javadoc")
+public class Policy {
+    private String name;
+    private Target target;
+    private List<Condition> conditions = Collections.emptyList();
+    private Effect effect;
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    @ApiModelProperty(required = true)
+    public Target getTarget() {
+        return this.target;
+    }
+
+    public void setTarget(final Target target) {
+        this.target = target;
+    }
+
+    /**
+     * @return the conditions
+     */
+    @ApiModelProperty(required = true)
+    public List<Condition> getConditions() {
+        return this.conditions;
+    }
+
+    /**
+     * @param conditions
+     *            the conditions to set
+     */
+    public void setConditions(final List<Condition> conditions) {
+        this.conditions = conditions;
+    }
+
+    @ApiModelProperty(required = true)
+    public Effect getEffect() {
+        return this.effect;
+    }
+
+    public void setEffect(final Effect effect) {
+        this.effect = effect;
+    }
+
+    @SuppressWarnings("nls")
+    @Override
+    public String toString() {
+        return "Policy [name=" + this.name + ", target=" + this.target + ", conditions=" + this.conditions + ", effect="
+                + this.effect + "]";
+    }
+
+}
diff --git a/model/src/main/java/com/ge/predix/acs/model/PolicySet.java b/model/src/main/java/com/ge/predix/acs/model/PolicySet.java
new file mode 100644
index 0000000..9d99a1c
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/model/PolicySet.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @author acs-engineers@ge.com
+ */
+@ApiModel(description = "A collection of access control policies evaluated in order. The first applicable policy"
+        + " determines the access control effect.")
+@SuppressWarnings("javadoc")
+public class PolicySet {
+    private List<Policy> policies = new ArrayList<>();
+    private String name;
+
+    public PolicySet() {
+        // required for jackson serialization
+    }
+
+    public PolicySet(final String name) {
+        this.name = name;
+    }
+
+    /**
+     * @return All policies in this policy set.
+     */
+    @ApiModelProperty(value = "A non empty list of Policies that define the Policy set",
+            required = true)
+    public List<Policy> getPolicies() {
+        return this.policies;
+    }
+
+    /**
+     * @param policies the policies to set
+     */
+    public void setPolicies(final List<Policy> policies) {
+        this.policies = policies;
+    }
+
+    @ApiModelProperty(value = "User defined name for the Policy set",
+            required = false)
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    @SuppressWarnings("nls")
+    @Override
+    public String toString() {
+        return "PolicySet [policies=" + this.policies + ", name=" + this.name + "]";
+    }
+
+}
diff --git a/model/src/main/java/com/ge/predix/acs/model/ResourceType.java b/model/src/main/java/com/ge/predix/acs/model/ResourceType.java
new file mode 100644
index 0000000..31bb31d
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/model/ResourceType.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.model;
+
+import java.util.List;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiParam;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@ApiModel("The resource the access control operation would read, create, delete, or modify. Typically used"
+        + " within a policy definition")
+public class ResourceType {
+
+    private String name;
+    private String uriTemplate;
+    private List<Attribute> attributes;
+    private String attributeUriTemplate;
+
+    @SuppressWarnings("javadoc")
+    public String getName() {
+        return this.name;
+    }
+
+    @SuppressWarnings("javadoc")
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    @SuppressWarnings("javadoc")
+    public String getUriTemplate() {
+        return this.uriTemplate;
+    }
+
+    @SuppressWarnings("javadoc")
+    public void setUriTemplate(final String uriTemplate) {
+        this.uriTemplate = uriTemplate;
+    }
+
+    @SuppressWarnings("javadoc")
+    public List<Attribute> getAttributes() {
+        return this.attributes;
+    }
+
+    @SuppressWarnings("javadoc")
+    public void setAttributes(final List<Attribute> attributes) {
+        this.attributes = attributes;
+    }
+
+    @ApiParam("If this is not specified, ACS uses resourceURI in the evaluation request as the resource URI to lookup "
+            + "resource attributes. This URI template can be used to extract a contiguous subset of resourceURI. "
+            + "For example, /region/us/report/asset/12 in evaluation request can be mapped to a resource /asset/12 by "
+            + "defining attributeUriTemplate as /region/us/report{attribute_uri}. ACS extracts the value of URI "
+            + "Template variable 'attribute_uri' as the resourceURI. ")
+    public String getAttributeUriTemplate() {
+        return this.attributeUriTemplate;
+    }
+
+    public void setAttributeUriTemplate(final String attributeUriTemplate) {
+        this.attributeUriTemplate = attributeUriTemplate;
+    }
+
+}
diff --git a/model/src/main/java/com/ge/predix/acs/model/SubjectType.java b/model/src/main/java/com/ge/predix/acs/model/SubjectType.java
new file mode 100644
index 0000000..848c6e4
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/model/SubjectType.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.model;
+
+import java.util.Collections;
+import java.util.List;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@ApiModel(description = "The entity performing the access controlled operation.")
+public class SubjectType {
+    private String name;
+    private List<Attribute> attributes = Collections.emptyList();
+
+    /**
+     * @return the name
+     */
+    @ApiModelProperty(required = true)
+    public String getName() {
+        return this.name;
+    }
+
+    /**
+     * @param name
+     *            the name to set
+     */
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    /**
+     * @return the attributes
+     */
+    public List<Attribute> getAttributes() {
+        return this.attributes;
+    }
+
+    /**
+     * @param attributes
+     *            the attributes to set
+     */
+    public void setAttributes(final List<Attribute> attributes) {
+        this.attributes = attributes;
+    }
+
+}
diff --git a/model/src/main/java/com/ge/predix/acs/model/Target.java b/model/src/main/java/com/ge/predix/acs/model/Target.java
new file mode 100644
index 0000000..7ecd0a2
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/model/Target.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.model;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@ApiModel(description = "Determines whether a policy applies to an access control request.")
+@SuppressWarnings("javadoc")
+public class Target {
+
+    private String name;
+    private SubjectType subject;
+    private String action;
+    private ResourceType resource;
+
+    public Target() {
+        super();
+    }
+
+    public Target(final String name, final SubjectType subject, final String action, final ResourceType resource) {
+        super();
+        this.name = name;
+        this.subject = subject;
+        this.action = action;
+        this.resource = resource;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    /**
+     * @return the subject
+     */
+    public SubjectType getSubject() {
+        return this.subject;
+    }
+
+    /**
+     * @param subject
+     *            the subject to set
+     */
+    public void setSubject(final SubjectType subject) {
+        this.subject = subject;
+    }
+
+    /**
+     * @return the action
+     */
+    @ApiModelProperty(
+            value = "String containing one or more comma separated HTTP verbs, "
+                    + "ex: \"GET\" or \"GET, POST\". If not specified it will match \"GET, POST, PUT, DELETE, PATCH\" ",
+            required = false)
+    public String getAction() {
+        return this.action;
+    }
+
+    /**
+     * @param action
+     *            the action to set
+     */
+    public void setAction(final String action) {
+        this.action = action;
+    }
+
+    public ResourceType getResource() {
+        return this.resource;
+    }
+
+    public void setResource(final ResourceType resource) {
+        this.resource = resource;
+    }
+
+}
diff --git a/model/src/main/java/com/ge/predix/acs/rest/AttributeAdapterConnection.java b/model/src/main/java/com/ge/predix/acs/rest/AttributeAdapterConnection.java
new file mode 100644
index 0000000..f852518
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/rest/AttributeAdapterConnection.java
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.rest;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+@ApiModel(description = "Connection configuration for an adapter to retrieve external resource or subject attributes.")
+public class AttributeAdapterConnection {
+
+    private String adapterEndpoint;
+
+    private String uaaTokenUrl;
+
+    private String uaaClientId;
+
+    private String uaaClientSecret;
+
+    public AttributeAdapterConnection() {
+        // Required to be here for Jackson deserialization
+    }
+
+    public AttributeAdapterConnection(final String adapterEndpoint, final String uaaTokenUrl, final String uaaClientId,
+            final String uaaClientSecret) {
+        this.adapterEndpoint = adapterEndpoint;
+        this.uaaTokenUrl = uaaTokenUrl;
+        this.uaaClientId = uaaClientId;
+        this.uaaClientSecret = uaaClientSecret;
+    }
+
+    public AttributeAdapterConnection(final AttributeAdapterConnection other) {
+        this.adapterEndpoint = other.adapterEndpoint;
+        this.uaaTokenUrl = other.uaaTokenUrl;
+        this.uaaClientId = other.uaaClientId;
+        this.uaaClientSecret = other.uaaClientSecret;
+    }
+
+    @ApiModelProperty(value = "Adapter URL to retrieve attributes from", required = true)
+    public String getAdapterEndpoint() {
+        return adapterEndpoint;
+    }
+
+    public void setAdapterEndpoint(final String adapterEndpoint) {
+        this.adapterEndpoint = adapterEndpoint;
+    }
+
+    @ApiModelProperty(value = "UAA URL to request an access token for this adapter", required = true)
+    public String getUaaTokenUrl() {
+        return uaaTokenUrl;
+    }
+
+    public void setUaaTokenUrl(final String uaaTokenUrl) {
+        this.uaaTokenUrl = uaaTokenUrl;
+    }
+
+    @ApiModelProperty(
+            value = "OAuth client used to obtain an access token for this adapter",
+            required = true)
+    public String getUaaClientId() {
+        return uaaClientId;
+    }
+
+    public void setUaaClientId(final String uaaClientId) {
+        this.uaaClientId = uaaClientId;
+    }
+
+    @ApiModelProperty(value = "OAuth client secret used to obtain an access token for this adapter", required = true)
+    public String getUaaClientSecret() {
+        return uaaClientSecret;
+    }
+
+    public void setUaaClientSecret(final String uaaClientSecret) {
+        this.uaaClientSecret = uaaClientSecret;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(this.adapterEndpoint).append(this.uaaTokenUrl).append(this.uaaClientId)
+                .toHashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof AttributeAdapterConnection) {
+            AttributeAdapterConnection other = (AttributeAdapterConnection) obj;
+            return new EqualsBuilder().append(this.adapterEndpoint, other.adapterEndpoint)
+                    .append(this.uaaTokenUrl, other.uaaTokenUrl).append(this.uaaClientId, other.uaaClientId).isEquals();
+        }
+        return false;
+    }
+}
diff --git a/model/src/main/java/com/ge/predix/acs/rest/AttributeConnector.java b/model/src/main/java/com/ge/predix/acs/rest/AttributeConnector.java
new file mode 100644
index 0000000..ff9f2dc
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/rest/AttributeConnector.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.rest;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+@ApiModel(description = "Connector configuration for external resource or subject attributes.")
+public class AttributeConnector {
+    private boolean isActive = false;
+
+    @JsonProperty(required = false)
+    private int maxCachedIntervalMinutes = 480; //default value
+
+    private Set<AttributeAdapterConnection> adapters;
+
+    @ApiModelProperty(value = "A flag to enable or disable the retrieval of remote attributes. Disabled by default.")
+    public boolean getIsActive() {
+        return this.isActive;
+    }
+
+    public void setIsActive(final boolean isActive) {
+        this.isActive = isActive;
+    }
+
+    @ApiModelProperty(
+            value = "Maximum time in minutes before remote attributes are refreshed. Set to 480 minutes by default")
+    public int getMaxCachedIntervalMinutes() {
+        return this.maxCachedIntervalMinutes;
+    }
+
+    public void setMaxCachedIntervalMinutes(final int maxCachedIntervalMinutes) {
+        this.maxCachedIntervalMinutes = maxCachedIntervalMinutes;
+    }
+
+    @ApiModelProperty(
+            value = "A set of adapters used to retrieve attributes from. Only one adapter is currently supported",
+            required = true)
+    public Set<AttributeAdapterConnection> getAdapters() {
+        return this.adapters;
+    }
+
+    public void setAdapters(final Set<AttributeAdapterConnection> adapters) {
+        this.adapters = adapters;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(this.isActive).append(this.maxCachedIntervalMinutes).append(this.adapters)
+                .toHashCode();
+
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof AttributeConnector) {
+            AttributeConnector other = (AttributeConnector) obj;
+            return new EqualsBuilder().append(this.isActive, other.isActive)
+                    .append(this.maxCachedIntervalMinutes, other.maxCachedIntervalMinutes)
+                    .append(this.adapters, other.adapters).isEquals();
+        }
+        return false;
+    }
+
+    public static AttributeConnector newInstance(final AttributeConnector other) {
+        AttributeConnector attributeConnector = new AttributeConnector();
+        attributeConnector.isActive = other.isActive;
+        attributeConnector.maxCachedIntervalMinutes = other.maxCachedIntervalMinutes;
+        attributeConnector.adapters = other.adapters.stream().map(AttributeAdapterConnection::new)
+                .collect(Collectors.toSet());
+        return attributeConnector;
+    }
+}
diff --git a/model/src/main/java/com/ge/predix/acs/rest/BaseResource.java b/model/src/main/java/com/ge/predix/acs/rest/BaseResource.java
new file mode 100644
index 0000000..36db08a
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/rest/BaseResource.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.rest;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.ge.predix.acs.model.Attribute;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Represents a Resource in the system identified by a subjectIdentifier.
+ */
+@ApiModel(description = "Represents a managed protected resource for V1.")
+public class BaseResource {
+
+    @ApiModelProperty(value = "The unique resource identifier, ex: \"/asset\"/sanramon", required = true)
+    private String resourceIdentifier;
+    @ApiModelProperty(value = "The list of attribute values being assigned", required = true)
+    private Set<Attribute> attributes = Collections.emptySet();
+    @ApiModelProperty(value = "A set that identifies resources whose attributes also apply to this resource during "
+            + "policy evaluation.")
+    private Set<Parent> parents = Collections.emptySet();
+
+    public BaseResource() {
+        super();
+    }
+
+    public BaseResource(final String resourceIdentifier) {
+        this.resourceIdentifier = resourceIdentifier;
+    }
+
+    public BaseResource(final String resourceIdentifier, final Set<Attribute> attributes) {
+        this.resourceIdentifier = resourceIdentifier;
+        this.attributes = attributes;
+    }
+
+    public Set<Attribute> getAttributes() {
+        return this.attributes;
+    }
+
+    public void setAttributes(final Set<Attribute> attributes) {
+        this.attributes = attributes;
+    }
+
+    public String getResourceIdentifier() {
+        return this.resourceIdentifier;
+    }
+
+    public void setResourceIdentifier(final String resourceIdentifier) {
+        this.resourceIdentifier = resourceIdentifier;
+    }
+
+    public Set<Parent> getParents() {
+        return this.parents;
+    }
+
+    public void setParents(final Set<Parent> parents) {
+        this.parents = parents;
+    }
+
+    @JsonIgnore
+    public boolean isIdentifierValid() {
+        return StringUtils.isNotBlank(getResourceIdentifier());
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(this.resourceIdentifier).toHashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof BaseResource) {
+            final BaseResource other = (BaseResource) obj;
+            return new EqualsBuilder().append(this.resourceIdentifier, other.getResourceIdentifier()).isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "BaseResource [resourceIdentifier=" + resourceIdentifier + ", attributes=" + attributes + ", parents="
+                + parents + "]";
+    }
+}
diff --git a/model/src/main/java/com/ge/predix/acs/rest/BaseSubject.java b/model/src/main/java/com/ge/predix/acs/rest/BaseSubject.java
new file mode 100644
index 0000000..9bfe1d3
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/rest/BaseSubject.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.rest;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.ge.predix.acs.model.Attribute;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Represents a Subject in the system identified by a subjectIdentifier.
+ */
+@ApiModel(description = "Represents a managed protected subject for V1.")
+public class BaseSubject {
+
+    @ApiModelProperty(value = "The unique subject identifier, ex: \"joe@ge.com\"", required = true)
+    private String subjectIdentifier;
+
+    @ApiModelProperty(value = "The list of attribute values being assigned", required = true)
+    private Set<Attribute> attributes = Collections.emptySet();
+
+    @ApiModelProperty(value = "A set that identifies subjects whose attributes also apply to this subject during"
+            + "policy evaluation.")
+    private Set<Parent> parents = Collections.emptySet();
+
+    public BaseSubject() {
+        super();
+    }
+
+    public BaseSubject(final String subjectIdentifier) {
+        this.subjectIdentifier = subjectIdentifier;
+    }
+
+    public BaseSubject(final String subjectIdentifier, final Set<Attribute> attributes) {
+        this.subjectIdentifier = subjectIdentifier;
+        this.attributes = attributes;
+    }
+
+    public Set<Attribute> getAttributes() {
+        return this.attributes;
+    }
+
+    public void setAttributes(final Set<Attribute> attributes) {
+        this.attributes = attributes;
+    }
+
+    public String getSubjectIdentifier() {
+        return this.subjectIdentifier;
+    }
+
+    public void setSubjectIdentifier(final String subjectIdentifier) {
+        this.subjectIdentifier = subjectIdentifier;
+    }
+
+    public Set<Parent> getParents() {
+        return this.parents;
+    }
+
+    public void setParents(final Set<Parent> parents) {
+        this.parents = parents;
+    }
+
+    @JsonIgnore
+    public boolean isIdentifierValid() {
+        return StringUtils.isNotBlank(getSubjectIdentifier());
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(this.subjectIdentifier).toHashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof BaseSubject) {
+            final BaseSubject other = (BaseSubject) obj;
+            return new EqualsBuilder().append(this.subjectIdentifier, other.getSubjectIdentifier()).isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "Subject [subjectIdentifier=" + this.subjectIdentifier + ", attributes=" + attributes + "]";
+    }
+}
diff --git a/model/src/main/java/com/ge/predix/acs/rest/Parent.java b/model/src/main/java/com/ge/predix/acs/rest/Parent.java
new file mode 100644
index 0000000..b8e0745
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/rest/Parent.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.rest;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.springframework.util.Assert;
+
+import com.ge.predix.acs.model.Attribute;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * Represents a source of inherited attributes.
+ */
+@ApiModel(description = "Represents a source of inherited attributes.")
+public class Parent {
+    @ApiModelProperty(
+            value = "The unique identifier of the parent, e.g. \"tom@ge.com\" or \"/site/sanramon\".",
+            required = true)
+    private String identifier;
+
+    @ApiModelProperty(
+            value = "Restrictions on when a subject inherits parent attributes based on the resource accessed,"
+                    + " i.e. a subject may only inherit the attributes from a parent subject if the user accesses"
+                    + " a resource with specific attributes. e.g. \"tom@ge.com\" inherits attributes from the"
+                    + " \"analysts\" group only when accessing resources where the \"site\" is \"sanramon\".",
+            required = true)
+    private Set<Attribute> scopes = Collections.emptySet();
+
+    public Parent() {
+        // Default constructor.
+    }
+
+    public Parent(final String identifier) {
+        this.identifier = identifier;
+    }
+
+    public Parent(final String identifier, final Set<Attribute> scopes) {
+        setScopes(scopes);
+        this.identifier = identifier;
+    }
+
+    public String getIdentifier() {
+        return this.identifier;
+    }
+
+    public void setIdentifier(final String identifier) {
+        this.identifier = identifier;
+    }
+
+    public Set<Attribute> getScopes() {
+        return this.scopes;
+    }
+
+    public void setScopes(final Set<Attribute> scopes) {
+        Assert.isTrue(scopes.size() < 2, "Multiple scope attributes are not supported, yet.");
+        this.scopes = scopes;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(this.identifier).toHashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof Parent) {
+            final Parent other = (Parent) obj;
+            return new EqualsBuilder().append(this.identifier, other.identifier).isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "Parent [identifier=" + identifier + ", scopes=" + scopes + "]";
+    }
+
+}
diff --git a/model/src/main/java/com/ge/predix/acs/rest/PolicyEvaluationRequestV1.java b/model/src/main/java/com/ge/predix/acs/rest/PolicyEvaluationRequestV1.java
new file mode 100644
index 0000000..610e011
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/rest/PolicyEvaluationRequestV1.java
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.rest;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import com.ge.predix.acs.model.Attribute;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+@SuppressWarnings({ "javadoc", "nls" })
+@ApiModel(description = "Policy evaluation request for V1.")
+public class PolicyEvaluationRequestV1 {
+
+    @Deprecated
+    public static final LinkedHashSet<String> EMPTY_POLICY_EVALUATION_ORDER = new LinkedHashSet<>();
+
+    private String resourceIdentifier;
+
+    private String subjectIdentifier;
+
+    private Set<Attribute> subjectAttributes;
+
+    private Set<Attribute> resourceAttributes;
+
+    private String action;
+
+    private LinkedHashSet<String> policySetsEvaluationOrder = new LinkedHashSet<>();
+
+    @ApiModelProperty(value = "The resource URI to be consumed",
+            required = true)
+    public String getResourceIdentifier() {
+        return this.resourceIdentifier;
+    }
+
+    public void setResourceIdentifier(final String resourceUri) {
+        this.resourceIdentifier = resourceUri;
+    }
+
+    @ApiModelProperty(value = "The subject identifier",
+            required = true)
+    public String getSubjectIdentifier() {
+        return this.subjectIdentifier;
+    }
+
+    public void setSubjectIdentifier(final String subjectIdentifier) {
+        this.subjectIdentifier = subjectIdentifier;
+    }
+
+    @ApiModelProperty(value = "Supplemental resource attributes provided by the requestor")
+    public Set<Attribute> getResourceAttributes() {
+        return this.resourceAttributes;
+    }
+
+    public void setResourceAttributes(final Set<Attribute> resourceAttributes) {
+        this.resourceAttributes = resourceAttributes;
+    }
+
+    /**
+     * @return the subjectAttributes
+     */
+    @ApiModelProperty(value = "Supplemental subject attributes provided by the requestor")
+    public Set<Attribute> getSubjectAttributes() {
+        return this.subjectAttributes;
+    }
+
+    /**
+     * @param subjectAttributes the subjectAttributes to set
+     */
+    public void setSubjectAttributes(final Set<Attribute> subjectAttributes) {
+        this.subjectAttributes = subjectAttributes;
+    }
+
+    @ApiModelProperty(value = "The action on the given resource URI",
+            required = true)
+    public String getAction() {
+        return this.action;
+    }
+
+    public void setAction(final String action) {
+        this.action = action;
+    }
+
+    @ApiModelProperty(value =
+            "This list of policy set IDs specifies the order in which the service will evaluate policies. "
+                    + "Evaluation stops when a policy with matching target is found and the condition returns true, "
+                    + "Or all policies are exhausted.")
+    public LinkedHashSet<String> getPolicySetsEvaluationOrder() {
+        return this.policySetsEvaluationOrder;
+    }
+
+    public void setPolicySetsEvaluationOrder(final LinkedHashSet<String> policySetIds) {
+        if (policySetIds != null) {
+            this.policySetsEvaluationOrder = policySetIds;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        HashCodeBuilder hashCodeBuilder = new HashCodeBuilder();
+        hashCodeBuilder.append(this.action).append(this.resourceIdentifier).append(this.subjectIdentifier);
+        if (null != this.subjectAttributes) {
+            for (Attribute attribute : this.subjectAttributes) {
+                hashCodeBuilder.append(attribute);
+            }
+        }
+        for (String policyID : this.policySetsEvaluationOrder) {
+            hashCodeBuilder.append(policyID);
+        }
+        return hashCodeBuilder.toHashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+
+        if (obj instanceof PolicyEvaluationRequestV1) {
+            final PolicyEvaluationRequestV1 other = (PolicyEvaluationRequestV1) obj;
+            EqualsBuilder equalsBuilder = new EqualsBuilder();
+
+            // Element by element comparison may produce true negative in Sets so use built in equals
+            // From AbstractSet's (HashSet's ancestor) documentation
+            // This implementation first checks if the specified object is this set; if so it returns true.
+            // Then, it checks if the specified object is a set whose size is identical to the size of this set;
+            // if not, it returns false. If so, it returns containsAll((Collection) o).
+            equalsBuilder.append(this.subjectAttributes, other.subjectAttributes);
+            equalsBuilder.append(this.policySetsEvaluationOrder, other.policySetsEvaluationOrder);
+
+            equalsBuilder.append(this.action, other.action).append(this.resourceIdentifier, other.resourceIdentifier)
+                    .append(this.subjectIdentifier, other.subjectIdentifier);
+            return equalsBuilder.isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "PolicyEvaluationRequest [resourceIdentifier=" + this.resourceIdentifier + ", subjectIdentifier="
+                + this.subjectIdentifier + ", action=" + this.action + "]";
+    }
+
+}
diff --git a/model/src/main/java/com/ge/predix/acs/rest/PolicyEvaluationResult.java b/model/src/main/java/com/ge/predix/acs/rest/PolicyEvaluationResult.java
new file mode 100644
index 0000000..a84dbde
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/rest/PolicyEvaluationResult.java
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.rest;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Effect;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ *
+ */
+@SuppressWarnings({ "javadoc", "nls" })
+@ApiModel(description = "Policy evaluation result")
+public class PolicyEvaluationResult {
+
+    @ApiModelProperty(value = "The effect of the policy evaluation", required = true)
+    private Effect effect;
+
+    @ApiModelProperty(value = "The collection of the subject's attributes", required = false)
+    private Set<Attribute> subjectAttributes = Collections.emptySet();
+
+    @ApiModelProperty(value = "The collection of the resource's attributes", required = false)
+    private List<Attribute> resourceAttributes = Collections.emptyList();
+
+    @ApiModelProperty(
+            value = "The resources that matched the policy evaluation request's resource identifier based on "
+                    + "the attribute uri templates defined in the policy set. For example, a policy request of"
+                    + "    /v1/site/1/plant/asset/1\nagainst a policy set with attribute uri templates:\n"
+                    + "    /v1{attribute_uri}/plant/asset/{asset_id}\n    /v1/site/{site_id}/plant{attribute_uri}\n"
+                    + "would include:\n" + "    /site/1\n    /asset/2\n" + "in this set, respectively.",
+            required = false)
+    private Set<String> resolvedResourceUris = Collections.emptySet();
+
+    private long timestamp;
+
+    private String message;
+
+    public PolicyEvaluationResult() {
+        this.effect = Effect.NOT_APPLICABLE;
+    }
+
+    public PolicyEvaluationResult(final Effect decision) {
+        this.effect = decision;
+    }
+
+    public PolicyEvaluationResult(final Effect effect, final Set<Attribute> subjectAttributes,
+            final List<Attribute> resourceAttributes) {
+        this.effect = effect;
+        this.subjectAttributes = subjectAttributes;
+        this.resourceAttributes = resourceAttributes;
+    }
+
+    public PolicyEvaluationResult(final Effect effect, final Set<Attribute> subjectAttributes,
+            final List<Attribute> resourceAttributes, final Set<String> resolvedResourceUris) {
+        this.effect = effect;
+        this.subjectAttributes = subjectAttributes;
+        this.resourceAttributes = resourceAttributes;
+        this.resolvedResourceUris = resolvedResourceUris;
+    }
+
+    public Effect getEffect() {
+        return this.effect;
+    }
+
+    public void setEffect(final Effect effect) {
+        this.effect = effect;
+    }
+
+    public Set<Attribute> getSubjectAttributes() {
+        return this.subjectAttributes;
+    }
+
+    public void setSubjectAttributes(final Set<Attribute> subjectAttributes) {
+        this.subjectAttributes = subjectAttributes;
+    }
+
+    public List<Attribute> getResourceAttributes() {
+        return this.resourceAttributes;
+    }
+
+    public void setResourceAttributes(final List<Attribute> resourceAttributes) {
+        this.resourceAttributes = resourceAttributes;
+    }
+
+    public Set<String> getResolvedResourceUris() {
+        return this.resolvedResourceUris;
+    }
+
+    public void setResolvedResourceUris(final Set<String> resolvedResourceUris) {
+        this.resolvedResourceUris = resolvedResourceUris;
+    }
+
+    public long getTimestamp() {
+        return this.timestamp;
+    }
+
+    public void setTimestamp(final long timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    @Override
+    public String toString() {
+        return "PolicyEvaluationResult [effect=" + this.effect + ", subjectAttributes=" + this.subjectAttributes
+                + ", resourceAttributes=" + this.resourceAttributes + "]";
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(final String message) {
+        this.message = message;
+    }
+}
diff --git a/model/src/main/java/com/ge/predix/acs/rest/RestModelIdExtractor.java b/model/src/main/java/com/ge/predix/acs/rest/RestModelIdExtractor.java
new file mode 100644
index 0000000..d53766b
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/rest/RestModelIdExtractor.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.rest;
+
+import java.util.Map;
+
+import org.springframework.web.util.UriTemplate;
+
+/**
+ * Utility class to extract ids from a given URI according to a given URI template.
+ *
+ * @author acs-engineers@ge.com
+ */
+public class RestModelIdExtractor {
+
+    private final UriTemplate uriTemplate;
+
+    /**
+     * Constructor given URI template.
+     *
+     * @param uriTemplateValue
+     *            URI template used by the model
+     */
+    public RestModelIdExtractor(final String uriTemplateValue) {
+        this.uriTemplate = new UriTemplate(uriTemplateValue);
+    }
+
+    /**
+     * Extracts the "pathVariable" from the given "uri".
+     *
+     * @param uri
+     *            The input uri
+     * @param pathVariable
+     *            The path variable being extracted
+     * @return the value of the pathVariable
+     */
+    public String extractId(final String uri, final String pathVariable) {
+        try {
+            Map<String, String> pathVariables = this.uriTemplate.match(uri);
+            return pathVariables.get(pathVariable);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}
diff --git a/model/src/main/java/com/ge/predix/acs/rest/Zone.java b/model/src/main/java/com/ge/predix/acs/rest/Zone.java
new file mode 100644
index 0000000..80dafcc
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/rest/Zone.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.rest;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * A zone encapsulates all policies and privilege data maintained by ACS for its users. It is a mechanism to define
+ * partitioned of authorization data for users of ACS.
+ *
+ * @author acs-engineers@ge.com
+ */
+public class Zone {
+
+    private String name;
+    private String description;
+    private String subdomain;
+
+    public Zone() {
+        // Intentionally left blank to setup this object with setters
+    }
+
+    public Zone(final String name, final String subdomain, final String description) {
+        this.name = name;
+        this.subdomain = subdomain;
+        this.description = description;
+    }
+
+    /**
+     * Unique name for this zone.
+     *
+     * @return
+     */
+    @JsonProperty(required = true)
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    /**
+     * DNS subdomain for accessing this zone on ACS service.
+     *
+     * @return
+     */
+    @JsonProperty(required = true)
+    public String getSubdomain() {
+        return this.subdomain;
+    }
+
+    public void setSubdomain(final String subdomain) {
+        this.subdomain = subdomain;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(this.name).append(this.description).append(this.subdomain).toHashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof Zone) {
+            final Zone other = (Zone) obj;
+            return new EqualsBuilder().append(this.name, other.name).append(this.description, other.description)
+                    .append(this.subdomain, other.subdomain).isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "Zone [name=" + this.name + ", description=" + this.description + ", subdomain=" + this.subdomain + "]";
+    }
+
+}
diff --git a/model/src/main/java/com/ge/predix/acs/rest/attribute/adapter/AttributesResponse.java b/model/src/main/java/com/ge/predix/acs/rest/attribute/adapter/AttributesResponse.java
new file mode 100644
index 0000000..b8ff3c1
--- /dev/null
+++ b/model/src/main/java/com/ge/predix/acs/rest/attribute/adapter/AttributesResponse.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.rest.attribute.adapter;
+
+import java.util.Set;
+
+import com.ge.predix.acs.model.Attribute;
+
+public class AttributesResponse {
+
+    private Set<Attribute> attributes;
+    private String id;
+
+    public AttributesResponse() {
+        // Default constructor necessary for Jackson
+    }
+
+    public AttributesResponse(final Set<Attribute> attributes, final String id) {
+        this.attributes = attributes;
+        this.id = id;
+    }
+
+    public Set<Attribute> getAttributes() {
+        return this.attributes;
+    }
+
+    public void setAttributes(final Set<Attribute> attributes) {
+        this.attributes = attributes;
+    }
+
+    public String getId() {
+        return this.id;
+    }
+
+    public void setId(final String id) {
+        this.id = id;
+    }
+}
diff --git a/model/src/main/resources/META-INF/LICENSE b/model/src/main/resources/META-INF/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/model/src/main/resources/META-INF/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
\ No newline at end of file
diff --git a/model/src/test/java/com/ge/predix/acs/model/ParentTest.java b/model/src/test/java/com/ge/predix/acs/model/ParentTest.java
new file mode 100644
index 0000000..870f8fd
--- /dev/null
+++ b/model/src/test/java/com/ge/predix/acs/model/ParentTest.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.model;
+
+import com.ge.predix.acs.rest.Parent;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+public class ParentTest {
+    
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void testMultipleScopesNotAllowed() {
+        Attribute a1 = new Attribute("issuer", "a1");
+        Attribute a2 = new Attribute("issuer", "a2");
+        new Parent("testParent", new HashSet<Attribute>(Arrays.asList(new Attribute[] {  a1, a2 })));
+    }
+
+}
diff --git a/model/src/test/java/com/ge/predix/acs/model/PolicySetTest.java b/model/src/test/java/com/ge/predix/acs/model/PolicySetTest.java
new file mode 100644
index 0000000..f332cc5
--- /dev/null
+++ b/model/src/test/java/com/ge/predix/acs/model/PolicySetTest.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.model;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.exc.InvalidFormatException;
+
+public class PolicySetTest {
+
+    private static final String POLICY_1_FILE_PATH = "src/test/resources/policy-1.json";
+    private static final String POLICY_UNSUPPORTED_EFFECT_FILE_PATH = "src/test/resources"
+            + "/policy-unsupported-effect.json";
+    private PolicySet policySet;
+
+    @BeforeClass
+    public void beforeClass() throws IOException {
+        File file = new File(POLICY_1_FILE_PATH);
+        this.policySet = PolicySets.loadFromFile(file);
+    }
+
+    @Test
+    public void testLoadPolicySet() throws Exception {
+        Assert.assertNotNull(this.policySet);
+        Assert.assertEquals(this.policySet.getPolicies().size(), 7);
+    }
+
+    @Test
+    public void testLoadTargetName() {
+        Assert.assertEquals(this.policySet.getPolicies().get(0).getTarget().getName(), "When an operator reads a site");
+    }
+
+    @Test
+    public void testLoadTargetResourceName() {
+        Assert.assertEquals(this.policySet.getPolicies().get(0).getTarget().getResource().getName(), "Site");
+    }
+
+    @Test
+    public void testLoadTargetAction() {
+        Assert.assertEquals(this.policySet.getPolicies().get(0).getTarget().getAction(), "GET");
+    }
+
+    @Test
+    public void testLoadTargetSubjectName() {
+        Assert.assertEquals(this.policySet.getPolicies().get(0).getTarget().getSubject().getName(), "Operator");
+    }
+
+    @Test
+    public void testLoadConditions() {
+        Assert.assertEquals(this.policySet.getPolicies().get(0).getConditions().size(), 1);
+    }
+
+    @Test
+    public void testLoadConditionName() {
+        Assert.assertEquals(this.policySet.getPolicies().get(0).getConditions().get(0).getName(),
+                "is assigned to site");
+    }
+
+    @Test
+    public void testLoadEffect() {
+        Assert.assertEquals(this.policySet.getPolicies().get(0).getEffect(), Effect.PERMIT);
+    }
+
+    @Test(expectedExceptions = InvalidFormatException.class)
+    public void testLoadUnsupportedEffect() throws IOException {
+        File file = new File(POLICY_UNSUPPORTED_EFFECT_FILE_PATH);
+        PolicySets.loadFromFile(file);
+    }
+}
diff --git a/model/src/test/java/com/ge/predix/acs/model/PolicySets.java b/model/src/test/java/com/ge/predix/acs/model/PolicySets.java
new file mode 100644
index 0000000..4a9e6e4
--- /dev/null
+++ b/model/src/test/java/com/ge/predix/acs/model/PolicySets.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.model;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+public final class PolicySets {
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+    private PolicySets() {
+        // Prevents instantiation.
+    }
+
+    @SuppressWarnings("javadoc")
+    public static PolicySet loadFromFile(final File file) throws IOException {
+        return OBJECT_MAPPER.readValue(file, PolicySet.class);
+    }
+}
diff --git a/model/src/test/java/com/ge/predix/acs/rest/PolicyEvaluationRequestV1Test.java b/model/src/test/java/com/ge/predix/acs/rest/PolicyEvaluationRequestV1Test.java
new file mode 100644
index 0000000..8358cfc
--- /dev/null
+++ b/model/src/test/java/com/ge/predix/acs/rest/PolicyEvaluationRequestV1Test.java
@@ -0,0 +1,364 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.rest;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.model.Attribute;
+
+public class PolicyEvaluationRequestV1Test {
+
+    public static final LinkedHashSet<String> EVALUATION_ORDER_P1_P2 = Stream.of("P1", "P2")
+            .collect(Collectors.toCollection(LinkedHashSet::new));
+    public static final LinkedHashSet<String> EVALUATION_ORDER_P2_P1 = Stream.of("P2", "P1")
+            .collect(Collectors.toCollection(LinkedHashSet::new));
+
+    @Test
+    public void testEqualsNoAttributes() {
+        PolicyEvaluationRequestV1 a = new PolicyEvaluationRequestV1();
+        a.setSubjectIdentifier("subject");
+        a.setAction("GET");
+        a.setResourceIdentifier("/resource");
+
+        PolicyEvaluationRequestV1 b = new PolicyEvaluationRequestV1();
+        b.setSubjectIdentifier("subject");
+        b.setAction("GET");
+        b.setResourceIdentifier("/resource");
+        Assert.assertEquals(a, b);
+    }
+
+    @Test
+    public void testEqualsSameAttributesAndPolicySetsPriority() {
+        PolicyEvaluationRequestV1 a = new PolicyEvaluationRequestV1();
+        a.setSubjectIdentifier("subject");
+        a.setAction("GET");
+        a.setResourceIdentifier("/resource");
+        a.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] {
+                                new Attribute("issuer", "role"),
+                                new Attribute("issuer", "group")
+                                })));
+        a.setPolicySetsEvaluationOrder(EVALUATION_ORDER_P1_P2);
+
+        PolicyEvaluationRequestV1 b = new PolicyEvaluationRequestV1();
+        b.setSubjectIdentifier("subject");
+        b.setAction("GET");
+        b.setResourceIdentifier("/resource");
+        b.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] {
+                                        new Attribute("issuer", "role"),
+                                        new Attribute("issuer", "group")
+                                })));
+        b.setPolicySetsEvaluationOrder(EVALUATION_ORDER_P1_P2);
+        Assert.assertEquals(a, b);
+    }
+
+    @Test
+    public void testEqualsThisHasNoAttributes() {
+        PolicyEvaluationRequestV1 a = new PolicyEvaluationRequestV1();
+        a.setSubjectIdentifier("subject");
+        a.setAction("GET");
+        a.setResourceIdentifier("/resource");
+
+        PolicyEvaluationRequestV1 b = new PolicyEvaluationRequestV1();
+        b.setSubjectIdentifier("subject");
+        b.setAction("GET");
+        b.setResourceIdentifier("/resource");
+        b.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] {
+                                        new Attribute("issuer", "role"),
+                                        new Attribute("issuer", "group")
+                                })));
+        Assert.assertNotEquals(a, b);
+    }
+
+    @Test
+    public void testEqualsThatHasNoAttributes() {
+        PolicyEvaluationRequestV1 a = new PolicyEvaluationRequestV1();
+        a.setSubjectIdentifier("subject");
+        a.setAction("GET");
+        a.setResourceIdentifier("/resource");
+        a.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] {
+                                        new Attribute("issuer", "role"),
+                                        new Attribute("issuer", "group")
+                                })));
+
+        PolicyEvaluationRequestV1 b = new PolicyEvaluationRequestV1();
+        b.setSubjectIdentifier("subject");
+        b.setAction("GET");
+        b.setResourceIdentifier("/resource");
+        Assert.assertNotEquals(a, b);
+    }
+
+    @Test
+    public void testEqualsSizeAttributes() {
+        PolicyEvaluationRequestV1 a = new PolicyEvaluationRequestV1();
+        a.setSubjectIdentifier("subject");
+        a.setAction("GET");
+        a.setResourceIdentifier("/resource");
+        a.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] {
+                                        new Attribute("issuer", "role")
+                                })));
+
+        PolicyEvaluationRequestV1 b = new PolicyEvaluationRequestV1();
+        b.setSubjectIdentifier("subject");
+        b.setAction("GET");
+        b.setResourceIdentifier("/resource");
+        b.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] {
+                                        new Attribute("issuer", "role"),
+                                        new Attribute("issuer", "group")
+                                })));
+        Assert.assertNotEquals(a, b);
+    }
+
+    @Test
+    public void testEqualsDifferentAttributes() {
+        PolicyEvaluationRequestV1 a = new PolicyEvaluationRequestV1();
+        a.setSubjectIdentifier("subject");
+        a.setAction("GET");
+        a.setResourceIdentifier("/resource");
+        a.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] {
+                                        new Attribute("issuer", "role"),
+                                        new Attribute("issuer", "site")
+                                })));
+
+        PolicyEvaluationRequestV1 b = new PolicyEvaluationRequestV1();
+        b.setSubjectIdentifier("subject");
+        b.setAction("GET");
+        b.setResourceIdentifier("/resource");
+        b.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] {
+                                        new Attribute("issuer", "role"),
+                                        new Attribute("issuer", "group")
+                                })));
+        Assert.assertNotEquals(a, b);
+    }
+
+    @Test
+    public void testEqualsDifferentOrderPolicySetPriorities() {
+        PolicyEvaluationRequestV1 a = new PolicyEvaluationRequestV1();
+        a.setSubjectIdentifier("subject");
+        a.setAction("GET");
+        a.setResourceIdentifier("/resource");
+        a.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] { 
+                                        new Attribute("issuer", "role"), 
+                                        new Attribute("issuer", "site") 
+                                })));
+        a.setPolicySetsEvaluationOrder(EVALUATION_ORDER_P1_P2);
+
+        PolicyEvaluationRequestV1 b = new PolicyEvaluationRequestV1();
+        b.setSubjectIdentifier("subject");
+        b.setAction("GET");
+        b.setResourceIdentifier("/resource");
+        b.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] { 
+                                        new Attribute("issuer", "role"), 
+                                        new Attribute("issuer", "site") 
+                                })));
+        a.setPolicySetsEvaluationOrder(EVALUATION_ORDER_P2_P1);
+        Assert.assertNotEquals(a, b);
+    }
+
+    @Test
+    public void testHashCodeNoAttributes() {
+        PolicyEvaluationRequestV1 a = new PolicyEvaluationRequestV1();
+        a.setSubjectIdentifier("subject");
+        a.setAction("GET");
+        a.setResourceIdentifier("/resource");
+
+        PolicyEvaluationRequestV1 b = new PolicyEvaluationRequestV1();
+        b.setSubjectIdentifier("subject");
+        b.setAction("GET");
+        b.setResourceIdentifier("/resource");
+        Assert.assertEquals(a.hashCode(), b.hashCode());
+    }
+
+    @Test
+    public void testHashCodeSameAttributesAndPolicySetsPriority() {
+        PolicyEvaluationRequestV1 a = new PolicyEvaluationRequestV1();
+        a.setSubjectIdentifier("subject");
+        a.setAction("GET");
+        a.setResourceIdentifier("/resource");
+        a.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(new Attribute[] {
+                                new Attribute("issuer", "role"),
+                                new Attribute("issuer", "group")
+                        })));
+        a.setPolicySetsEvaluationOrder(EVALUATION_ORDER_P1_P2);
+
+        PolicyEvaluationRequestV1 b = new PolicyEvaluationRequestV1();
+        b.setSubjectIdentifier("subject");
+        b.setAction("GET");
+        b.setResourceIdentifier("/resource");
+        b.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] {
+                                        new Attribute("issuer", "role"),
+                                        new Attribute("issuer", "group")
+                                })));
+        b.setPolicySetsEvaluationOrder(EVALUATION_ORDER_P1_P2);
+        Assert.assertEquals(a.hashCode(), b.hashCode());
+    }
+
+    @Test
+    public void testHashCodeThisHasNoAttributes() {
+        PolicyEvaluationRequestV1 a = new PolicyEvaluationRequestV1();
+        a.setSubjectIdentifier("subject");
+        a.setAction("GET");
+        a.setResourceIdentifier("/resource");
+
+        PolicyEvaluationRequestV1 b = new PolicyEvaluationRequestV1();
+        b.setSubjectIdentifier("subject");
+        b.setAction("GET");
+        b.setResourceIdentifier("/resource");
+        b.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(new Attribute[] {
+                                new Attribute("issuer", "role"),
+                                new Attribute("issuer", "group")
+                        })));
+        Assert.assertNotEquals(a.hashCode(), b.hashCode());
+    }
+
+    @Test
+    public void testHashCodeThatHasNoAttributes() {
+        PolicyEvaluationRequestV1 a = new PolicyEvaluationRequestV1();
+        a.setSubjectIdentifier("subject");
+        a.setAction("GET");
+        a.setResourceIdentifier("/resource");
+        a.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] {
+                                        new Attribute("issuer", "role"),
+                                        new Attribute("issuer", "group")
+                                })));
+
+        PolicyEvaluationRequestV1 b = new PolicyEvaluationRequestV1();
+        b.setSubjectIdentifier("subject");
+        b.setAction("GET");
+        b.setResourceIdentifier("/resource");
+        Assert.assertNotEquals(a.hashCode(), b.hashCode());
+    }
+
+    @Test
+    public void testHashCodeSizeAttributes() {
+        PolicyEvaluationRequestV1 a = new PolicyEvaluationRequestV1();
+        a.setSubjectIdentifier("subject");
+        a.setAction("GET");
+        a.setResourceIdentifier("/resource");
+        a.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] {
+                                        new Attribute("issuer", "role")
+                                })));
+
+        PolicyEvaluationRequestV1 b = new PolicyEvaluationRequestV1();
+        b.setSubjectIdentifier("subject");
+        b.setAction("GET");
+        b.setResourceIdentifier("/resource");
+        b.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] {
+                                        new Attribute("issuer", "role"),
+                                        new Attribute("issuer", "group")
+                                })));
+        Assert.assertNotEquals(a.hashCode(), b.hashCode());
+    }
+
+    @Test
+    public void testHashCodeDifferentOrderPolicySetsPriority() {
+        PolicyEvaluationRequestV1 a = new PolicyEvaluationRequestV1();
+        a.setSubjectIdentifier("subject");
+        a.setAction("GET");
+        a.setResourceIdentifier("/resource");
+        a.setPolicySetsEvaluationOrder(EVALUATION_ORDER_P1_P2);
+
+        PolicyEvaluationRequestV1 b = new PolicyEvaluationRequestV1();
+        b.setSubjectIdentifier("subject");
+        b.setAction("GET");
+        b.setResourceIdentifier("/resource");
+        b.setPolicySetsEvaluationOrder(EVALUATION_ORDER_P2_P1);
+        Assert.assertNotEquals(a.hashCode(), b.hashCode());
+    }
+
+    @Test
+    public void testHashCodeDifferentAttributes() {
+        PolicyEvaluationRequestV1 a = new PolicyEvaluationRequestV1();
+        a.setSubjectIdentifier("subject");
+        a.setAction("GET");
+        a.setResourceIdentifier("/resource");
+        a.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] {
+                                        new Attribute("issuer", "role"),
+                                        new Attribute("issuer", "site")
+                                })));
+
+        PolicyEvaluationRequestV1 b = new PolicyEvaluationRequestV1();
+        b.setSubjectIdentifier("subject");
+        b.setAction("GET");
+        b.setResourceIdentifier("/resource");
+        b.setSubjectAttributes(
+                new HashSet<Attribute>(
+                        Arrays.asList(
+                                new Attribute[] {
+                                        new Attribute("issuer", "role"),
+                                        new Attribute("issuer", "group")
+                                })));
+        Assert.assertNotEquals(a.hashCode(), b.hashCode());
+    }
+}
diff --git a/model/src/test/resources/policy-1.json b/model/src/test/resources/policy-1.json
new file mode 100644
index 0000000..c4baeda
--- /dev/null
+++ b/model/src/test/resources/policy-1.json
@@ -0,0 +1,157 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uri.placeHolder('site_id'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read an asset if they are a member of the asset group.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can modify a site if they are assigned to the site and they are a manager.",
+            "target" : {
+                "name" : "When an operator modifies a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "PUT",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "role" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uri.placeHolder('site_id'))" },
+                { "name" : "is a manager",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'role'), 'manager')" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can create a case if they own the alarm and they are members of the asset group.",
+            "target" : {
+                "name" : "When an operator creates an case for an asset alarm",
+                "resource" : {
+                    "name" : "Asset alarm cases",
+                    "uriTemplate" : "/assets/{asset_id}/alarms/{alarm_id}/cases",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "owner" }
+                    ]
+                },
+                "action" : "POST",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "name_id" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" },
+                { "name" : "is owner of",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'name_id'), resource.attributes('https://acs.attributes.int', 'owner'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can see all sites that they are assigned to",
+            "target" : {
+                "name" : "When an operator reads all sites",
+                "resource" : {
+                    "name" : "Sites",
+                    "uriTemplate" : "/sites"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can see all assets that are assigned to their asset groups",
+            "target" : {
+                "name" : "When an operator reads all assets",
+                "resource" : {
+                    "name" : "Assets",
+                    "uriTemplate" : "/assets"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Deny all other operations by default",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/model/src/test/resources/policy-unsupported-effect.json b/model/src/test/resources/policy-unsupported-effect.json
new file mode 100644
index 0000000..6ce43c4
--- /dev/null
+++ b/model/src/test/resources/policy-unsupported-effect.json
@@ -0,0 +1,9 @@
+{
+    "name" : "Test parsing validation of policy effect.",
+    "policies" : [
+        {
+            "name" : "This effect should cause a parsing error.",
+            "effect" : "FAIL!!!"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..7dc306c
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,447 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+<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/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <organization>
+        <name>GE Digital</name>
+    </organization>
+
+    <!-- Project information necessary to deploy to Maven Central (see: http://central.sonatype.org/pages/requirements.html) -->
+    <groupId>com.ge.predix</groupId>
+    <artifactId>acs</artifactId>
+    <version>5.0.3-SNAPSHOT</version>
+    <packaging>pom</packaging>
+    <name>Predix Access Control Services Parent</name>
+    <description>Service to enforce authentication and/or authorization of certain resources</description>
+    <url>https://github.com/predix/acs</url>
+    <developers>
+        <developer>
+            <id>pdeveloper</id>
+            <name>Predix Developer</name>
+        </developer>
+    </developers>
+    <licenses>
+        <license>
+            <name>Apache License, Version 2.0</name>
+            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+            <distribution>repo</distribution>
+        </license>
+    </licenses>
+    <scm>
+        <url>https://github.com/predix/acs</url>
+    </scm>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>1.5.3.RELEASE</version>
+    </parent>
+
+    <modules>
+        <module>model</module>
+        <module>service</module>
+        <module>commons</module>
+    </modules>
+
+    <properties>
+        <!-- Maven JAVA Version -->
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven-compiler-plugin.version>2.3.2</maven-compiler-plugin.version>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <maven-bundle-plugin.version>2.3.7</maven-bundle-plugin.version>
+        <!-- Spring framework upgrade to 4.3.1 requires this version of Jackson -->
+
+        <jackson.version>2.6.7</jackson.version>
+        <jedis.version>2.9.0</jedis.version>
+        <spring-log-filter.version>1.1.3</spring-log-filter.version>
+        <springfox.swagger.version>2.6.1</springfox.swagger.version>
+        <google.guava.version>18.0</google.guava.version>
+        <springframework.data.jpa>1.7.2.RELEASE</springframework.data.jpa>
+        <h2database.version>1.4.185</h2database.version>
+        <flyway.version>3.0</flyway.version>
+        <spring-security.version>4.2.0.RELEASE</spring-security.version>
+        <titandb.version>1.1.0-SNAPSHOT</titandb.version>
+        <openjpa.version>2.4.2</openjpa.version>
+        <uaa-token-lib.version>3.3.3-SNAPSHOT</uaa-token-lib.version>
+
+        <!-- Test Dependency Version -->
+        <mockito.version>1.10.19</mockito.version>
+        <reportng.version>1.1.2</reportng.version>
+        <testng.version>6.9.10</testng.version>
+        <cucumber.version>1.2.5</cucumber.version>
+        <groovy-all.version>2.4.12</groovy-all.version>
+        <maven-surefire.version>2.20</maven-surefire.version>
+        <maven-failsafe.version>2.20</maven-failsafe.version>
+        <custom-argline>-Xms512m -Xmx1024m -XX:MaxMetaspaceSize=512m</custom-argline>
+    </properties>
+
+    <distributionManagement>
+        <repository>
+            <id>ossrh</id>
+            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+        </repository>
+        <snapshotRepository>
+            <id>ossrh</id>
+            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+        </snapshotRepository>
+    </distributionManagement>
+
+    <!-- This allows child modules to use dependency versions declared here. -->
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>com.ge.predix</groupId>
+                <artifactId>acs-model</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.ge.predix</groupId>
+                <artifactId>acs-commons</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.codehaus.groovy</groupId>
+                <artifactId>groovy-all</artifactId>
+                <version>${groovy-all.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-dependencies</artifactId>
+                <version>Dalston.RELEASE</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-project-info-reports-plugin</artifactId>
+                <version>2.9</version>
+                <type>maven-plugin</type>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <version>${springfox.swagger.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>${jackson.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.fasterxml.jackson.core</groupId>
+                    <artifactId>jackson-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.fasterxml.jackson.core</groupId>
+                    <artifactId>jackson-annotations</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+            <version>${springfox.swagger.version}</version>
+        </dependency>
+
+        <!-- Mockito and testng bring their own version of hamcrest as a transitive dependency.
+             Place hamcrest above mockito and testng to avoid version mismatch
+             that causes NoSuchMethodException -->
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-all</artifactId>
+            <version>1.3</version>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- For Testing -->
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <version>${testng.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>${mockito.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>versions-maven-plugin</artifactId>
+                <configuration>
+                    <allowSnapshots>true</allowSnapshots>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>${maven-surefire.version}</version>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <version>2.17</version>
+                <dependencies>
+                    <dependency>
+                        <groupId>com.puppycrawl.tools</groupId>
+                        <artifactId>checkstyle</artifactId>
+                        <version>7.5.1</version>
+                    </dependency>
+                </dependencies>
+                <executions>
+                    <execution>
+                        <id>validate</id>
+                        <phase>validate</phase>
+                        <configuration>
+                            <configLocation>checkstyle-config/gog-sun-checks-eclipse.xml</configLocation>
+                            <encoding>UTF-8</encoding>
+                            <consoleOutput>true</consoleOutput>
+                            <failsOnError>true</failsOnError>
+                            <logViolationsToConsole>true</logViolationsToConsole>
+                            <includeTestSourceDirectory>true</includeTestSourceDirectory>
+                            <linkXRef>false</linkXRef>
+                        </configuration>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <version>1.4.1</version>
+                <executions>
+                    <execution>
+                        <id>enforce-versions</id>
+                        <goals>
+                            <goal>enforce</goal>
+                        </goals>
+                        <configuration>
+                            <rules>
+                                <dependencyConvergence/>
+                            </rules>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <pluginManagement>
+            <plugins>
+                <!--This plugin's configuration is used to store Eclipse
+                    m2e settings only. It has no influence on the Maven build itself. -->
+                <plugin>
+                    <groupId>org.eclipse.m2e</groupId>
+                    <artifactId>lifecycle-mapping</artifactId>
+                    <version>1.0.0</version>
+                    <configuration>
+                        <lifecycleMappingMetadata>
+                            <pluginExecutions>
+                                <pluginExecution>
+                                    <pluginExecutionFilter>
+                                        <groupId>
+                                            org.apache.maven.plugins
+                                        </groupId>
+                                        <artifactId>
+                                            maven-checkstyle-plugin
+                                        </artifactId>
+                                        <versionRange>
+                                            [2.15,)
+                                        </versionRange>
+                                        <goals>
+                                            <goal>check</goal>
+                                        </goals>
+                                    </pluginExecutionFilter>
+                                    <action>
+                                        <ignore/>
+                                    </action>
+                                </pluginExecution>
+                            </pluginExecutions>
+                        </lifecycleMappingMetadata>
+                    </configuration>
+                </plugin>
+                <plugin>
+                    <groupId>org.codehaus.mojo</groupId>
+                    <artifactId>versions-maven-plugin</artifactId>
+                    <version>2.2</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+    <profiles>
+        <profile>
+            <id>with.jacoco</id>
+            <activation>
+                <property>
+                    <name>profile-name</name>
+                    <value>with.jacoco</value>
+                </property>
+            </activation>
+            <properties>
+                <jacoco.version>0.7.2.201409121644</jacoco.version>
+                <sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin>
+                <sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
+                <sonar.jacoco.reportPath>${project.basedir}/target/jacoco.exec</sonar.jacoco.reportPath>
+                <sonar.language>java</sonar.language>
+            </properties>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.jacoco</groupId>
+                        <artifactId>jacoco-maven-plugin</artifactId>
+                        <version>${jacoco.version}</version>
+                        <configuration>
+                            <destFile>${sonar.jacoco.reportPath}</destFile>
+                            <append>true</append>
+                            <excludes>
+                                <exclude>com/ge/predix/acs/model/*.class</exclude>
+                                <exclude>com/ge/predix/acs/rest/*.class</exclude>
+                                <exclude>com/ge/predix/acs/privilege/management/dao/*.class</exclude>
+                                <exclude>com/ge/predix/acs/issuer/management/dao/*.class</exclude>
+                                <exclude>com/ge/predix/acs/zone/management/dao/*.class</exclude>
+                                <exclude>com/ge/predix/acs/service/policy/admin/dao/*.class</exclude>
+                                <exclude>com/ge/predix/acs/jmx/*.class</exclude>
+                                <exclude>com/ge/predix/acs/AccessControlService.class</exclude>
+                                <exclude>db/postgres/*.class</exclude>
+                                <!-- The below classes are covered in integration
+                                    tests and these can be commented back in to get the actual code coverage
+                                    report -->
+                                <!-- <exclude>com/ge/predix/acs/privilege/management/ResourcePrivilegeManagementController.class</exclude>
+                                    <exclude>com/ge/predix/acs/monitoring/AcsMonitoringController.class</exclude>
+                                    <exclude>com/ge/predix/acs/privilege/management/SubjectPrivilegeManagementController.class</exclude>
+                                    <exclude>com/ge/predix/acs/service/policy/admin/PolicyManagementController.class</exclude>
+                                    <exclude>com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationController.class</exclude>
+                                    <exclude>com/ge/predix/acs/zone/management/ZoneController.class</exclude>
+                                    <exclude>com/ge/predix/acs/config/CloudDataSourceConfig.class </exclude> -->
+                            </excludes>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <id>agent</id>
+                                <goals>
+                                    <goal>prepare-agent</goal>
+                                </goals>
+                            </execution>
+                            <execution>
+                                <id>post-unit-test</id>
+                                <phase>test</phase>
+                                <goals>
+                                    <goal>report</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>release</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.sonatype.plugins</groupId>
+                        <artifactId>nexus-staging-maven-plugin</artifactId>
+                        <version>1.6.6</version>
+                        <extensions>true</extensions>
+                        <configuration>
+                            <serverId>ossrh</serverId>
+                            <nexusUrl>https://oss.sonatype.org/</nexusUrl>
+                            <autoReleaseAfterClose>true</autoReleaseAfterClose>
+                        </configuration>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-source-plugin</artifactId>
+                        <version>2.2.1</version>
+                        <executions>
+                            <execution>
+                                <id>attach-sources</id>
+                                <goals>
+                                    <goal>jar-no-fork</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-javadoc-plugin</artifactId>
+                        <version>2.9.1</version>
+                        <executions>
+                            <execution>
+                                <id>attach-javadocs</id>
+                                <goals>
+                                    <goal>jar</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-gpg-plugin</artifactId>
+                        <version>1.6</version>
+                        <executions>
+                            <execution>
+                                <id>sign-artifacts</id>
+                                <phase>verify</phase>
+                                <goals>
+                                    <goal>sign</goal>
+                                </goals>
+                                <configuration>
+                                    <homedir>${gpg.homedir}</homedir>
+                                    <gpgArguments>
+                                        <arg>--pinentry-mode</arg>
+                                        <arg>loopback</arg>
+                                    </gpgArguments>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+</project>
diff --git a/run-integration-tests.sh b/run-integration-tests.sh
new file mode 100755
index 0000000..6367fba
--- /dev/null
+++ b/run-integration-tests.sh
@@ -0,0 +1,56 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+#!/usr/bin/env bash
+
+function usage {
+    echo "Usage: source ./$( basename "$( python -c "import os; print os.path.abspath('${BASH_SOURCE[0]}')" )" ) [-t] [-s <maven_settings_file>]"
+}
+
+unset MVN_SETTINGS_FILE_LOC
+unset TITAN_SUFFIX
+
+while getopts ':s:t' option; do
+    case "$option" in
+        s)
+            export MVN_SETTINGS_FILE_LOC="$OPTARG"
+            ;;
+        t)
+            export TITAN_SUFFIX='-titan'
+            ;;            
+        '?' | ':')
+            usage
+            return 2
+            ;;
+    esac
+done
+
+unset PORT_OFFSET
+source ./set-env-local.sh
+
+if [ -z "$MVN_SETTINGS_FILE_LOC" ]; then
+    mvn clean package -P "public${TITAN_SUFFIX}" -D skipTests
+    cd acs-integration-tests
+    mvn clean verify -P "public${TITAN_SUFFIX}"
+    cd -
+else
+    mvn clean package -P "public${TITAN_SUFFIX}" -D skipTests -s "$MVN_SETTINGS_FILE_LOC"
+    cd acs-integration-tests
+    mvn clean verify -P "public${TITAN_SUFFIX}" -s "../${MVN_SETTINGS_FILE_LOC}"
+    cd -
+fi
\ No newline at end of file
diff --git a/service/.gitignore b/service/.gitignore
new file mode 100644
index 0000000..09cccad
--- /dev/null
+++ b/service/.gitignore
@@ -0,0 +1,15 @@
+/build
+/bin
+/target
+.classpath
+.project
+.settings
+target
+/test-output/
+/surefire-reports*/
+.DS_Store
+.springBeans
+.idea
+*.ipr
+*.iml
+.checkstyle
diff --git a/service/README.md b/service/README.md
new file mode 100644
index 0000000..bded04d
--- /dev/null
+++ b/service/README.md
@@ -0,0 +1,16 @@
+###Monitoring
+
+The following monitoring API endpoint are available:
+
+* GET /monitoring/heartbeat
+  
+  Description: Returns "alive" as long asa the ACS Service is up and running. No dependencies are checked. This is API is light weigh as can be invoked as frequently as needed.
+
+1. GET /health
+  
+  Description: Returns on of the following custom statuses:
+    - ACS_DB_OUT_OF_SERVICE: Returned when the ACS Service could not communicate with the ACS Database. 
+    - UAA_OUT_OF_SERVICE: Returned when the ACS Service could not communicate with the UAA.
+    - Other Spring Standard status values can be returned: DOWN, OUT_OF_SERVICE, UNKNOWN, UP
+
+NOTE: **Since this API does perform checks on remote resources (DB and UAA) calling it too frequently might cause additional traffic and resource consumption, please use it judiciously.**
diff --git a/service/docs/ZoneProvisioning.md b/service/docs/ZoneProvisioning.md
new file mode 100644
index 0000000..b44031e
--- /dev/null
+++ b/service/docs/ZoneProvisioning.md
@@ -0,0 +1,27 @@
+# How to Provision Zones
+
+## Get token for a user authorized to create zones
+* Eventually, this step will be done by the ACS service broker. 
+* The command below is a sample to illustrate how to get a token from any UAA instance. The value passed in Authorization header below is base64 encoded value of '{client-id:client-secret}'.  
+For the steps below, the token must be for a user/clientid combination which is available in the trusted UAA for the ACS Instance being used.
+
+```bash
+curl 'http://localhost:8080/uaa/oauth/token'  \
+	-H 'Authorization: Basic <base64(<CLIENT_ID:<CLIENT_SECRET>)>' \
+	-H 'Content-Type: application/x-www-form-urlencoded'  \
+	--data 'username=<USER_ID>&password=<PASSWORD>&grant_type=password'
+```
+	
+## Create a zone
+```bash
+curl -X PUT http://localhost:8181/v1/zone/my-acs-zone -d@<FILE_PATH>/sample-zone.json -v \
+-H "Authorization: Bearer <TOKEN_FROM_PREVIOUS_CMD>" \
+-H "Content-Type: application/json"	
+```
+
+## Verify a zone is created
+```bash
+curl -X GET http://localhost:8181/v1/zone/my-acs-zone -v \
+-H "Authorization: Bearer <TOKEN_FROM_PREVIOUS_CMD>" \
+-H "Content-Type: application/json"	
+```
diff --git a/service/docs/acs-uaa-setup.md b/service/docs/acs-uaa-setup.md
new file mode 100644
index 0000000..fe8a634
--- /dev/null
+++ b/service/docs/acs-uaa-setup.md
@@ -0,0 +1,129 @@
+# Setting Up UAA for ACS
+
+## Installing UAAC
+
+UAAC is a ruby gem that wraps RESTful API calls to UAA in a convenient
+CLI command. To install this feature:
+
+1. Install ruby on your platform
+2. Install the cloud foundry UAAC ruby gem
+```
+gem install cf-uaac
+```
+
+## Getting an Administrator Token
+
+Before you can issue commands to UAA you have to login as an
+administrator.
+
+1. Instruct UAAC to target your UAA server.
+```
+uaac target https://[host]:[port]/uaa
+```
+2. Login as the UAAC administrator.
+```
+uaac token owner get account_manager admin -s \
+[account_manager client secret] -p [admin password]
+```
+
+## Create ACS Privilege Groups
+
+The following groups designate ACS privileges to their members.
+
+1. Create group for reading and evaluating policies.
+```
+uaac group add acs.policies.read
+```
+2. Create group for writing policies.
+```
+uaac group add acs.policies.write
+```
+3. Create group for reading attributes.
+```
+uaac group add acs.attributes.read
+```
+4. Create group for writing attributes.
+```
+uaac group add acs.attributes.write
+```
+
+## Making an Application User an ACS Administrator
+
+ACS administrators can read/write policies and/or attributes. This
+user will create and maintain policies and attribute assignments that
+will enforce application privileges.
+
+1. Create an administrative user if one does not yet exist
+```
+uaac user add admin-user -p [password] --emails admin-user@example.com
+```
+2. Assign membership to the required groups.
+```
+uaac member add acs.policies.read admin-user
+```
+```
+uaac member add acs.zones.admin admin-user
+```
+```
+uaac member add acs.policies.write admin-user
+```
+```
+uaac member add acs.attributes.read admin-user
+```
+```
+uaac member add acs.attributes.write admin-user
+```
+
+## Giving Application Users Attribute Management Privileges
+
+Your application will most likely have users that can assign and
+revoke privileges to other users. This is an example of how to create
+such users or simply assign the required group membership.
+
+1. Create the application user if one does not yet exist
+```
+uaac user add app-user -p [password] --emails app-user@example.com
+```
+2. Assign membership to the required groups.
+```
+uaac member add predix-acs.zones.<acs_zone_name>.user app-user
+```
+```
+uaac member add acs.attributes.read app-user
+```
+```
+uaac member add acs.attributes.write app-user
+```
+
+## Creating or Modifying an OAuth Client to Allow ACS Operations
+
+You will need an OAuth client that allows users to perform ACS
+operations. To create the OAuth client:
+
+```
+uaac client add acs-app --scope \
+acs.policies.read,acs.policies.write,acs.attributes.read,acs.attributes.write,predix-acs.zones.<acs_zone_name>.user \
+--authorized_grant_types [grant types] --authorities uaa.resource -s [client secret]
+```
+
+Note: See UAAC help for available OAuth grant types.
+Note: If a client already exists use "uaac client update" instead.
+
+## Giving Your Application OAuth Client Policy Evaluation Privileges
+
+You need an application OAuth client in order to call the ACS policy
+evaluation service. Here's how you can create one if you have not
+already. You can reuse the client created in the previous
+step. However, do not reuse that client if you want to separate which
+applications have the ability to perform ACS administrative tasks on
+behalf of users.
+
+```
+uaac client add myapp --authorized_grant_types [grant types] -s \
+[client secret]
+```
+
+## Registering your UAA with ACS
+
+TODO: Add steps for registering UAA with ACS
+
diff --git a/service/docs/sample-zone.json b/service/docs/sample-zone.json
new file mode 100644
index 0000000..bfcf9c5
--- /dev/null
+++ b/service/docs/sample-zone.json
@@ -0,0 +1,5 @@
+{
+    "name": "my-acs-zone",
+    "subdomain": "my-zone-subdomain",
+    "description": "Sample acs zone"
+}
diff --git a/service/manifest.yml b/service/manifest.yml
new file mode 100644
index 0000000..14eded5
--- /dev/null
+++ b/service/manifest.yml
@@ -0,0 +1,17 @@
+---
+# Manifest for ACS. Properties can be overridden from the command line. This file can be used for manual cf push. 
+applications:
+- name: acs_default
+  path: ./target/acs-service-*.jar
+  memory: 1024M
+  instances: 1
+  timeout: 180
+  services:
+  - acs_db
+  env:
+    SPRING_PROFILES_ACTIVE: public
+    ACS_BASE_DOMAIN: 
+    ACS_DB: acs_db
+# The following property must be configured for the service to be functional 
+#    uaaCheckHealthUrl=http://<UAA_URL>/healthz
+
diff --git a/service/pom.xml b/service/pom.xml
new file mode 100644
index 0000000..997413a
--- /dev/null
+++ b/service/pom.xml
@@ -0,0 +1,623 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+<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/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.ge.predix</groupId>
+        <artifactId>acs</artifactId>
+        <version>5.0.3-SNAPSHOT</version>
+        <relativePath>../</relativePath>
+    </parent>
+    <artifactId>acs-service</artifactId>
+    <name>Predix Access Control Service Implementation</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-sleuth-zipkin</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.thinkaurelius.titan</groupId>
+            <artifactId>titan-core</artifactId>
+            <version>${titandb.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.commons</groupId>
+                    <artifactId>commons-lang3</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>commons-io</groupId>
+                    <artifactId>commons-io</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.codehaus.groovy</groupId>
+                    <artifactId>groovy</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.acplt</groupId>
+                    <artifactId>oncrpc</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.thinkaurelius.titan</groupId>
+            <artifactId>titan-cassandra</artifactId>
+            <version>${titandb.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>ch.qos.logback</groupId>
+                    <artifactId>logback-classic</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>ch.qos.logback</groupId>
+                    <artifactId>logback-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>io.netty</groupId>
+                    <artifactId>netty-all</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>jline</groupId>
+                    <artifactId>jline</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>commons-lang</groupId>
+                    <artifactId>commons-lang</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.thrift</groupId>
+                    <artifactId>libthrift</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.xerial.snappy</groupId>
+                    <artifactId>snappy-java</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.commons</groupId>
+                    <artifactId>commons-lang3</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.codehaus.jackson</groupId>
+                    <artifactId>jackson-mapper-asl</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.cassandra</groupId>
+                    <artifactId>cassandra-all</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.cassandra</groupId>
+                    <artifactId>cassandra-thrift</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.github.stephenc.high-scale-lib</groupId>
+                    <artifactId>high-scale-lib</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cassandra</groupId>
+            <artifactId>cassandra-thrift</artifactId>
+            <version>2.1.9</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.commons</groupId>
+                    <artifactId>commons-lang3</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.carrotsearch</groupId>
+                    <artifactId>hppc</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>log4j-over-slf4j</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cassandra</groupId>
+            <artifactId>cassandra-all</artifactId>
+            <version>2.1.9</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.thrift</groupId>
+                    <artifactId>libthrift</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.commons</groupId>
+                    <artifactId>commons-lang3</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.codehaus.jackson</groupId>
+                    <artifactId>jackson-mapper-asl</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.codehaus.jackson</groupId>
+                    <artifactId>jackson-core-asl</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>log4j-over-slf4j</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>ch.qos.logback</groupId>
+                    <artifactId>logback-classic</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>ch.qos.logback</groupId>
+                    <artifactId>logback-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>jline</groupId>
+                    <artifactId>jline</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>io.netty</groupId>
+                    <artifactId>netty-all</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.thrift</groupId>
+            <artifactId>libthrift</artifactId>
+            <version>0.9.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-commons</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>ch.qos.logback</groupId>
+                    <artifactId>logback-classic</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>log4j-over-slf4j</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.data</groupId>
+            <artifactId>spring-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jolokia</groupId>
+            <artifactId>jolokia-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.hibernate</groupId>
+                    <artifactId>hibernate-validator</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-tomcat</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>log4j-over-slf4j</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>ch.qos.logback</groupId>
+                    <artifactId>logback-classic</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jetty</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.hibernate</groupId>
+                    <artifactId>hibernate-entitymanager</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.jayway.jsonpath</groupId>
+            <artifactId>json-path</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.ow2.asm</groupId>
+                    <artifactId>asm</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.fge</groupId>
+            <artifactId>json-schema-validator</artifactId>
+            <version>2.2.6</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.google.code.findbugs</groupId>
+                    <artifactId>jsr305</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.flywaydb</groupId>
+            <artifactId>flyway-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>${google.guava.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security.oauth</groupId>
+            <artifactId>spring-security-oauth2</artifactId>
+            <!-- These transitive dependencies are incompatible with spring
+                core dependencies pulled in by spring boot -->
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-beans</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-context</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-jwt</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.bouncycastle</groupId>
+                    <artifactId>bcprov-jdk15on</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.bouncycastle</groupId>
+                    <artifactId>bcpkix-jdk15on</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.openjpa</groupId>
+            <artifactId>openjpa</artifactId>
+            <version>${openjpa.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-lang</groupId>
+                    <artifactId>commons-lang</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cloud-connectors</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.commons</groupId>
+                    <artifactId>commons-lang3</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.ge.predix</groupId>
+            <artifactId>acs-commons</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ge.predix</groupId>
+            <artifactId>acs-model</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ge.predix</groupId>
+            <artifactId>uaa-token-lib</artifactId>
+            <version>${uaa-token-lib.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.bouncycastle</groupId>
+                    <artifactId>bcprov-jdk15on</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.ge.predix</groupId>
+            <artifactId>spring-log-filter</artifactId>
+            <version>${spring-log-filter.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>ch.qos.logback</groupId>
+                    <artifactId>logback-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>ch.qos.logback</groupId>
+                    <artifactId>logback-classic</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.ge.predix</groupId>
+            <artifactId>spring-cors-filter</artifactId>
+            <version>1.0.0</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-parent</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+            <version>${jedis.version}</version><!--$NO-MVN-MAN-VER$-->
+        </dependency>
+    </dependencies>
+
+    <properties>
+        <start-class>com.ge.predix.acs.AccessControlService</start-class>
+        <jenkins.build.number>dev</jenkins.build.number>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.openjpa</groupId>
+                <artifactId>openjpa-maven-plugin</artifactId>
+                <version>${openjpa.version}</version>
+                <configuration>
+                    <includes>**/dao/*.class</includes>
+                    <addDefaultConstructor>true</addDefaultConstructor>
+                    <enforcePropertyRestrictions>true</enforcePropertyRestrictions>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>enhancer</id>
+                        <phase>process-classes</phase>
+                        <goals>
+                            <goal>enhance</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.apache.openjpa</groupId>
+                        <artifactId>openjpa</artifactId>
+                        <!-- set the version to be the same as the level
+                            in your runtime -->
+                        <version>${openjpa.version}</version>
+                    </dependency>
+                </dependencies>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <classifier>exec</classifier>
+                    <mainClass>${start-class}</mainClass>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>${maven-surefire.version}</version>
+                <configuration>
+                    <argLine>${custom-argline}</argLine>
+                    <suiteXmlFiles>
+                        <suiteXmlFile>src/test/resources/unit-tests.xml</suiteXmlFile>
+                    </suiteXmlFiles>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <version>${maven-failsafe.version}</version>
+                <configuration>
+                    <argLine>${custom-argline}</argLine>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>integration-test</id>
+                        <goals>
+                            <goal>integration-test</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>verify</id>
+                        <goals>
+                            <goal>verify</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <pluginManagement>
+            <plugins>
+                <!--This plugin's configuration is used to store Eclipse
+                    m2e settings only. It has no influence on the Maven build itself. -->
+                <plugin>
+                    <groupId>org.eclipse.m2e</groupId>
+                    <artifactId>lifecycle-mapping</artifactId>
+                    <version>1.0.0</version>
+                    <configuration>
+                        <lifecycleMappingMetadata>
+                            <pluginExecutions>
+                                <pluginExecution>
+                                    <pluginExecutionFilter>
+                                        <groupId>
+                                            org.apache.openjpa
+                                        </groupId>
+                                        <artifactId>
+                                            openjpa-maven-plugin
+                                        </artifactId>
+                                        <versionRange>
+                                            [2.3.0,)
+                                        </versionRange>
+                                        <goals>
+                                            <goal>enhance</goal>
+                                        </goals>
+                                    </pluginExecutionFilter>
+                                    <action>
+                                        <ignore/>
+                                    </action>
+                                </pluginExecution>
+                            </pluginExecutions>
+                        </lifecycleMappingMetadata>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>public</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <version>${maven-surefire.version}</version>
+                        <configuration>
+                            <environmentVariables>
+                                <SPRING_PROFILES_ACTIVE>h2,public,simple-cache</SPRING_PROFILES_ACTIVE>
+                            </environmentVariables>
+                            <reportsDirectory>${basedir}/surefire-reports</reportsDirectory>
+                        </configuration>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-failsafe-plugin</artifactId>
+                        <version>${maven-failsafe.version}</version>
+                        <configuration>
+                             <environmentVariables>
+                                <SPRING_PROFILES_ACTIVE>h2,public,simple-cache</SPRING_PROFILES_ACTIVE>
+                            </environmentVariables>
+                            <reportsDirectory>${basedir}/surefire-reports/controller-tests</reportsDirectory>
+                            <summaryFile>${basedir}/surefire-reports/controller-tests/failsafe-summary.xml</summaryFile>
+                            <excludes>
+                                <!-- Exclude graph database specific test for public profile -->
+                                <exclude>**/PolicyEvalWithGraphDbControllerIT.java</exclude>
+                                <exclude>**/Hierarchical*IT.java</exclude>
+                            </excludes>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <profile>
+            <id>public-titan</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <version>${maven-surefire.version}</version>
+                        <configuration>
+                            <environmentVariables>
+                                <SPRING_PROFILES_ACTIVE>h2,public,simple-cache,titan</SPRING_PROFILES_ACTIVE>
+                            </environmentVariables>
+                            <reportsDirectory>${basedir}/surefire-reports-titan</reportsDirectory>
+                        </configuration>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-failsafe-plugin</artifactId>
+                        <version>${maven-failsafe.version}</version>
+                        <configuration>
+                            <environmentVariables>
+                                <SPRING_PROFILES_ACTIVE>h2,public,simple-cache,titan</SPRING_PROFILES_ACTIVE>
+                            </environmentVariables>
+                            <reportsDirectory>${basedir}/surefire-reports-titan/controller-tests</reportsDirectory>
+                            <summaryFile>${basedir}/surefire-reports-titan/controller-tests/failsafe-summary.xml</summaryFile>
+                            <excludes>
+                                <exclude>**/NonHierarchical*IT.java</exclude>
+                            </excludes>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+</project>
diff --git a/service/src/main/java/com/ge/predix/acs/AccessControlService.java b/service/src/main/java/com/ge/predix/acs/AccessControlService.java
new file mode 100644
index 0000000..af84284
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/AccessControlService.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs;
+
+import static com.google.common.base.Predicates.or;
+import static springfox.documentation.builders.PathSelectors.regex;
+
+import java.net.URI;
+import java.util.Collections;
+
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ImportResource;
+
+import com.ge.predix.acs.monitoring.ManagementSecurityRoleFilter;
+import com.ge.predix.acs.request.context.AcsRequestEnrichingFilter;
+import com.google.common.base.Predicate;
+
+import io.swagger.models.Scheme;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/**
+ * Access Control Service - Spring Boot Application.
+ *
+ * @author acs-engineers@ge.com
+ */
+@SpringBootApplication
+@EnableSwagger2
+@ImportResource(value = "classpath:security-config.xml")
+public class AccessControlService {
+
+    private final AcsRequestEnrichingFilter acsRequestEnrichingFilter;
+    private final ManagementSecurityRoleFilter managementSecurityRoleFilter;
+
+    @Value("${ACS_URL:}")
+    private String acsUrl;
+
+    @Autowired
+    public AccessControlService(final AcsRequestEnrichingFilter acsRequestEnrichingFilter,
+            final ManagementSecurityRoleFilter managementSecurityRoleFilter) {
+        this.acsRequestEnrichingFilter = acsRequestEnrichingFilter;
+        this.managementSecurityRoleFilter = managementSecurityRoleFilter;
+    }
+
+    public static void main(final String[] args) {
+        SpringApplication.run(AccessControlService.class, args);
+    }
+
+    /**
+     * Register acsRequestEnrichingFilter and disable it. If not disabled spring-boot applies this to all requests. This
+     * is configured explicitly as a custom filter in security-config.xml.
+     */
+    @Bean
+    public FilterRegistrationBean filterRegistrationBean() {
+        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
+        filterRegistrationBean.setEnabled(false);
+        filterRegistrationBean.setFilter(this.acsRequestEnrichingFilter);
+        return filterRegistrationBean;
+    }
+
+    @Bean
+    public FilterRegistrationBean monitoringFilterRegistrationBean() {
+        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(this.managementSecurityRoleFilter);
+        filterRegistrationBean.addUrlPatterns("/health*");
+        filterRegistrationBean.setOrder(1);
+        return filterRegistrationBean;
+    }
+
+    @Bean
+    public Docket attributeManagementApi() {
+        return new Docket(DocumentationType.SWAGGER_2).groupName("acs")
+                .protocols(Collections.singleton(StringUtils.isEmpty(this.acsUrl) ? Scheme.HTTPS.toValue()
+                        : URI.create(this.acsUrl).getScheme()))
+                .apiInfo(apiInfo()).select().paths(attributeManagementPaths()).build();
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Predicate<String> attributeManagementPaths() {
+        return or(regex("/v1/subject.*"), regex("/v1/resource.*"), regex("/v1/policy-set.*"),
+                regex("/v1/policy-evaluation.*"), regex("/monitoring/heartbeat.*"), regex("/v1/connector.*"));
+    }
+
+    private static ApiInfo apiInfo() {
+        return new ApiInfoBuilder().title("Access Control").description("Access Control Services (ACS). ").version("v1")
+                .license("Apache 2.0").licenseUrl("https://github.com/predix/acs/blob/develop/LICENSE").build();
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/PolicyContextResolver.java b/service/src/main/java/com/ge/predix/acs/PolicyContextResolver.java
new file mode 100644
index 0000000..7181c2b
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/PolicyContextResolver.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs;
+
+/**
+ * Allows us to encapsulate the way we retrieve the origin, application id from the security context, as well as some
+ * convenient behavior like failing when certain properties are not available.
+ *
+ * @author acs-engineers@ge.com
+ */
+public interface PolicyContextResolver {
+
+    /**
+     * @return The issuer or idp id of the authenticated subject from the Security Context or throws a
+     *         InvalidACSRequestScopeException if not available
+     */
+    String getIssuerIdOrFail();
+
+    /**
+     * @return The client id of the authenticated subject from the Security Context or throws a
+     *         InvalidACSRequestScopeException if not available
+     */
+    String getClientIdOrFail();
+}
diff --git a/service/src/main/java/com/ge/predix/acs/SpringSecurityPolicyContextResolver.java b/service/src/main/java/com/ge/predix/acs/SpringSecurityPolicyContextResolver.java
new file mode 100644
index 0000000..a13171b
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/SpringSecurityPolicyContextResolver.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs;
+
+import java.util.Map;
+
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
+import org.springframework.security.oauth2.provider.OAuth2Request;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.service.InvalidACSRequestException;
+
+/**
+ * Retrieve the authentication context.
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings("nls")
+@Component
+public class SpringSecurityPolicyContextResolver implements PolicyContextResolver {
+    @Override
+    public String getIssuerIdOrFail() {
+        String issuer = null;
+        OAuth2Request oAuth2Request = getAuthentication().getOAuth2Request();
+
+        if (oAuth2Request != null) {
+            Map<String, String> requestParameters = oAuth2Request.getRequestParameters();
+
+            if (requestParameters != null && requestParameters.containsKey("iss")) {
+                issuer = requestParameters.get("iss");
+            }
+        }
+
+        if (issuer == null) {
+            throw new InvalidACSRequestException("Authetication issuer cannot be null");
+        }
+
+        return issuer;
+    }
+
+    @Override
+    public String getClientIdOrFail() {
+        String clientId = null;
+
+        OAuth2Request oAuth2Request = getAuthentication().getOAuth2Request();
+        if (oAuth2Request != null) {
+            clientId = oAuth2Request.getClientId();
+        }
+        if (clientId == null) {
+            throw new InvalidACSRequestException("Authetication clientId cannot be null");
+        }
+
+        return clientId;
+    }
+
+    private OAuth2Authentication getAuthentication() {
+        OAuth2Authentication oAuth2authentication = (OAuth2Authentication) SecurityContextHolder.getContext()
+                .getAuthentication();
+
+        // postcondition check that the oAuth2authentication should never be
+        // null
+        if (oAuth2authentication == null) {
+            throw new InvalidACSRequestException("OAuth2Authentication cannot be null");
+        }
+
+        return oAuth2authentication;
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/cache/AbstractAttributeCache.java b/service/src/main/java/com/ge/predix/acs/attribute/cache/AbstractAttributeCache.java
new file mode 100644
index 0000000..768736a
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/cache/AbstractAttributeCache.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.cache;
+
+public abstract class AbstractAttributeCache implements AttributeCache {
+
+    static String resourceKey(final String zoneId, final String identifier) {
+        return zoneId + ":attr-res-id:" + Integer.toHexString(identifier.hashCode());
+    }
+
+    static String subjectKey(final String zoneId, final String identifier) {
+        return zoneId + ":attr-sub-id:" + Integer.toHexString(identifier.hashCode());
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/cache/AttributeCache.java b/service/src/main/java/com/ge/predix/acs/attribute/cache/AttributeCache.java
new file mode 100644
index 0000000..54206ca
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/cache/AttributeCache.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.cache;
+
+import com.ge.predix.acs.attribute.readers.CachedAttributes;
+
+public interface AttributeCache {
+    String RESOURCE = "resource";
+    String SUBJECT = "subject";
+
+    default void setAttributes(final String identifier, final CachedAttributes value) {
+        this.set(identifier, value);
+    }
+
+    default CachedAttributes getAttributes(final String identifier) {
+        return this.get(identifier);
+    }
+
+    void set(String key, CachedAttributes value);
+
+    // get should return null if value is not found in the cache
+    CachedAttributes get(String key);
+
+    void flushAll();
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/cache/AttributeCacheFactory.java b/service/src/main/java/com/ge/predix/acs/attribute/cache/AttributeCacheFactory.java
new file mode 100644
index 0000000..7387505
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/cache/AttributeCacheFactory.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.cache;
+
+import java.util.function.BiFunction;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.env.Environment;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AttributeCacheFactory {
+    private static final Logger LOGGER = LoggerFactory.getLogger(AttributeCacheFactory.class);
+
+    @Value("${ENABLE_RESOURCE_CACHING:true}")
+    private boolean resourceCachingEnabled;
+
+    @Value("${ENABLE_SUBJECT_CACHING:true}")
+    private boolean subjectCachingEnabled;
+
+    private Environment environment;
+
+    @Autowired
+    public AttributeCacheFactory(final Environment environment) {
+        this.environment = environment;
+    }
+
+    private AttributeCacheFactory() {
+        throw new AssertionError();
+    }
+
+    public AttributeCache createResourceAttributeCache(final long maxCachedIntervalMinutes, final String zoneName,
+            final RedisTemplate<String, String> resourceCacheRedisTemplate) {
+        return createAttributeCache(AttributeCache.RESOURCE, maxCachedIntervalMinutes, zoneName,
+                resourceCacheRedisTemplate, AbstractAttributeCache::resourceKey, resourceCachingEnabled);
+    }
+
+    public AttributeCache createSubjectAttributeCache(final long maxCachedIntervalMinutes, final String zoneName,
+            final RedisTemplate<String, String> subjectCacheRedisTemplate) {
+        return createAttributeCache(AttributeCache.SUBJECT, maxCachedIntervalMinutes, zoneName,
+                subjectCacheRedisTemplate, AbstractAttributeCache::subjectKey, subjectCachingEnabled);
+    }
+
+    private AttributeCache createAttributeCache(final String cacheType, final long maxCachedIntervalMinutes,
+            final String zoneName, final RedisTemplate<String, String> cacheRedisTemplate,
+            final BiFunction<String, String, String> getKey, final boolean enableAttributeCaching) {
+        String[] profiles = environment.getActiveProfiles();
+
+        if (!enableAttributeCaching) {
+            LOGGER.info("Caching disabled for {} attributes.", cacheType);
+            return new NonCachingAttributeCache();
+        }
+        if (ArrayUtils.contains(profiles, "redis") || ArrayUtils.contains(profiles, "cloud-redis")) {
+            LOGGER.info("Redis caching enabled for {} attributes.", cacheType);
+            return new RedisAttributeCache(maxCachedIntervalMinutes, zoneName, getKey, cacheRedisTemplate);
+        }
+        LOGGER.info("In-memory caching enabled for {} attributes.", cacheType);
+        return new InMemoryAttributeCache(maxCachedIntervalMinutes, zoneName, getKey);
+    }
+}
\ No newline at end of file
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/cache/InMemoryAttributeCache.java b/service/src/main/java/com/ge/predix/acs/attribute/cache/InMemoryAttributeCache.java
new file mode 100644
index 0000000..ae0da05
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/cache/InMemoryAttributeCache.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.cache;
+
+import java.util.Map;
+import java.util.function.BiFunction;
+
+import org.apache.commons.collections4.map.PassiveExpiringMap;
+
+import com.ge.predix.acs.attribute.readers.CachedAttributes;
+
+public class InMemoryAttributeCache extends AbstractAttributeCache {
+
+    private String zoneName;
+    private BiFunction<String, String, String> getKey;
+    private Map<String, CachedAttributes> attributeCache;
+
+    InMemoryAttributeCache(final long maxCachedIntervalMinutes, final String zoneName,
+            final BiFunction<String, String, String> getKey) {
+        this.zoneName = zoneName;
+        this.getKey = getKey;
+        this.attributeCache = new PassiveExpiringMap<>(maxCachedIntervalMinutes);
+    }
+
+    @Override
+    public void set(final String key, final CachedAttributes value) {
+        this.attributeCache.put(this.getKey.apply(this.zoneName, key), value);
+    }
+
+    @Override
+    public CachedAttributes get(final String key) {
+        return this.attributeCache.get(this.getKey.apply(this.zoneName, key));
+
+    }
+
+    @Override
+    public void flushAll() {
+        this.attributeCache.clear();
+
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/cache/NonCachingAttributeCache.java b/service/src/main/java/com/ge/predix/acs/attribute/cache/NonCachingAttributeCache.java
new file mode 100644
index 0000000..e11037b
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/cache/NonCachingAttributeCache.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.cache;
+
+import com.ge.predix.acs.attribute.readers.CachedAttributes;
+
+public class NonCachingAttributeCache implements AttributeCache {
+
+    @Override
+    public void set(final String key, final CachedAttributes value) {
+        //Intentionally left empty to satisfy interface
+    }
+
+    @Override
+    public CachedAttributes get(final String key) {
+        return null;
+    }
+
+    @Override
+    public void flushAll() {
+        //Intentionally left empty to satisfy interface
+    }
+
+}
\ No newline at end of file
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/cache/RedisAttributeCache.java b/service/src/main/java/com/ge/predix/acs/attribute/cache/RedisAttributeCache.java
new file mode 100644
index 0000000..a7c062b
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/cache/RedisAttributeCache.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.cache;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiFunction;
+
+import org.apache.commons.lang3.StringUtils;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import com.ge.predix.acs.attribute.readers.CachedAttributes;
+
+public class RedisAttributeCache extends AbstractAttributeCache {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(RedisAttributeCache.class);
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+    private String zoneName;
+    private BiFunction<String, String, String> getKey;
+    private RedisTemplate<String, String> resourceCacheRedisTemplate;
+    private long maxCachedIntervalMinutes;
+
+    RedisAttributeCache(final long maxCachedIntervalMinutes, final String zoneName,
+            final BiFunction<String, String, String> getKey,
+            final RedisTemplate<String, String> resourceCacheRedisTemplate) {
+        this.maxCachedIntervalMinutes = maxCachedIntervalMinutes;
+        this.zoneName = zoneName;
+        this.getKey = getKey;
+        this.resourceCacheRedisTemplate = resourceCacheRedisTemplate;
+    }
+
+    @Override
+    public void set(final String key, final CachedAttributes value) {
+        String cachedValueString;
+        try {
+            cachedValueString = OBJECT_MAPPER.writeValueAsString(value);
+        } catch (IOException e) {
+            LOGGER.error("Unable to write attributes to cache.", e);
+            return;
+        }
+        this.resourceCacheRedisTemplate.opsForValue()
+                .set(this.getKey.apply(this.zoneName, key), cachedValueString, this.maxCachedIntervalMinutes,
+                        TimeUnit.MINUTES);
+        LOGGER.trace("Set key '{}', to value '{}' in attribute cache", key, cachedValueString);
+    }
+
+    @Override
+    public CachedAttributes get(final String key) {
+        String cachedValueString = this.resourceCacheRedisTemplate.opsForValue()
+                .get(this.getKey.apply(this.zoneName, key));
+        LOGGER.trace("Got value '{}' from attribute cache", cachedValueString);
+        if (StringUtils.isEmpty(cachedValueString)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(cachedValueString, CachedAttributes.class);
+        } catch (IOException e) {
+            LOGGER.error("Unable to read attributes from cache.", e);
+            return null;
+        }
+    }
+
+    @Override
+    public void flushAll() {
+        this.resourceCacheRedisTemplate.getConnectionFactory().getConnection().flushDb();
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/connector/ConnectorHttpMethodsFilter.java b/service/src/main/java/com/ge/predix/acs/attribute/connector/ConnectorHttpMethodsFilter.java
new file mode 100644
index 0000000..a124ee3
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/connector/ConnectorHttpMethodsFilter.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.connector;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.security.AbstractHttpMethodsFilter;
+
+@Component
+public class ConnectorHttpMethodsFilter extends AbstractHttpMethodsFilter {
+
+    private static final String CHANGE_GET_RESOURCE_CONNECTOR_URI_REGEX = "\\A/v1/connector/resource/??\\Z";
+    private static final String CHANGE_GET_SUBJECT_CONNECTOR_URI_REGEX = "\\A/v1/connector/subject/??\\Z";
+
+    private static Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods() {
+        Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods = new LinkedHashMap<>();
+        uriPatternsAndAllowedHttpMethods.put(CHANGE_GET_RESOURCE_CONNECTOR_URI_REGEX,
+                new HashSet<>(Arrays.asList(HttpMethod.PUT, HttpMethod.GET, HttpMethod.DELETE, HttpMethod.HEAD)));
+        uriPatternsAndAllowedHttpMethods.put(CHANGE_GET_SUBJECT_CONNECTOR_URI_REGEX,
+                new HashSet<>(Arrays.asList(HttpMethod.PUT, HttpMethod.GET, HttpMethod.DELETE, HttpMethod.HEAD)));
+        return uriPatternsAndAllowedHttpMethods;
+    }
+
+    public ConnectorHttpMethodsFilter() {
+        super(uriPatternsAndAllowedHttpMethods());
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/connector/management/AttributeConnectorController.java b/service/src/main/java/com/ge/predix/acs/attribute/connector/management/AttributeConnectorController.java
new file mode 100644
index 0000000..9e12dc4
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/connector/management/AttributeConnectorController.java
@@ -0,0 +1,177 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.connector.management;
+
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.V1;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.created;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.noContent;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.notFound;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.ok;
+import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
+import static org.springframework.web.bind.annotation.RequestMethod.GET;
+import static org.springframework.web.bind.annotation.RequestMethod.PUT;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.ge.predix.acs.commons.web.AcsApiUriTemplates;
+import com.ge.predix.acs.commons.web.BaseRestApi;
+import com.ge.predix.acs.commons.web.RestApiException;
+import com.ge.predix.acs.rest.AttributeConnector;
+
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+
+@RestController
+public class AttributeConnectorController extends BaseRestApi {
+
+    @Autowired
+    private AttributeConnectorServiceImpl service;
+
+    @ApiOperation(value = "Creates or updates connector configuration for external resource attributes for the given "
+            + "zone.", tags = { "Attribute Connector Management" })
+    @ApiResponses(value = {
+            @ApiResponse(code = 201, message = "Connector configuration for the given zone is successfully created.") })
+    @RequestMapping(method = PUT, value = V1 + AcsApiUriTemplates.RESOURCE_CONNECTOR_URL,
+            consumes = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<String> putResourceConnector(
+            @ApiParam(value = "New or updated connector configuration for external resource attributes",
+                    required = true) @RequestBody final AttributeConnector connector) {
+        try {
+            boolean connectorCreated = this.service.upsertResourceConnector(connector);
+
+            if (connectorCreated) {
+                // return 201 with empty response body
+                return created(V1 + AcsApiUriTemplates.RESOURCE_CONNECTOR_URL, false);
+            }
+            // return 200 with empty response body
+            return ok();
+        } catch (AttributeConnectorException e) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage(), e);
+        }
+    }
+
+    @ApiOperation(value = "Retrieves connector configuration for external resource attributes for the given zone.",
+            tags = { "Attribute Connector Management" }, response = AttributeConnector.class)
+    @ApiResponses(
+            value = { @ApiResponse(code = 404, message = "Connector configuration for the given zone is not found.") })
+    @RequestMapping(method = GET, value = V1 + AcsApiUriTemplates.RESOURCE_CONNECTOR_URL)
+    public ResponseEntity<AttributeConnector> getResourceConnector() {
+        try {
+            AttributeConnector connector = this.service.retrieveResourceConnector();
+            if (connector != null) {
+                return ok(obfuscateAdapterSecret(connector));
+            }
+            return notFound();
+        } catch (AttributeConnectorException e) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY, e);
+        }
+    }
+
+    @ApiOperation(value = "Deletes connector configuration for external resource attributes for the given zone.",
+            tags = { "Attribute Connector Management" })
+    @ResponseStatus(value = HttpStatus.NO_CONTENT)
+    @ApiResponses(value = {
+            @ApiResponse(code = 204, message = "Connector configuration for the given zone is successfully deleted."),
+            @ApiResponse(code = 404, message = "Connector configuration for the given zone is not found.") })
+    @RequestMapping(method = DELETE, value = V1 + AcsApiUriTemplates.RESOURCE_CONNECTOR_URL)
+    public ResponseEntity<Void> deleteResourceConnector() {
+        try {
+            Boolean deleted = this.service.deleteResourceConnector();
+            if (deleted) {
+                return noContent();
+            }
+            return notFound();
+        } catch (AttributeConnectorException e) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage(), e);
+        }
+    }
+
+    @ApiOperation(value = "Creates or updates connector configuration for external subject attributes for the given "
+            + "zone.", tags = { "Attribute Connector Management" })
+    @ApiResponses(value = {
+            @ApiResponse(code = 201, message = "Connector configuration for the given zone is successfully created.") })
+    @RequestMapping(method = PUT, value = V1 + AcsApiUriTemplates.SUBJECT_CONNECTOR_URL,
+            consumes = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<String> putSubjectConnector(
+            @ApiParam(value = "New or updated connector configuration for external subject attributes",
+                    required = true) @RequestBody final AttributeConnector connector) {
+        try {
+            boolean connectorCreated = this.service.upsertSubjectConnector(connector);
+
+            if (connectorCreated) {
+                // return 201 with empty response body
+                return created(V1 + AcsApiUriTemplates.SUBJECT_CONNECTOR_URL, false);
+            }
+            // return 200 with empty response body
+            return ok();
+        } catch (AttributeConnectorException e) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage(), e);
+        }
+    }
+
+    @ApiOperation(value = "Retrieves connector configuration for external subject attributes for the given zone.",
+            tags = { "Attribute Connector Management" }, response = AttributeConnector.class)
+    @ApiResponses(
+            value = { @ApiResponse(code = 404, message = "Connector configuration for the given zone is not found.") })
+    @RequestMapping(method = GET, value = V1 + AcsApiUriTemplates.SUBJECT_CONNECTOR_URL)
+    public ResponseEntity<AttributeConnector> getSubjectConnector() {
+        try {
+            AttributeConnector connector = this.service.retrieveSubjectConnector();
+            if (connector != null) {
+                return ok(obfuscateAdapterSecret(connector));
+            }
+            return notFound();
+        } catch (AttributeConnectorException e) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY, e);
+        }
+    }
+
+    @ApiOperation(value = "Deletes connector configuration for external subject attributes for the given zone.",
+            tags = { "Attribute Connector Management" })
+    @ResponseStatus(value = HttpStatus.NO_CONTENT)
+    @ApiResponses(value = {
+            @ApiResponse(code = 204, message = "Connector configuration for the given zone is successfully deleted."),
+            @ApiResponse(code = 404, message = "Connector configuration for the given zone is not found.") })
+    @RequestMapping(method = DELETE, value = V1 + AcsApiUriTemplates.SUBJECT_CONNECTOR_URL)
+    public ResponseEntity<Void> deleteSubjectConnector() {
+        try {
+            Boolean deleted = this.service.deleteSubjectConnector();
+            if (deleted) {
+                return noContent();
+            }
+            return notFound();
+        } catch (AttributeConnectorException e) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage(), e);
+        }
+    }
+
+    private AttributeConnector obfuscateAdapterSecret(final AttributeConnector connector) {
+        connector.getAdapters().forEach(adapter -> adapter.setUaaClientSecret("**********"));
+        return connector;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/connector/management/AttributeConnectorException.java b/service/src/main/java/com/ge/predix/acs/attribute/connector/management/AttributeConnectorException.java
new file mode 100644
index 0000000..3b7c17c
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/connector/management/AttributeConnectorException.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.connector.management;
+
+public class AttributeConnectorException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    public AttributeConnectorException() {
+        super();
+    }
+
+    public AttributeConnectorException(final String message) {
+        super(message);
+    }
+
+    public AttributeConnectorException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/connector/management/AttributeConnectorService.java b/service/src/main/java/com/ge/predix/acs/attribute/connector/management/AttributeConnectorService.java
new file mode 100644
index 0000000..d2d362f
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/connector/management/AttributeConnectorService.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.connector.management;
+
+import com.ge.predix.acs.rest.AttributeConnector;
+
+public interface AttributeConnectorService {
+
+    boolean upsertResourceConnector(AttributeConnector connector);
+
+    AttributeConnector retrieveResourceConnector();
+
+    boolean deleteResourceConnector();
+
+    AttributeConnector getResourceAttributeConnector();
+
+    boolean isResourceAttributeConnectorConfigured();
+
+    boolean upsertSubjectConnector(AttributeConnector connector);
+
+    AttributeConnector retrieveSubjectConnector();
+
+    boolean deleteSubjectConnector();
+
+    AttributeConnector getSubjectAttributeConnector();
+
+    boolean isSubjectAttributeConnectorConfigured();
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/connector/management/AttributeConnectorServiceImpl.java b/service/src/main/java/com/ge/predix/acs/attribute/connector/management/AttributeConnectorServiceImpl.java
new file mode 100644
index 0000000..1d2a731
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/connector/management/AttributeConnectorServiceImpl.java
@@ -0,0 +1,275 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.connector.management;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Set;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.attribute.readers.AttributeReaderFactory;
+import com.ge.predix.acs.encryption.Encryptor;
+import com.ge.predix.acs.rest.AttributeAdapterConnection;
+import com.ge.predix.acs.rest.AttributeConnector;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.ge.predix.acs.zone.management.dao.ZoneRepository;
+import com.ge.predix.acs.zone.resolver.ZoneResolver;
+
+@Component
+public class AttributeConnectorServiceImpl implements AttributeConnectorService {
+
+    private static final String HTTPS = "https";
+
+    private static final int CACHED_INTERVAL_THRESHOLD_MINUTES = 30;
+
+    @Autowired
+    private ZoneRepository zoneRepository;
+    @Autowired
+    private ZoneResolver zoneResolver;
+    @Autowired
+    private AttributeReaderFactory attributeReaderFactory;
+
+    @Value("${ENCRYPTION_KEY}")
+    private String encryptionKey;
+
+    private Encryptor encryptor;
+
+    @PostConstruct
+    public void postConstruct() {
+        setEncryptionKey(this.encryptionKey);
+    }
+
+    void setEncryptionKey(final String encryptionKey) {
+        this.encryptor = new Encryptor();
+        this.encryptor.setEncryptionKey(encryptionKey);
+    }
+
+    @Override
+    public boolean upsertResourceConnector(final AttributeConnector connector) {
+        ZoneEntity zoneEntity = this.zoneResolver.getZoneEntityOrFail();
+        validateConnectorConfigOrFail(connector);
+
+        boolean isCreated = false;
+        try {
+            AttributeConnector existingConnector = zoneEntity.getResourceAttributeConnector();
+            isCreated = null == existingConnector;
+            connector.setAdapters(encryptAdapterClientSecrets(connector.getAdapters()));
+            zoneEntity.setResourceAttributeConnector(connector);
+            this.zoneRepository.save(zoneEntity);
+            if (!isCreated) {
+                this.attributeReaderFactory.removeResourceReader(zoneEntity.getName());
+            }
+        } catch (Exception e) {
+            String message = String.format(
+                    "Unable to persist connector configuration for resource attributes for zone '%s'",
+                    zoneEntity.getName());
+            throw new AttributeConnectorException(message, e);
+        }
+        return isCreated;
+    }
+
+    @Override
+    public AttributeConnector retrieveResourceConnector() {
+        ZoneEntity zoneEntity = this.zoneResolver.getZoneEntityOrFail();
+        try {
+            AttributeConnector connector = zoneEntity.getResourceAttributeConnector();
+            if (null != connector) {
+                // Deep copy the connector to prevent double-decryption of secrets
+                connector = AttributeConnector.newInstance(connector);
+                connector.setAdapters(decryptAdapterClientSecrets(connector.getAdapters()));
+            }
+            return connector;
+        } catch (Exception e) {
+            String message = String.format(
+                    "Unable to retrieve connector configuration for resource attributes for zone '%s'",
+                    zoneEntity.getName());
+            throw new AttributeConnectorException(message, e);
+        }
+    }
+
+    @Override
+    public boolean deleteResourceConnector() {
+        ZoneEntity zoneEntity = this.zoneResolver.getZoneEntityOrFail();
+        try {
+            if (null == zoneEntity.getResourceAttributeConnector()) {
+                return false;
+            }
+            zoneEntity.setResourceAttributeConnector(null);
+            this.zoneRepository.save(zoneEntity);
+            this.attributeReaderFactory.removeResourceReader(zoneEntity.getName());
+        } catch (Exception e) {
+            String message = String.format(
+                    "Unable to delete connector configuration for resource attributes for zone '%s'",
+                    zoneEntity.getName());
+            throw new AttributeConnectorException(message, e);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean upsertSubjectConnector(final AttributeConnector connector) {
+        ZoneEntity zoneEntity = this.zoneResolver.getZoneEntityOrFail();
+        validateConnectorConfigOrFail(connector);
+
+        boolean isCreated = false;
+        try {
+            AttributeConnector existingConnector = zoneEntity.getSubjectAttributeConnector();
+            isCreated = null == existingConnector;
+            connector.setAdapters(encryptAdapterClientSecrets(connector.getAdapters()));
+            zoneEntity.setSubjectAttributeConnector(connector);
+            this.zoneRepository.save(zoneEntity);
+            if (!isCreated) {
+                this.attributeReaderFactory.removeSubjectReader(zoneEntity.getName());
+            }
+        } catch (Exception e) {
+            String message = String.format(
+                    "Unable to persist connector configuration for subject attributes for zone '%s'",
+                    zoneEntity.getName());
+            throw new AttributeConnectorException(message, e);
+        }
+        return isCreated;
+    }
+
+    @Override
+    public AttributeConnector retrieveSubjectConnector() {
+        ZoneEntity zoneEntity = this.zoneResolver.getZoneEntityOrFail();
+        try {
+            AttributeConnector connector = zoneEntity.getSubjectAttributeConnector();
+            if (null != connector) {
+                // Deep copy the connector to prevent double-decryption of secrets
+                connector = AttributeConnector.newInstance(connector);
+                connector.setAdapters(decryptAdapterClientSecrets(connector.getAdapters()));
+            }
+            return connector;
+        } catch (Exception e) {
+            String message = String.format(
+                    "Unable to retrieve connector configuration for subject attributes for zone '%s'",
+                    zoneEntity.getName());
+            throw new AttributeConnectorException(message, e);
+        }
+    }
+
+    @Override
+    public boolean deleteSubjectConnector() {
+        ZoneEntity zoneEntity = this.zoneResolver.getZoneEntityOrFail();
+        try {
+            if (null == zoneEntity.getSubjectAttributeConnector()) {
+                return false;
+            }
+            zoneEntity.setSubjectAttributeConnector(null);
+            this.zoneRepository.save(zoneEntity);
+            this.attributeReaderFactory.removeSubjectReader(zoneEntity.getName());
+        } catch (Exception e) {
+            String message = String.format(
+                    "Unable to delete connector configuration for subject attributes for zone '%s'",
+                    zoneEntity.getName());
+            throw new AttributeConnectorException(message, e);
+        }
+        return true;
+    }
+
+    private void validateAdapterEntityOrFail(final AttributeAdapterConnection adapter) {
+        if (adapter == null) {
+            throw new AttributeConnectorException("Attribute connector configuration requires at least one adapter");
+        }
+        try {
+            if (!new URL(adapter.getAdapterEndpoint()).getProtocol().equalsIgnoreCase(HTTPS)) {
+                throw new AttributeConnectorException("Attribute adapter endpoint must use the HTTPS protocol");
+            }
+        } catch (MalformedURLException e) {
+            throw new AttributeConnectorException(
+                    "Attribute adapter endpoint either has no protocol or is not a valid URL", e);
+        }
+        try {
+            if (!new URL(adapter.getUaaTokenUrl()).getProtocol().equalsIgnoreCase(HTTPS)) {
+                throw new AttributeConnectorException("Attribute adapter UAA token URL must use the HTTPS protocol");
+            }
+        } catch (MalformedURLException e) {
+            throw new AttributeConnectorException(
+                    "Attribute adapter UAA token URL either has no protocol or is not a valid URL", e);
+        }
+        if (adapter.getUaaClientId() == null || adapter.getUaaClientId().isEmpty()) {
+            throw new AttributeConnectorException("Attribute adapter configuration requires a nonempty client ID");
+        }
+        if (adapter.getUaaClientSecret() == null || adapter.getUaaClientSecret().isEmpty()) {
+            throw new AttributeConnectorException("Attribute adapter configuration requires a nonempty client secret");
+        }
+    }
+
+    private void validateConnectorConfigOrFail(final AttributeConnector connector) {
+        if (connector == null) {
+            throw new AttributeConnectorException("Attribute connector configuration requires a valid connector");
+        }
+        if (connector.getAdapters() == null || connector.getAdapters().isEmpty()
+                || connector.getAdapters().size() > 1) {
+            throw new AttributeConnectorException("Attribute connector configuration requires one adapter");
+        }
+        if (connector.getMaxCachedIntervalMinutes() < CACHED_INTERVAL_THRESHOLD_MINUTES) {
+            throw new AttributeConnectorException(
+                    "Minimum value for maxCachedIntervalMinutes is " + CACHED_INTERVAL_THRESHOLD_MINUTES);
+        }
+        connector.getAdapters().parallelStream().forEach(this::validateAdapterEntityOrFail);
+    }
+
+    private Set<AttributeAdapterConnection> encryptAdapterClientSecrets(
+            final Set<AttributeAdapterConnection> adapters) {
+        if (CollectionUtils.isEmpty(adapters)) {
+            return Collections.emptySet();
+        }
+        adapters.forEach(adapter -> adapter.setUaaClientSecret(this.encryptor.encrypt(adapter.getUaaClientSecret())));
+        return adapters;
+    }
+
+    private Set<AttributeAdapterConnection> decryptAdapterClientSecrets(
+            final Set<AttributeAdapterConnection> adapters) {
+        if (CollectionUtils.isEmpty(adapters)) {
+            return Collections.emptySet();
+        }
+        adapters.forEach(adapter -> adapter.setUaaClientSecret(this.encryptor.decrypt(adapter.getUaaClientSecret())));
+        return adapters;
+    }
+
+    @Override
+    public AttributeConnector getResourceAttributeConnector() {
+        return retrieveResourceConnector();
+    }
+
+    @Override
+    public AttributeConnector getSubjectAttributeConnector() {
+        return retrieveSubjectConnector();
+    }
+
+    @Override
+    public boolean isResourceAttributeConnectorConfigured() {
+        return this.getResourceAttributeConnector() != null && this.getResourceAttributeConnector().getIsActive();
+    }
+
+    @Override
+    public boolean isSubjectAttributeConnectorConfigured() {
+        return this.getSubjectAttributeConnector() != null && this.getSubjectAttributeConnector().getIsActive();
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/readers/AttributeReader.java b/service/src/main/java/com/ge/predix/acs/attribute/readers/AttributeReader.java
new file mode 100644
index 0000000..9cf9665
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/readers/AttributeReader.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+import java.util.Set;
+
+import com.ge.predix.acs.model.Attribute;
+
+@FunctionalInterface
+public interface AttributeReader {
+    Set<Attribute> getAttributes(String identifier);
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/readers/AttributeReaderFactory.java b/service/src/main/java/com/ge/predix/acs/attribute/readers/AttributeReaderFactory.java
new file mode 100644
index 0000000..8e0b4a3
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/readers/AttributeReaderFactory.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ConcurrentReferenceHashMap;
+
+import com.ge.predix.acs.attribute.cache.AttributeCacheFactory;
+import com.ge.predix.acs.attribute.connector.management.AttributeConnectorService;
+import com.ge.predix.acs.zone.resolver.SpringSecurityZoneResolver;
+
+// CHECKSTYLE:OFF: FinalClass
+@Component
+public class AttributeReaderFactory {
+
+    @Value("${ADAPTER_TIMEOUT_MILLIS:3000}")
+    private int adapterTimeoutMillis;
+
+    @Autowired
+    private PrivilegeServiceResourceAttributeReader privilegeServiceResourceAttributeReader;
+
+    @Autowired
+    private PrivilegeServiceSubjectAttributeReader privilegeServiceSubjectAttributeReader;
+
+    @Autowired
+    private AttributeCacheFactory attributeCacheFactory;
+
+    private AttributeConnectorService connectorService;
+    private RedisTemplate<String, String> resourceCacheRedisTemplate;
+    private RedisTemplate<String, String> subjectCacheRedisTemplate;
+
+    // Caches that use the multiton design pattern (keyed off the zone name)
+    private final Map<String, ExternalResourceAttributeReader> externalResourceAttributeReaderCache = new
+            ConcurrentReferenceHashMap<>();
+    private final Map<String, ExternalSubjectAttributeReader> externalSubjectAttributeReaderCache = new
+            ConcurrentReferenceHashMap<>();
+
+    public ResourceAttributeReader getResourceAttributeReader() {
+        if (!this.connectorService.isResourceAttributeConnectorConfigured()) {
+            return this.privilegeServiceResourceAttributeReader;
+        }
+
+        String zoneName = SpringSecurityZoneResolver.getZoneName();
+        ExternalResourceAttributeReader externalResourceAttributeReader = this.externalResourceAttributeReaderCache
+                .get(zoneName);
+        if (externalResourceAttributeReader != null) {
+            return externalResourceAttributeReader;
+        }
+
+        externalResourceAttributeReader = new ExternalResourceAttributeReader(this.connectorService,
+                this.attributeCacheFactory.createResourceAttributeCache(
+                        this.connectorService.getResourceAttributeConnector().getMaxCachedIntervalMinutes(), zoneName,
+                        this.resourceCacheRedisTemplate), adapterTimeoutMillis);
+        this.externalResourceAttributeReaderCache.put(zoneName, externalResourceAttributeReader);
+        return externalResourceAttributeReader;
+    }
+
+    public SubjectAttributeReader getSubjectAttributeReader() {
+        if (!connectorService.isSubjectAttributeConnectorConfigured()) {
+            return this.privilegeServiceSubjectAttributeReader;
+        }
+
+        String zoneName = SpringSecurityZoneResolver.getZoneName();
+        ExternalSubjectAttributeReader externalSubjectAttributeReader = this.externalSubjectAttributeReaderCache
+                .get(zoneName);
+        if (externalSubjectAttributeReader != null) {
+            return externalSubjectAttributeReader;
+        }
+
+        externalSubjectAttributeReader = new ExternalSubjectAttributeReader(connectorService, this.attributeCacheFactory
+                .createSubjectAttributeCache(
+                        this.connectorService.getSubjectAttributeConnector().getMaxCachedIntervalMinutes(), zoneName,
+                        this.subjectCacheRedisTemplate), adapterTimeoutMillis);
+        this.externalSubjectAttributeReaderCache.put(zoneName, externalSubjectAttributeReader);
+        return externalSubjectAttributeReader;
+    }
+
+    public void removeResourceReader(final String zoneName) {
+        this.externalResourceAttributeReaderCache.remove(zoneName);
+    }
+
+    public void removeSubjectReader(final String zoneName) {
+        this.externalSubjectAttributeReaderCache.remove(zoneName);
+    }
+
+    @Autowired(required = false)
+    public void setResourceCacheRedisTemplate(final RedisTemplate<String, String> resourceCacheRedisTemplate) {
+        this.resourceCacheRedisTemplate = resourceCacheRedisTemplate;
+    }
+
+    @Autowired(required = false)
+    public void setSubjectCacheRedisTemplate(final RedisTemplate<String, String> subjectCacheRedisTemplate) {
+        this.subjectCacheRedisTemplate = subjectCacheRedisTemplate;
+    }
+
+    @Autowired
+    public void setConnectorService(final AttributeConnectorService connectorService) {
+        this.connectorService = connectorService;
+    }
+}
+// CHECKSTYLE:ON: FinalClass
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/readers/AttributeRetrievalException.java b/service/src/main/java/com/ge/predix/acs/attribute/readers/AttributeRetrievalException.java
new file mode 100644
index 0000000..a2f3997
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/readers/AttributeRetrievalException.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+import java.text.MessageFormat;
+
+public class AttributeRetrievalException extends RuntimeException {
+
+    public AttributeRetrievalException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    public AttributeRetrievalException(final String message) {
+        super(message);
+    }
+
+    static String getAdapterErrorMessage(final String adapterEndpoint) {
+        return MessageFormat.format("Couldn''t get attributes from the adapter with endpoint ''{0}''", adapterEndpoint);
+    }
+
+    static String getStorageErrorMessage(final String id) {
+        return String.format("Total size of attributes or number of attributes too large for id: '%s'", id);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/readers/CachedAttributes.java b/service/src/main/java/com/ge/predix/acs/attribute/readers/CachedAttributes.java
new file mode 100644
index 0000000..fa0afd1
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/readers/CachedAttributes.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+import java.util.Set;
+
+import com.ge.predix.acs.model.Attribute;
+
+public class CachedAttributes {
+
+    private State state;
+    private Set<Attribute> attributes;
+
+    public CachedAttributes() {
+        // Needed for jackson serialization
+    }
+
+    public CachedAttributes(final Set<Attribute> attributes) {
+        this.attributes = attributes;
+        this.state = State.SUCCESS;
+    }
+
+    public CachedAttributes(final State state) {
+        this.state = state;
+    }
+
+    public State getState() {
+        return this.state;
+    }
+
+    public void setState(final State state) {
+        this.state = state;
+    }
+
+    public Set<Attribute> getAttributes() {
+        return attributes;
+    }
+
+    public void setAttributes(final Set<Attribute> attributes) {
+        this.attributes = attributes;
+    }
+
+    public enum State {
+
+        SUCCESS,
+        DO_NOT_RETRY
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/readers/ExternalAttributeReader.java b/service/src/main/java/com/ge/predix/acs/attribute/readers/ExternalAttributeReader.java
new file mode 100644
index 0000000..bf28838
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/readers/ExternalAttributeReader.java
@@ -0,0 +1,200 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
+import org.springframework.util.ConcurrentReferenceHashMap;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import com.ge.predix.acs.attribute.cache.AttributeCache;
+import com.ge.predix.acs.attribute.connector.management.AttributeConnectorService;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.AttributeAdapterConnection;
+import com.ge.predix.acs.rest.attribute.adapter.AttributesResponse;
+
+public abstract class ExternalAttributeReader implements AttributeReader {
+
+    @Value("${MAX_NUMBER_OF_ATTRIBUTES:1500}")
+    private int maxNumberOfAttributes = 1500;
+
+    @Value("${MAX_SIZE_OF_ATTRIBUTES_IN_BYTES:500000}")
+    private int maxSizeOfAttributesInBytes = 500000;
+
+    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ExternalAttributeReader.class);
+    private static final String ID = "id";
+
+    private final int adapterTimeoutMillis;
+    private final AttributeConnectorService connectorService;
+    private final AttributeCache attributeCache;
+    private final Map<AttributeAdapterConnection, OAuth2RestTemplate> adapterRestTemplateCache = new
+            ConcurrentReferenceHashMap<>();
+
+    public ExternalAttributeReader(final AttributeConnectorService connectorService,
+            final AttributeCache attributeCache, final int adapterTimeoutMillis) {
+        this.connectorService = connectorService;
+        this.attributeCache = attributeCache;
+        this.adapterTimeoutMillis = adapterTimeoutMillis;
+    }
+
+    AttributeConnectorService getConnectorService() {
+        return this.connectorService;
+    }
+
+    /**
+     * Tries to get the attributes for the identifier in the cache. If the attributes are not in the cache, uses the
+     * configured adapters for the zone to retrieve the attributes.
+     *
+     * @param identifier The identifier of the subject or resource to retrieve attributes for.
+     * @return The set of attributes corresponding to the attributes id passed as a parameter.
+     * @throws AttributeRetrievalException Throw this exception if the Attributes returned from the adapter are too
+     *                                     large or there was a connection problem to the adapter.
+     */
+    @Override
+    public Set<Attribute> getAttributes(final String identifier) {
+        CachedAttributes cachedAttributes = this.attributeCache.getAttributes(identifier);
+        if (null == cachedAttributes) {
+            LOGGER.trace("Attributes not found in cache");
+            // If get returns null then key either doesn't exist in cache or has been evicted.
+            // Circuit breaker story to check adapter connection to be done soon.
+            cachedAttributes = getAttributesFromAdapters(identifier);
+            this.attributeCache.setAttributes(identifier, cachedAttributes);
+        }
+        if (cachedAttributes.getState().equals(CachedAttributes.State.DO_NOT_RETRY)) {
+            // If get returns CachedAttributes with DO_NOT_RETRY throw the storage exception.
+            throw new AttributeRetrievalException(AttributeRetrievalException.getStorageErrorMessage(identifier));
+        }
+        return cachedAttributes.getAttributes();
+    }
+
+    private void setRequestFactory(final OAuth2RestTemplate restTemplate) {
+        CloseableHttpClient httpClient = HttpClientBuilder.create().useSystemProperties().build();
+        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
+        requestFactory.setReadTimeout(this.adapterTimeoutMillis);
+        requestFactory.setConnectTimeout(this.adapterTimeoutMillis);
+        requestFactory.setConnectionRequestTimeout(this.adapterTimeoutMillis);
+        restTemplate.setRequestFactory(requestFactory);
+    }
+
+    OAuth2RestTemplate getAdapterOauth2RestTemplate(final AttributeAdapterConnection adapterConnection) {
+        String uaaTokenUrl = adapterConnection.getUaaTokenUrl();
+        String uaaClientId = adapterConnection.getUaaClientId();
+        String uaaClientSecret = adapterConnection.getUaaClientSecret();
+
+        OAuth2RestTemplate oAuth2RestTemplate = this.adapterRestTemplateCache.get(adapterConnection);
+        if (oAuth2RestTemplate != null) {
+            return oAuth2RestTemplate;
+        }
+
+        ClientCredentialsResourceDetails clientCredentials = new ClientCredentialsResourceDetails();
+        clientCredentials.setAccessTokenUri(uaaTokenUrl);
+        clientCredentials.setClientId(uaaClientId);
+        clientCredentials.setClientSecret(uaaClientSecret);
+        oAuth2RestTemplate = new OAuth2RestTemplate(clientCredentials);
+        this.setRequestFactory(oAuth2RestTemplate);
+        this.adapterRestTemplateCache.put(adapterConnection, oAuth2RestTemplate);
+        return oAuth2RestTemplate;
+    }
+
+    CachedAttributes getAttributesFromAdapters(final String identifier) {
+
+        CachedAttributes cachedAttributes = new CachedAttributes(Collections.emptySet());
+
+        Set<AttributeAdapterConnection> adapterConnections = this.getAttributeAdapterConnections();
+        AttributeAdapterConnection adapterConnection = matchAdapterConnection(adapterConnections, identifier);
+
+        if (null != adapterConnection) {
+
+            String adapterUrl = UriComponentsBuilder.fromUriString(adapterConnection.getAdapterEndpoint())
+                    .queryParam(ID, identifier).toUriString();
+
+            AttributesResponse attributesResponse;
+
+            try {
+                attributesResponse = this.getAdapterOauth2RestTemplate(adapterConnection).
+                        getForEntity(adapterUrl, AttributesResponse.class).getBody();
+            } catch (final Exception e) {
+                LOGGER.debug(AttributeRetrievalException.getAdapterErrorMessage(adapterUrl), e);
+                throw new AttributeRetrievalException(AttributeRetrievalException.getAdapterErrorMessage(identifier));
+            }
+
+            if (isSizeLimitsExceeded(attributesResponse.getAttributes())) {
+                LOGGER.debug(AttributeRetrievalException.getStorageErrorMessage(identifier));
+                cachedAttributes.setState(CachedAttributes.State.DO_NOT_RETRY);
+                return cachedAttributes;
+            }
+            cachedAttributes.setAttributes(attributesResponse.getAttributes());
+        }
+
+        return cachedAttributes;
+    }
+
+    //Matching mechanism of a resource/subject id to a adapter is yet to be defined. Current implementation only
+    //supports exactly one adapterConnection per connector.
+    private AttributeAdapterConnection matchAdapterConnection(final Set<AttributeAdapterConnection> adapterConnections,
+            final String identifier) {
+        if (1 == adapterConnections.size()) {
+            return adapterConnections.iterator().next();
+        } else {
+            throw new IllegalStateException("Connector must have exactly one adapterConnection.");
+        }
+    }
+
+    private boolean isSizeLimitsExceeded(final Set<Attribute> attributes) {
+        if (attributes.size() > this.maxNumberOfAttributes) {
+            return true;
+        }
+        long size = 0;
+        for (Attribute attribute : attributes) {
+            size += size(attribute);
+            if (size > this.maxSizeOfAttributesInBytes) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private int size(final Attribute attribute) {
+        int size = 0;
+        String issuer = attribute.getIssuer();
+        String name = attribute.getName();
+        String value = attribute.getValue();
+        if (null != issuer) {
+            size += issuer.length();
+        }
+        if (null != name) {
+            size += name.length();
+        }
+        if (null != value) {
+            size += value.length();
+        }
+        return size;
+    }
+
+    abstract Set<AttributeAdapterConnection> getAttributeAdapterConnections();
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/readers/ExternalResourceAttributeReader.java b/service/src/main/java/com/ge/predix/acs/attribute/readers/ExternalResourceAttributeReader.java
new file mode 100644
index 0000000..5339f0f
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/readers/ExternalResourceAttributeReader.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+import java.util.Set;
+
+import com.ge.predix.acs.attribute.cache.AttributeCache;
+import com.ge.predix.acs.attribute.connector.management.AttributeConnectorService;
+import com.ge.predix.acs.rest.AttributeAdapterConnection;
+
+public class ExternalResourceAttributeReader extends ExternalAttributeReader implements ResourceAttributeReader {
+
+    public ExternalResourceAttributeReader(final AttributeConnectorService connectorService,
+            final AttributeCache resourceAttributeCache, final int adapterTimeoutMillis) {
+        super(connectorService, resourceAttributeCache, adapterTimeoutMillis);
+    }
+
+    @Override
+    Set<AttributeAdapterConnection> getAttributeAdapterConnections() {
+        return this.getConnectorService().getResourceAttributeConnector().getAdapters();
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/readers/ExternalSubjectAttributeReader.java b/service/src/main/java/com/ge/predix/acs/attribute/readers/ExternalSubjectAttributeReader.java
new file mode 100644
index 0000000..0375c2d
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/readers/ExternalSubjectAttributeReader.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+import java.util.Set;
+
+import com.ge.predix.acs.attribute.cache.AttributeCache;
+import com.ge.predix.acs.attribute.connector.management.AttributeConnectorService;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.AttributeAdapterConnection;
+
+public class ExternalSubjectAttributeReader extends ExternalAttributeReader implements SubjectAttributeReader {
+
+    public ExternalSubjectAttributeReader(final AttributeConnectorService connectorService,
+            final AttributeCache subjectAttributeCache, final int adapterTimeoutMillis) {
+        super(connectorService, subjectAttributeCache, adapterTimeoutMillis);
+    }
+
+    @Override
+    Set<AttributeAdapterConnection> getAttributeAdapterConnections() {
+        return this.getConnectorService().getSubjectAttributeConnector().getAdapters();
+    }
+
+    @Override
+    public Set<Attribute> getAttributesByScope(final String subjectId, final Set<Attribute> scopes) {
+        // Connectors have no notion of scoped attributes
+        return this.getAttributes(subjectId);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/readers/PrivilegeServiceResourceAttributeReader.java b/service/src/main/java/com/ge/predix/acs/attribute/readers/PrivilegeServiceResourceAttributeReader.java
new file mode 100644
index 0000000..78c2e98
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/readers/PrivilegeServiceResourceAttributeReader.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.privilege.management.PrivilegeManagementService;
+import com.ge.predix.acs.rest.BaseResource;
+
+@Component
+public class PrivilegeServiceResourceAttributeReader implements ResourceAttributeReader {
+    @Autowired
+    private PrivilegeManagementService privilegeManagementService;
+
+    @Override
+    public Set<Attribute> getAttributes(final String identifier) {
+        Set<Attribute> resourceAttributes = Collections.emptySet();
+        BaseResource resource =
+            this.privilegeManagementService.getByResourceIdentifierWithInheritedAttributes(identifier);
+        if (null != resource) {
+            resourceAttributes = Collections.unmodifiableSet(new HashSet<>(resource.getAttributes()));
+        }
+        return resourceAttributes;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/readers/PrivilegeServiceSubjectAttributeReader.java b/service/src/main/java/com/ge/predix/acs/attribute/readers/PrivilegeServiceSubjectAttributeReader.java
new file mode 100644
index 0000000..dbc6631
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/readers/PrivilegeServiceSubjectAttributeReader.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.privilege.management.PrivilegeManagementService;
+import com.ge.predix.acs.rest.BaseSubject;
+
+@Component
+public class PrivilegeServiceSubjectAttributeReader implements SubjectAttributeReader {
+    @Autowired
+    private PrivilegeManagementService privilegeManagementService;
+
+    @Override
+    public Set<Attribute> getAttributes(final String identifier) {
+        return this.getAttributesByScope(identifier, Collections.emptySet());
+    }
+
+    @Override
+    public Set<Attribute> getAttributesByScope(final String identifier, final Set<Attribute> scopes) {
+        Set<Attribute> subjectAttributes = Collections.emptySet();
+        BaseSubject subject = this.privilegeManagementService.getBySubjectIdentifierAndScopes(identifier, scopes);
+        if (null != subject) {
+            subjectAttributes = Collections.unmodifiableSet(new HashSet<>(subject.getAttributes()));
+        }
+        return subjectAttributes;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/readers/ResourceAttributeReader.java b/service/src/main/java/com/ge/predix/acs/attribute/readers/ResourceAttributeReader.java
new file mode 100644
index 0000000..be5ee05
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/readers/ResourceAttributeReader.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+//tagging interface to create explicit type for ResourceAttributeReader
+public interface ResourceAttributeReader extends AttributeReader {
+}
diff --git a/service/src/main/java/com/ge/predix/acs/attribute/readers/SubjectAttributeReader.java b/service/src/main/java/com/ge/predix/acs/attribute/readers/SubjectAttributeReader.java
new file mode 100644
index 0000000..1db5f61
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/attribute/readers/SubjectAttributeReader.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+import java.util.Set;
+
+import com.ge.predix.acs.model.Attribute;
+
+public interface SubjectAttributeReader extends AttributeReader {
+    Set<Attribute> getAttributesByScope(String identifier, Set<Attribute> scopes);
+}
diff --git a/service/src/main/java/com/ge/predix/acs/config/AcsConfigUtil.java b/service/src/main/java/com/ge/predix/acs/config/AcsConfigUtil.java
new file mode 100644
index 0000000..306c04b
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/config/AcsConfigUtil.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.config;
+
+import javax.persistence.EntityManagerFactory;
+import javax.sql.DataSource;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.JpaVendorAdapter;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter;
+import org.springframework.transaction.PlatformTransactionManager;
+
+/**
+ * Utility class to use OpenJpa instead of Hibernate.
+ *
+ * @author acs-engineers@ge.com
+ */
+public class AcsConfigUtil {
+    @Bean
+    public LocalContainerEntityManagerFactoryBean entityManagerFactory(final DataSource dataSource) {
+        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
+        em.setDataSource(dataSource);
+        em.setPackagesToScan(new String[] { "com.ge.predix.acs.service.policy.admin.dao",
+                "com.ge.predix.acs.privilege.management.dao",
+                "com.ge.predix.acs.zone.management.dao",
+                "com.ge.predix.acs.attribute.connector.management.dao"
+        });
+
+        JpaVendorAdapter vendorAdapter = new OpenJpaVendorAdapter();
+        em.setJpaVendorAdapter(vendorAdapter);
+        return em;
+    }
+
+    @Bean
+    public PlatformTransactionManager transactionManager(final EntityManagerFactory emf) {
+        JpaTransactionManager transactionManager = new JpaTransactionManager();
+        transactionManager.setEntityManagerFactory(emf);
+        return transactionManager;
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/config/CommonRedisConfig.java b/service/src/main/java/com/ge/predix/acs/config/CommonRedisConfig.java
new file mode 100644
index 0000000..d85f555
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/config/CommonRedisConfig.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.config;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+@Configuration
+@Profile({ "cloud-redis", "redis" })
+public class CommonRedisConfig {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CommonRedisConfig.class);
+
+    private RedisConnectionFactory decisionRedisConnectionFactory;
+    private RedisConnectionFactory resourceRedisConnectionFactory;
+    private RedisConnectionFactory subjectRedisConnectionFactory;
+
+    @Autowired
+    public CommonRedisConfig(final RedisConnectionFactory decisionRedisConnectionFactory,
+            final RedisConnectionFactory resourceRedisConnectionFactory,
+            final RedisConnectionFactory subjectRedisConnectionFactory) {
+        this.decisionRedisConnectionFactory = decisionRedisConnectionFactory;
+        this.resourceRedisConnectionFactory = resourceRedisConnectionFactory;
+        this.subjectRedisConnectionFactory = subjectRedisConnectionFactory;
+    }
+
+    @Bean(name = { "redisTemplate", "decisionCacheRedisTemplate" })
+    public RedisTemplate<String, String> decisionCacheRedisTemplate() {
+        return createCacheRedisTemplate(this.decisionRedisConnectionFactory, "Decision");
+
+    }
+
+    @Bean(name = { "resourceCacheRedisTemplate" })
+    public RedisTemplate<String, String> resourceCacheRedisTemplate() {
+        return createCacheRedisTemplate(this.resourceRedisConnectionFactory, "Resource");
+
+    }
+
+    @Bean(name = { "subjectCacheRedisTemplate" })
+    public RedisTemplate<String, String> subjectCacheRedisTemplate() {
+        return createCacheRedisTemplate(this.subjectRedisConnectionFactory, "Subject");
+    }
+
+    private RedisTemplate<String, String> createCacheRedisTemplate(final RedisConnectionFactory redisConnectionFactory,
+            final String redisTemplateType) {
+        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setConnectionFactory(redisConnectionFactory);
+        redisTemplate.setDefaultSerializer(new StringRedisSerializer());
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setValueSerializer(new StringRedisSerializer());
+        LOGGER.info("Successfully created {} Redis template.", redisTemplateType);
+        return redisTemplate;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/config/EnvDataSourceConfig.java b/service/src/main/java/com/ge/predix/acs/config/EnvDataSourceConfig.java
new file mode 100644
index 0000000..0d638d6
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/config/EnvDataSourceConfig.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.config;
+
+import javax.persistence.EntityManagerFactory;
+import javax.sql.DataSource;
+
+import org.apache.tomcat.jdbc.pool.PoolProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.transaction.PlatformTransactionManager;
+
+/**
+ * DataSourceConfig used for connecting directly to a postgres database.
+ */
+@Configuration
+@Profile({ "envDbConfig" })
+@EnableJpaRepositories({ "com.ge.predix.acs.service.policy.admin.dao",
+    "com.ge.predix.acs.privilege.management.dao",
+    "com.ge.predix.acs.zone.management.dao",
+    "com.ge.predix.acs.attribute.connector.management.dao" })
+public class EnvDataSourceConfig {
+    private static final Logger LOGGER = LoggerFactory.getLogger(EnvDataSourceConfig.class);
+
+    private final AcsConfigUtil acsConfigUtil = new AcsConfigUtil();
+
+    @Value("${DB_DRIVER_CLASS_NAME:org.postgresql.Driver}")
+    private String driverClassName;
+    @Value("${DB_URL:jdbc:postgresql:acs}")
+    private String url;
+    @Value("${DB_USERNAME:postgres}")
+    private String username;
+    @Value("${DB_PASSWORD:}")
+    private String password;
+    @Value("${MIN_ACTIVE:0}")
+    private int minActive;
+    @Value("${MAX_ACTIVE:100}")
+    private int maxActive;
+    @Value("${MAX_WAIT_TIME:30000}")
+    private int maxWaitTime;
+
+    @Bean
+    public DataSource dataSource() {
+        LOGGER.info("Starting ACS with the database connection: '{}'.", this.url); //$NON-NLS-1$
+        PoolProperties poolProperties = new PoolProperties();
+        poolProperties.setDriverClassName(this.driverClassName);
+        poolProperties.setUrl(this.url);
+        poolProperties.setUsername(this.username);
+        poolProperties.setPassword(this.password);
+        poolProperties.setMaxActive(this.maxActive);
+        poolProperties.setMinIdle(this.minActive);
+        poolProperties.setMaxWait(this.maxWaitTime);
+        return new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);
+    }
+
+    @Bean
+    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
+        return this.acsConfigUtil.entityManagerFactory(this.dataSource());
+    }
+
+    @Bean
+    public PlatformTransactionManager transactionManager(final EntityManagerFactory emf) {
+        return this.acsConfigUtil.transactionManager(emf);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/config/GraphBeanDefinitionRegistryPostProcessor.java b/service/src/main/java/com/ge/predix/acs/config/GraphBeanDefinitionRegistryPostProcessor.java
new file mode 100644
index 0000000..297ac97
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/config/GraphBeanDefinitionRegistryPostProcessor.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.config;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.privilege.management.dao.GraphResourceRepository;
+import com.ge.predix.acs.privilege.management.dao.GraphSubjectRepository;
+
+@Profile("titan")
+@Component
+public class GraphBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
+
+    @Override
+    public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) {
+        // Do nothing.
+    }
+
+    @Override
+    public void postProcessBeanDefinitionRegistry(final BeanDefinitionRegistry registry) {
+        BeanDefinition resourceRepositoryBeanDefinition = new RootBeanDefinition(GraphResourceRepository.class);
+        registry.registerBeanDefinition("resourceHierarchicalRepository", resourceRepositoryBeanDefinition);
+
+        BeanDefinition subjectRepositoryBeanDefinition = new RootBeanDefinition(GraphSubjectRepository.class);
+        registry.registerBeanDefinition("subjectHierarchicalRepository", subjectRepositoryBeanDefinition);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/config/GraphConfig.java b/service/src/main/java/com/ge/predix/acs/config/GraphConfig.java
new file mode 100644
index 0000000..9b92105
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/config/GraphConfig.java
@@ -0,0 +1,237 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.config;
+
+import static com.ge.predix.acs.privilege.management.dao.GraphGenericRepository.PARENT_EDGE_LABEL;
+import static com.ge.predix.acs.privilege.management.dao.GraphGenericRepository.SCOPE_PROPERTY_KEY;
+import static com.ge.predix.acs.privilege.management.dao.GraphGenericRepository.ZONE_ID_KEY;
+import static com.ge.predix.acs.privilege.management.dao.GraphResourceRepository.RESOURCE_ID_KEY;
+import static com.ge.predix.acs.privilege.management.dao.GraphSubjectRepository.SUBJECT_ID_KEY;
+
+import java.util.Arrays;
+import java.util.concurrent.ExecutionException;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.structure.Direction;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.util.Assert;
+
+import com.ge.predix.acs.privilege.management.dao.GraphGenericRepository;
+import com.ge.predix.acs.privilege.management.dao.GraphResourceRepository;
+import com.ge.predix.acs.privilege.management.dao.GraphSubjectRepository;
+import com.thinkaurelius.titan.core.EdgeLabel;
+import com.thinkaurelius.titan.core.PropertyKey;
+import com.thinkaurelius.titan.core.TitanFactory;
+import com.thinkaurelius.titan.core.TitanFactory.Builder;
+import com.thinkaurelius.titan.core.TitanGraph;
+import com.thinkaurelius.titan.core.VertexLabel;
+import com.thinkaurelius.titan.core.schema.SchemaAction;
+import com.thinkaurelius.titan.core.schema.SchemaStatus;
+import com.thinkaurelius.titan.core.schema.TitanManagement;
+import com.thinkaurelius.titan.core.schema.TitanManagement.IndexBuilder;
+import com.thinkaurelius.titan.graphdb.database.management.ManagementSystem;
+
+@Configuration
+@Profile({ "titan" })
+public class GraphConfig {
+    private static final Logger LOGGER = LoggerFactory.getLogger(GraphConfig.class);
+
+    public static final String BY_SCOPE_INDEX_NAME = "byScopeIndex";
+    public static final String BY_SUBJECT_UNIQUE_INDEX_NAME = "bySubjectUnique";
+    public static final String BY_RESOURCE_UNIQUE_INDEX_NAME = "byResourceUnique";
+    public static final String BY_ZONE_INDEX_NAME = "byZone";
+    public static final String BY_VERSION_UNIQUE_INDEX_NAME = "byVersion";
+    public static final String BY_ZONE_AND_RESOURCE_UNIQUE_INDEX_NAME = "byZoneAndResourceUnique";
+    public static final String BY_ZONE_AND_SUBJECT_UNIQUE_INDEX_NAME = "byZoneAndSubjectUnique";
+
+    @Value("${TITAN_ENABLE_CASSANDRA:false}")
+    private boolean cassandraEnabled;
+    @Value("${TITAN_STORAGE_CASSANDRA_KEYSPACE:titan}")
+    private String cassandraKeyspace;
+    @Value("${TITAN_STORAGE_HOSTNAME:localhost}")
+    private String hostname;
+    @Value("${TITAN_STORAGE_USERNAME:}")
+    private String username;
+    @Value("${TITAN_STORAGE_PASSWORD:}")
+    private String password;
+    @Value("${TITAN_STORAGE_PORT:9160}")
+    private int port;
+    @Value("${TITAN_ENABLE_CACHE:true}")
+    private boolean cacheEnabled;
+    @Value("${TITAN_CACHE_CLEAN_WAIT:50}")
+    private int titanCacheCleanWait;
+    @Value("${TITAN_CACHE_TIME:1000}")
+    private int titanCacheTime;
+    @Value("${TITAN_CACHE_SIZE:0.25}")
+    private double titanCacheSize;
+
+    private Graph graph;
+
+    @PostConstruct
+    public void init() throws InterruptedException {
+        this.graph = createGraph();
+    }
+
+    public Graph createGraph() throws InterruptedException {
+        Graph newGraph;
+        if (this.cassandraEnabled) {
+            Builder titanBuilder = TitanFactory.build().set("storage.backend", "cassandra")
+                    .set("storage.cassandra.keyspace", this.cassandraKeyspace)
+                    .set("storage.hostname", Arrays.asList(hostname.split(","))).set("storage.port", this.port)
+                    .set("cache.db-cache", this.cacheEnabled).set("cache.db-cache-clean-wait", this.titanCacheCleanWait)
+                    .set("cache.db-cache-time", this.titanCacheTime).set("cache.db-cache-size", this.titanCacheSize);
+            if (StringUtils.isNotEmpty(this.username)) {
+                titanBuilder = titanBuilder.set("storage.username", this.username);
+            }
+            if (StringUtils.isNotEmpty(this.password)) {
+                titanBuilder = titanBuilder.set("storage.password", this.password);
+            }
+            newGraph = titanBuilder.open();
+        } else {
+            newGraph = TitanFactory.build().set("storage.backend", "inmemory").open();
+        }
+        createSchemaElements(newGraph);
+        LOGGER.info("Initialized titan db.");
+        return newGraph;
+    }
+
+    public static void createSchemaElements(final Graph newGraph) throws InterruptedException {
+        createVertexLabel(newGraph, GraphResourceRepository.RESOURCE_LABEL);
+        createVertexLabel(newGraph, GraphSubjectRepository.SUBJECT_LABEL);
+        createVertexLabel(newGraph, GraphGenericRepository.VERSION_VERTEX_LABEL);
+
+        createIndex(newGraph, BY_ZONE_INDEX_NAME, ZONE_ID_KEY);
+        createUniqueCompositeIndex(newGraph, BY_ZONE_AND_RESOURCE_UNIQUE_INDEX_NAME,
+                new String[] { ZONE_ID_KEY, RESOURCE_ID_KEY });
+
+        createUniqueCompositeIndex(newGraph, BY_ZONE_AND_SUBJECT_UNIQUE_INDEX_NAME,
+                new String[] { ZONE_ID_KEY, SUBJECT_ID_KEY });
+
+        createUniqueCompositeIndex(newGraph, BY_VERSION_UNIQUE_INDEX_NAME,
+                new String[] { GraphGenericRepository.VERSION_PROPERTY_KEY }, Integer.class);
+
+        createEdgeIndex(newGraph, BY_SCOPE_INDEX_NAME, PARENT_EDGE_LABEL, SCOPE_PROPERTY_KEY);
+    }
+
+    public static void createIndex(final Graph newGraph, final String indexName, final String indexKey)
+            throws InterruptedException {
+        newGraph.tx().rollback(); // Never create new indexes while a transaction is active
+        TitanManagement mgmt = ((TitanGraph) newGraph).openManagement();
+        if (!mgmt.containsGraphIndex(indexName)) {
+            PropertyKey indexPropertyKey = mgmt.makePropertyKey(indexKey).dataType(String.class).make();
+            mgmt.buildIndex(indexName, Vertex.class).addKey(indexPropertyKey).buildCompositeIndex();
+        }
+        mgmt.commit();
+        // Wait for the index to become available
+        ManagementSystem.awaitGraphIndexStatus((TitanGraph) newGraph, indexName).status(SchemaStatus.ENABLED).call();
+    }
+
+    public static void createUniqueIndexForLabel(final Graph newGraph, final String indexName, final String indexKey,
+            final String label) throws InterruptedException {
+        newGraph.tx().rollback(); // Never create new indexes while a transaction is active
+        TitanManagement mgmt = ((TitanGraph) newGraph).openManagement();
+        if (!mgmt.containsGraphIndex(indexName)) {
+            PropertyKey indexPropertyKey = mgmt.makePropertyKey(indexKey).dataType(Integer.class).make();
+            VertexLabel versionLabel = mgmt.makeVertexLabel(label).make();
+            // Create a unique composite index for the property key that indexes only vertices with a given label
+            mgmt.buildIndex(indexName, Vertex.class).addKey(indexPropertyKey).indexOnly(versionLabel).unique()
+                    .buildCompositeIndex();
+        }
+        mgmt.commit();
+        // Wait for the index to become available
+        ManagementSystem.awaitGraphIndexStatus((TitanGraph) newGraph, indexName).status(SchemaStatus.ENABLED).call();
+    }
+
+    private static void createUniqueCompositeIndex(final Graph newGraph, final String indexName,
+            final String[] propertyKeyNames) throws InterruptedException {
+        createUniqueCompositeIndex(newGraph, indexName, propertyKeyNames, String.class);
+    }
+
+    public static void createUniqueCompositeIndex(final Graph newGraph, final String indexName,
+            final String[] propertyKeyNames, final Class<?> propertyKeyType) throws InterruptedException {
+
+        Assert.notEmpty(propertyKeyNames);
+        newGraph.tx().rollback(); // Never create new indexes while a transaction is active
+        TitanManagement mgmt = ((TitanGraph) newGraph).openManagement();
+        IndexBuilder indexBuilder = mgmt.buildIndex(indexName, Vertex.class);
+        if (!mgmt.containsGraphIndex(indexName)) {
+            for (String propertyKeyName : propertyKeyNames) {
+                PropertyKey indexPropertyKey = getOrCreatePropertyKey(propertyKeyName, propertyKeyType, mgmt);
+                indexBuilder.addKey(indexPropertyKey);
+            }
+            indexBuilder.unique().buildCompositeIndex();
+        }
+        mgmt.commit();
+        // Wait for the index to become available
+        ManagementSystem.awaitGraphIndexStatus((TitanGraph) newGraph, indexName).status(SchemaStatus.ENABLED).call();
+    }
+
+    private static PropertyKey getOrCreatePropertyKey(final String keyName, final Class<?> keyType,
+            final TitanManagement mgmt) {
+        PropertyKey propertyKey = mgmt.getPropertyKey(keyName);
+        if (null == propertyKey) {
+            propertyKey = mgmt.makePropertyKey(keyName).dataType(keyType).make();
+        }
+        return propertyKey;
+    }
+
+    public static void createEdgeIndex(final Graph newGraph, final String indexName, final String label,
+            final String indexKey) throws InterruptedException {
+        newGraph.tx().rollback(); // Never create new indexes while a transaction is active
+        TitanManagement mgmt = ((TitanGraph) newGraph).openManagement();
+        EdgeLabel edgeLabel = mgmt.getOrCreateEdgeLabel(label);
+        if (!mgmt.containsRelationIndex(edgeLabel, indexName)) {
+            PropertyKey indexPropertyKey = getOrCreatePropertyKey(indexKey, String.class, mgmt);
+            mgmt.buildEdgeIndex(edgeLabel, indexName, Direction.OUT, indexPropertyKey);
+        }
+        mgmt.commit();
+        // Wait for the index to become available
+        ManagementSystem.awaitRelationIndexStatus((TitanGraph) newGraph, indexName, label).status(SchemaStatus.ENABLED)
+                .call();
+    }
+
+    public static void createVertexLabel(final Graph newGraph, final String vertexLabel) throws InterruptedException {
+        newGraph.tx().rollback();
+        TitanManagement mgmt = ((TitanGraph) newGraph).openManagement();
+        mgmt.getOrCreateVertexLabel(vertexLabel);
+        mgmt.commit();
+    }
+
+    public static void reIndex(final Graph newGraph, final String indexName)
+            throws InterruptedException, ExecutionException {
+        TitanManagement mgmt = ((TitanGraph) newGraph).openManagement();
+        mgmt.updateIndex(mgmt.getGraphIndex(indexName), SchemaAction.REINDEX).get();
+        newGraph.tx().commit();
+    }
+
+    @Bean
+    GraphTraversalSource graphTraversal() {
+        return this.graph.traversal();
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/config/InMemoryDataSourceConfig.java b/service/src/main/java/com/ge/predix/acs/config/InMemoryDataSourceConfig.java
new file mode 100644
index 0000000..7d72fc8
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/config/InMemoryDataSourceConfig.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.config;
+
+import javax.persistence.EntityManagerFactory;
+import javax.sql.DataSource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.transaction.PlatformTransactionManager;
+
+/**
+ * Configuration file used for in-memory profile.
+ *
+ * @author acs-engineers@ge.com
+ */
+@Configuration
+@EnableAutoConfiguration
+@Profile({ "h2" })
+@EnableJpaRepositories({ "com.ge.predix.acs.service.policy.admin.dao", "com.ge.predix.acs.privilege.management.dao",
+        "com.ge.predix.acs.zone.management.dao", "com.ge.predix.acs.attribute.connector.management.dao" })
+public class InMemoryDataSourceConfig {
+    private static final Logger LOGGER = LoggerFactory.getLogger(InMemoryDataSourceConfig.class);
+
+    private final AcsConfigUtil acsConfigUtil = new AcsConfigUtil();
+
+    @Bean
+    public DataSource getDataSourceConfig() {
+        LOGGER.info("Starting ACS with H2 database"); //$NON-NLS-1$
+        return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
+    }
+
+    @Bean
+    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
+        return this.acsConfigUtil.entityManagerFactory(this.getDataSourceConfig());
+    }
+
+    @Bean
+    public PlatformTransactionManager transactionManager(final EntityManagerFactory emf) {
+        return this.acsConfigUtil.transactionManager(emf);
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/config/LocalRedisConnectionFactoryConfig.java b/service/src/main/java/com/ge/predix/acs/config/LocalRedisConnectionFactoryConfig.java
new file mode 100644
index 0000000..30bd62d
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/config/LocalRedisConnectionFactoryConfig.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.config;
+
+import javax.annotation.PostConstruct;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.core.env.Environment;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
+
+import redis.clients.jedis.JedisPoolConfig;
+
+/**
+ * DataSourceConfig used for all cloud profiles.
+ *
+ * @author acs-engineers@ge.com
+ */
+@Configuration
+@Profile({ "redis" })
+public class LocalRedisConnectionFactoryConfig {
+    private static final Logger LOGGER = LoggerFactory.getLogger(LocalRedisConnectionFactoryConfig.class);
+
+    @Autowired
+    private Environment environment;
+
+    private LocalRedisProperties decisionRedisProperties;
+    private LocalRedisProperties resourceRedisProperties;
+    private LocalRedisProperties subjectRedisProperties;
+
+    @PostConstruct
+    private void setupProperties() {
+        this.decisionRedisProperties = new LocalRedisProperties(this.environment, "DECISION");
+        this.resourceRedisProperties = new LocalRedisProperties(this.environment, "RESOURCE");
+        this.subjectRedisProperties = new LocalRedisProperties(this.environment, "SUBJECT");
+    }
+
+    @Bean(name = { "redisConnectionFactory", "decisionRedisConnectionFactory" })
+    public RedisConnectionFactory decisionRedisConnectionFactory() {
+        LOGGER.info("Successfully created Decision Redis connection factory.");
+        return createJedisConnectionFactory(this.decisionRedisProperties);
+    }
+
+    @Bean(name = { "resourceRedisConnectionFactory" })
+    public RedisConnectionFactory resourceRedisConnectionFactory() {
+        LOGGER.info("Successfully created Resource Redis connection factory.");
+        return createJedisConnectionFactory(this.resourceRedisProperties);
+    }
+
+    @Bean(name = { "subjectRedisConnectionFactory" })
+    public RedisConnectionFactory subjectRedisConnectionFactory() {
+        LOGGER.info("Successfully created Subject Redis connection factory.");
+        return createJedisConnectionFactory(subjectRedisProperties);
+    }
+
+    private RedisConnectionFactory createJedisConnectionFactory(final LocalRedisProperties redisProperties) {
+        JedisPoolConfig poolConfig = new JedisPoolConfig();
+        poolConfig.setMaxTotal(redisProperties.getMinActive());
+        poolConfig.setMinIdle(redisProperties.getMaxActive());
+        poolConfig.setMaxWaitMillis(redisProperties.getMaxWaitTime());
+        poolConfig.setTestOnBorrow(false);
+
+        JedisConnectionFactory connFactory = new JedisConnectionFactory(poolConfig);
+        connFactory.setUsePool(false);
+        connFactory.setTimeout(redisProperties.getSoTimeout());
+        connFactory.setHostName(redisProperties.getRedisHost());
+        connFactory.setPort(redisProperties.getRedisPort());
+        return connFactory;
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/config/LocalRedisProperties.java b/service/src/main/java/com/ge/predix/acs/config/LocalRedisProperties.java
new file mode 100644
index 0000000..84413e4
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/config/LocalRedisProperties.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.config;
+
+import org.springframework.core.env.Environment;
+
+public class LocalRedisProperties {
+
+    private Environment env;
+    private String redisHost;
+    private int redisPort;
+    private int minActive;
+    private int maxActive;
+    private int maxWaitTime;
+    private int soTimeout;
+
+    public LocalRedisProperties(final Environment environment, final String cacheType) {
+        this.env = environment;
+        this.redisHost = this.env.getProperty(cacheType + "_REDIS_HOST", String.class, "localhost");
+        this.redisPort = this.env.getProperty(cacheType + "_REDIS_PORT", Integer.class, 6379);
+        this.minActive = this.env.getProperty(cacheType + "_REDIS_MIN_ACTIVE", Integer.class, 0);
+        this.maxActive = this.env.getProperty(cacheType + "_REDIS_MAX_ACTIVE", Integer.class, 100);
+        this.maxWaitTime = this.env.getProperty(cacheType + "_REDIS_MAX_WAIT_TIME", Integer.class, 2000);
+        this.soTimeout = this.env.getProperty(cacheType + "_REDIS_SOCKET_TIMEOUT", Integer.class, 3000);
+    }
+
+    public String getRedisHost() {
+        return redisHost;
+    }
+
+    public int getRedisPort() {
+        return redisPort;
+    }
+
+    public int getMinActive() {
+        return minActive;
+    }
+
+    public int getMaxActive() {
+        return maxActive;
+    }
+
+    public int getMaxWaitTime() {
+        return maxWaitTime;
+    }
+
+    public int getSoTimeout() {
+        return soTimeout;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/config/PolicyEvaluationCacheConfig.java b/service/src/main/java/com/ge/predix/acs/config/PolicyEvaluationCacheConfig.java
new file mode 100644
index 0000000..19e3bdc
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/config/PolicyEvaluationCacheConfig.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.config;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+
+import com.ge.predix.acs.policy.evaluation.cache.InMemoryPolicyEvaluationCache;
+import com.ge.predix.acs.policy.evaluation.cache.NonCachingPolicyEvaluationCache;
+import com.ge.predix.acs.policy.evaluation.cache.PolicyEvaluationCache;
+import com.ge.predix.acs.policy.evaluation.cache.RedisPolicyEvaluationCache;
+
+@Configuration
+public class PolicyEvaluationCacheConfig {
+    private static final Logger LOGGER = LoggerFactory.getLogger(PolicyEvaluationCacheConfig.class);
+
+    @Autowired
+    private Environment environment;
+
+    @Bean
+    public PolicyEvaluationCache cache(@Value("${ENABLE_DECISION_CACHING:true}") final boolean cachingEnabled) {
+        if (!cachingEnabled) {
+            LOGGER.info("Caching disabled for policy evaluation");
+            return new NonCachingPolicyEvaluationCache();
+        }
+        List<String> activeProfiles = Arrays.asList(this.environment.getActiveProfiles());
+        if (activeProfiles.contains("redis") || activeProfiles.contains("cloud-redis")) {
+            LOGGER.info("Redis caching enabled for policy evaluation.");
+            return new RedisPolicyEvaluationCache();
+        }
+        LOGGER.info("In-memory caching enabled for policy evaluation.");
+        return new InMemoryPolicyEvaluationCache();
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/config/UrlPathHelperNonDecoding.java b/service/src/main/java/com/ge/predix/acs/config/UrlPathHelperNonDecoding.java
new file mode 100644
index 0000000..51249ed
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/config/UrlPathHelperNonDecoding.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.config;
+
+import org.springframework.web.util.UrlPathHelper;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+public class UrlPathHelperNonDecoding extends UrlPathHelper {
+
+    public UrlPathHelperNonDecoding() {
+        super.setUrlDecode(false);
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/config/monitoring/AcsMonitoringConfig.java b/service/src/main/java/com/ge/predix/acs/config/monitoring/AcsMonitoringConfig.java
new file mode 100644
index 0000000..b99c1b6
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/config/monitoring/AcsMonitoringConfig.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.config.monitoring;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class AcsMonitoringConfig {
+    @Bean
+    public RestTemplate uaaTemplate() {
+        return new RestTemplate();
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/config/tokenservice/ZoneAwareTokenServiceConfig.java b/service/src/main/java/com/ge/predix/acs/config/tokenservice/ZoneAwareTokenServiceConfig.java
new file mode 100644
index 0000000..1ca3fbe
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/config/tokenservice/ZoneAwareTokenServiceConfig.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.config.tokenservice;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+import com.ge.predix.uaa.token.lib.DefaultZoneConfiguration;
+import com.ge.predix.uaa.token.lib.FastTokenServices;
+import com.ge.predix.uaa.token.lib.ZoneAwareFastTokenService;
+
+@Configuration
+@Profile({ "public" })
+public class ZoneAwareTokenServiceConfig {
+    @Bean
+    public FastTokenServices defaultFastTokenService(
+            @Value("${TRUSTED_ISSUER_ID:${ACS_UAA_URL}/oauth/token}") final String trustedIssuer,
+            @Value("${UAA_USE_HTTPS:false}") final boolean useHttps) {
+        FastTokenServices fastTokenService = new FastTokenServices();
+        fastTokenService.setStoreClaims(true);
+        fastTokenService.setTrustedIssuers(Collections.singletonList(trustedIssuer));
+        fastTokenService.setUseHttps(useHttps);
+        return fastTokenService;
+    }
+
+    @Bean
+    public DefaultZoneConfiguration defaultZoneConfig(
+            @Value("${TRUSTED_ISSUER_ID:${ACS_UAA_URL}/oauth/token}") final String trustedIssuer) {
+        DefaultZoneConfiguration defaultZoneConfig = new DefaultZoneConfiguration();
+        defaultZoneConfig.setTrustedIssuerId(trustedIssuer);
+        defaultZoneConfig.setAllowedUriPatterns(
+                Arrays.asList("/v1/zone/**", "/health*", "/monitoring/heartbeat*"));
+        return defaultZoneConfig;
+    }
+
+    @Bean
+    public ZoneAwareFastTokenService tokenService(@Value("${ACS_SERVICE_ID:predix-acs}") final String acsServiceId,
+            @Value("${ACS_BASE_DOMAIN:localhost}") final String acsBaseDomain,
+            final FastTokenServices defaultFastTokenService, final DefaultZoneConfiguration defaultZoneConfig) {
+        ZoneAwareFastTokenService tokenService = new ZoneAwareFastTokenService();
+        tokenService.setDefaultFastTokenService(defaultFastTokenService);
+        tokenService.setDefaultZoneConfig(defaultZoneConfig);
+        tokenService.setServiceBaseDomain(acsBaseDomain);
+        tokenService.setServiceId(acsServiceId);
+        tokenService.setServiceZoneHeaders("Predix-Zone-Id,ACS-Zone-Subdomain");
+        tokenService.setUseSubdomainsForZones(false);
+        return tokenService;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/encryption/DecryptionFailureException.java b/service/src/main/java/com/ge/predix/acs/encryption/DecryptionFailureException.java
new file mode 100644
index 0000000..d857363
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/encryption/DecryptionFailureException.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.encryption;
+
+@SuppressWarnings("serial")
+public class DecryptionFailureException extends RuntimeException {
+
+    public DecryptionFailureException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/encryption/EncryptionFailureException.java b/service/src/main/java/com/ge/predix/acs/encryption/EncryptionFailureException.java
new file mode 100644
index 0000000..c45a028
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/encryption/EncryptionFailureException.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.encryption;
+
+@SuppressWarnings("serial")
+public class EncryptionFailureException extends RuntimeException {
+
+    public EncryptionFailureException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/encryption/Encryptor.java b/service/src/main/java/com/ge/predix/acs/encryption/Encryptor.java
new file mode 100644
index 0000000..dcab455
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/encryption/Encryptor.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.encryption;
+
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.commons.codec.binary.Base64;
+
+import com.google.common.primitives.Bytes;
+
+public final class Encryptor {
+
+    private static final String ALGO_WITH_PADDING = "AES/CBC/PKCS5PADDING";
+    private static final String ALGO = "AES";
+    private static final int IV_LENGTH_IN_BYTES = 16;
+    private static final int KEY_LENGTH_IN_BYTES = 16;
+    private static final String CHARSET_NAME = "UTF-8";
+
+    private static final ThreadLocal<Cipher> CIPHER_THREAD_LOCAL = ThreadLocal.withInitial(() -> {
+        try {
+            return Cipher.getInstance(ALGO_WITH_PADDING);
+        } catch (Exception e) {
+            throw new CipherInitializationFailureException(
+                    "Could not create instance of cipher with algorithm: " + ALGO_WITH_PADDING, e);
+        }
+    });
+
+    private SecretKeySpec secretKeySpec;
+
+    private static final class CipherInitializationFailureException extends RuntimeException {
+
+        CipherInitializationFailureException(final String message, final Throwable cause) {
+            super(message, cause);
+        }
+    }
+
+    // Modified version of Cassandra's CipherFactory#buildCipher
+    // (https://github.com/apache/cassandra/blob/trunk/src/java/org/apache/cassandra/security/CipherFactory.java)
+    private Cipher buildCipher(final byte[] iv, final int cipherMode) {
+        try {
+            Cipher cipher = CIPHER_THREAD_LOCAL.get();
+            cipher.init(cipherMode, this.secretKeySpec, new IvParameterSpec(iv));
+            return cipher;
+        } catch (Exception e) {
+            throw new CipherInitializationFailureException(
+                    "Could not initialize instance of cipher with algorithm: " + ALGO_WITH_PADDING, e);
+        }
+    }
+
+    public String encrypt(final String value) {
+        try {
+            byte[] ivBytes = new byte[IV_LENGTH_IN_BYTES];
+            new SecureRandom().nextBytes(ivBytes);
+
+            byte[] encrypted = this.buildCipher(ivBytes, Cipher.ENCRYPT_MODE).doFinal(value.getBytes(CHARSET_NAME));
+            byte[] result = Bytes.concat(ivBytes, encrypted);
+
+            return Base64.encodeBase64String(result);
+        } catch (Exception e) {
+            throw new EncryptionFailureException("Unable to encrypt", e);
+        }
+    }
+
+    public String decrypt(final String encrypted) {
+        try {
+            byte[] encryptedBytes = Base64.decodeBase64(encrypted);
+            byte[] ivBytes = Arrays.copyOfRange(encryptedBytes, 0, IV_LENGTH_IN_BYTES);
+            byte[] encryptedSecretBytes = Arrays.copyOfRange(encryptedBytes, IV_LENGTH_IN_BYTES, encryptedBytes.length);
+
+            byte[] original = this.buildCipher(ivBytes, Cipher.DECRYPT_MODE).doFinal(encryptedSecretBytes);
+
+            return new String(original, CHARSET_NAME);
+        } catch (Exception e) {
+            throw new DecryptionFailureException("Unable to decrypt", e);
+        }
+    }
+
+    public void setEncryptionKey(final String encryptionKey) {
+        try {
+            this.secretKeySpec = new SecretKeySpec(encryptionKey.getBytes(CHARSET_NAME), 0, KEY_LENGTH_IN_BYTES, ALGO);
+        } catch (Exception e) {
+            throw new SymmetricKeyValidationException("Encryption key must be string of length: " + KEY_LENGTH_IN_BYTES,
+                    e);
+        }
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/encryption/SymmetricKeyValidationException.java b/service/src/main/java/com/ge/predix/acs/encryption/SymmetricKeyValidationException.java
new file mode 100644
index 0000000..94781d7
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/encryption/SymmetricKeyValidationException.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.encryption;
+
+@SuppressWarnings("serial")
+public class SymmetricKeyValidationException extends RuntimeException {
+    
+    public SymmetricKeyValidationException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/issuer/management/dao/IssuerEntity.java b/service/src/main/java/com/ge/predix/acs/issuer/management/dao/IssuerEntity.java
new file mode 100644
index 0000000..13cc6e8
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/issuer/management/dao/IssuerEntity.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.issuer.management.dao;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+/**
+ *
+ * This class is no longer used except in migration logic.
+ *
+ */
+@Entity
+@Table(
+        name = "issuer",
+        uniqueConstraints = { @UniqueConstraint(columnNames = { "issuer_id" }),
+                @UniqueConstraint(columnNames = { "issuer_check_token_url" }) })
+@Deprecated
+public class IssuerEntity {
+
+    @Id
+    @Column(name = "id")
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private long id;
+
+    @Column(name = "issuer_id", nullable = false)
+    private String issuerId;
+
+    @Column(name = "issuer_check_token_url", nullable = false)
+    private String issuerCheckTokenUrl;
+
+    public IssuerEntity() {
+    }
+
+    public IssuerEntity(final String issuerId, final String issuerCheckTokenUrl) {
+        super();
+        this.issuerId = issuerId;
+        this.issuerCheckTokenUrl = issuerCheckTokenUrl;
+    }
+
+    public long getId() {
+        return this.id;
+    }
+
+    public void setId(final long id) {
+        this.id = id;
+    }
+
+    /**
+     * This is the canonical identifier for the issuer, which is available in a OAUTH token.
+     *
+     * @return
+     */
+    public String getIssuerId() {
+        return this.issuerId;
+    }
+
+    public void setIssuerId(final String issuerId) {
+        this.issuerId = issuerId;
+    }
+
+    /**
+     * URL provided by this issuer to validate OAUTH tokens.
+     *
+     * @return
+     */
+    public String getIssuerCheckTokenUrl() {
+        return this.issuerCheckTokenUrl;
+    }
+
+    public void setIssuerCheckTokenUrl(final String issuerCheckTokenUrl) {
+        this.issuerCheckTokenUrl = issuerCheckTokenUrl;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(this.issuerId).append(this.issuerCheckTokenUrl).toHashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof IssuerEntity) {
+            final IssuerEntity other = (IssuerEntity) obj;
+            return new EqualsBuilder().append(this.issuerId, other.issuerId)
+                    .append(this.issuerCheckTokenUrl, other.issuerCheckTokenUrl).isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "IssuerEntity [id=" + this.id + ", issuerId=" + this.issuerId + ", issuerCheckTokenUrl="
+                + this.issuerCheckTokenUrl + "]";
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/jmx/DataSourceMBean.java b/service/src/main/java/com/ge/predix/acs/jmx/DataSourceMBean.java
new file mode 100644
index 0000000..23f3239
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/jmx/DataSourceMBean.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.jmx;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tomcat.jdbc.pool.DataSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Profile;
+import org.springframework.jmx.export.annotation.ManagedAttribute;
+import org.springframework.jmx.export.annotation.ManagedResource;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.stereotype.Component;
+
+@Profile({ "performance" })
+@ManagedResource(objectName = DataSourceMBean.OBJECTNAME,
+        description = "Connection pool propteries")
+@Component
+public class DataSourceMBean {
+    public static final String OBJECTNAME = "com.ge.predix.acs.jmx:name=DataSourceMBean";
+    private static final String DELIMITER = ";|,";
+    private static final String PROP_DELIMITER = "=|:";
+
+    @Autowired
+    private LocalContainerEntityManagerFactoryBean entityManagerFactory;
+
+    @ManagedAttribute
+    public String getDataSourceImpl() {
+        if (this.entityManagerFactory == null || this.entityManagerFactory.getDataSource() == null) {
+            return "";
+        }
+        return this.entityManagerFactory.getDataSource().getClass().getName();
+    }
+
+    @ManagedAttribute
+    public Map<String, Object> getConnectionPool() {
+        Map<String, Object> connectionPool = new HashMap<>();
+        if (this.entityManagerFactory == null || this.entityManagerFactory.getDataSource() == null) {
+            return connectionPool;
+        }
+        // For TOMCAT_POOL_DATASOURCE
+        if (this.entityManagerFactory.getDataSource().getClass().isAssignableFrom(DataSource.class)) {
+            org.apache.tomcat.jdbc.pool.DataSource tomcatDs = (org.apache.tomcat.jdbc.pool.DataSource) this
+                    .entityManagerFactory
+                    .getDataSource();
+            connectionPool.put("driverClassName", tomcatDs.getDriverClassName());
+            connectionPool.put("numActive", tomcatDs.getNumActive());
+            connectionPool.put("maxActive", tomcatDs.getMaxActive());
+            connectionPool.put("numIdle", tomcatDs.getNumIdle());
+            connectionPool.put("minIdle", tomcatDs.getMinIdle());
+            connectionPool.put("maxIdle", tomcatDs.getMaxIdle());
+            connectionPool.put("maxWait", tomcatDs.getMaxWait());
+            return connectionPool;
+        }
+
+        // in case it is not TOMCAT_POOL_DATASOURCE
+        String str = this.entityManagerFactory.getDataSource().toString();
+        int start = str.indexOf('[');
+        if (start == -1) {
+            start = str.indexOf('=') - 16;
+            if (start <= 0) {
+                return connectionPool;
+            }
+        }
+        String poolString = str.substring(start + 1);
+        String[] array = poolString.split(DELIMITER);
+        for (String prop : array) {
+            if (prop.contains("=") || prop.contains(":")) {
+                String[] nameValue = prop.trim().split(PROP_DELIMITER);
+                if (nameValue.length == 2) {
+                    connectionPool.put(nameValue[0], nameValue[1]);
+                } else {
+                    connectionPool.put(nameValue[0], "null");
+                }
+            }
+        }
+        return connectionPool;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/monitoring/AbstractCacheHealthIndicator.java b/service/src/main/java/com/ge/predix/acs/monitoring/AbstractCacheHealthIndicator.java
new file mode 100644
index 0000000..7cc1a6c
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/monitoring/AbstractCacheHealthIndicator.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import java.util.function.Supplier;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.Status;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisConnectionUtils;
+import org.springframework.util.Assert;
+
+@Profile({ "cloud-redis", "redis" })
+public abstract class AbstractCacheHealthIndicator implements CacheHealthIndicator {
+
+    static final String DESCRIPTION = "Health check performed by attempting to invoke the Redis INFO command";
+    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractCacheHealthIndicator.class);
+
+    private RedisConnectionFactory redisConnectionFactory;
+
+    private boolean cachingEnabled;
+
+    @Value("${ENABLED_REDIS_HEALTH_CHECK:false}")
+    private boolean healthCheckEnabled;
+
+    private String cacheType;
+
+    public AbstractCacheHealthIndicator(final RedisConnectionFactory redisConnectionFactory, final String cacheType,
+            final boolean cachingEnabled) {
+        Assert.notNull(redisConnectionFactory, "ConnectionFactory must not be null");
+        this.redisConnectionFactory = redisConnectionFactory;
+        this.cacheType = cacheType;
+        this.cachingEnabled = cachingEnabled;
+    }
+
+    @Override
+    public Health health() {
+        if (!this.healthCheckEnabled) {
+            return AcsMonitoringUtilities
+                    .health(Status.UNKNOWN, AcsMonitoringUtilities.HealthCode.HEALTH_CHECK_DISABLED, DESCRIPTION);
+        }
+        if (!this.cachingEnabled) {
+            return AcsMonitoringUtilities
+                    .health(Status.UNKNOWN, AcsMonitoringUtilities.HealthCode.DISABLED, DESCRIPTION);
+        }
+        return cacheHealth(redisConnectionFactory, this.cacheType, DESCRIPTION, this::getRedisConnection);
+    }
+
+    static Health cacheHealth(final RedisConnectionFactory connectionFactory, final String cacheType,
+            final String description, final Supplier<RedisConnection> connectionSupplier) {
+        AcsMonitoringUtilities.HealthCode healthCode;
+        RedisConnection connection = null;
+
+        try {
+            LOGGER.debug("Checking {} cache status", cacheType);
+            connection = connectionSupplier.get();
+            connection.info();
+            healthCode = AcsMonitoringUtilities.HealthCode.AVAILABLE;
+        } catch (Exception e) {
+            healthCode = AcsMonitoringUtilities.logError(AcsMonitoringUtilities.HealthCode.ERROR, LOGGER,
+                    AcsMonitoringUtilities.ERROR_MESSAGE_FORMAT, e);
+        } finally {
+            RedisConnectionUtils.releaseConnection(connection, connectionFactory);
+        }
+
+        try {
+            if (healthCode == AcsMonitoringUtilities.HealthCode.AVAILABLE) {
+                return AcsMonitoringUtilities.health(Status.UP, healthCode, description);
+            }
+            return AcsMonitoringUtilities.health(Status.DOWN, healthCode, description);
+        } catch (Exception e) {
+            return AcsMonitoringUtilities.health(Status.DOWN, AcsMonitoringUtilities
+                    .logError(AcsMonitoringUtilities.HealthCode.ERROR, LOGGER,
+                            AcsMonitoringUtilities.ERROR_MESSAGE_FORMAT, e), description);
+        }
+    }
+
+    @Override
+    public RedisConnection getRedisConnection() {
+        return RedisConnectionUtils.getConnection(this.redisConnectionFactory);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/monitoring/AcsDbHealthIndicator.java b/service/src/main/java/com/ge/predix/acs/monitoring/AcsDbHealthIndicator.java
new file mode 100644
index 0000000..f9fb66d
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/monitoring/AcsDbHealthIndicator.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.dao.PermissionDeniedDataAccessException;
+import org.springframework.dao.QueryTimeoutException;
+import org.springframework.dao.TransientDataAccessResourceException;
+import org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.privilege.management.dao.TitanMigrationManager;
+
+@Component
+public class AcsDbHealthIndicator implements HealthIndicator {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AcsDbHealthIndicator.class);
+    private static final String ERROR_MESSAGE_FORMAT = "Unexpected exception while checking ACS database status: {}";
+    static final String DESCRIPTION = "Health check performed by attempting to run a SELECT query against policy "
+            + "sets stored in the database";
+
+    private final AcsMonitoringRepository acsMonitoringRepository;
+    private TitanMigrationManager migrationManager;
+
+    @Autowired
+    public AcsDbHealthIndicator(final AcsMonitoringRepository acsMonitoringRepository) {
+        this.acsMonitoringRepository = acsMonitoringRepository;
+    }
+
+    @Autowired(required = false)
+    void setMigrationManager(final TitanMigrationManager migrationManager) {
+        this.migrationManager = migrationManager;
+    }
+
+    @Override
+    public Health health() {
+        return AcsMonitoringUtilities.health(this::check, DESCRIPTION);
+    }
+
+    private AcsMonitoringUtilities.HealthCode check() {
+        AcsMonitoringUtilities.HealthCode healthCode;
+
+        try {
+            LOGGER.debug("Checking ACS database status");
+            this.acsMonitoringRepository.queryPolicySetTable();
+
+            if (this.migrationManager != null && !this.migrationManager.isMigrationComplete()) {
+                healthCode = AcsMonitoringUtilities.HealthCode.MIGRATION_INCOMPLETE;
+            } else {
+                healthCode = AcsMonitoringUtilities.HealthCode.AVAILABLE;
+            }
+        } catch (TransientDataAccessResourceException | QueryTimeoutException e) {
+            healthCode = AcsMonitoringUtilities.logError(AcsMonitoringUtilities.HealthCode.UNAVAILABLE, LOGGER,
+                    ERROR_MESSAGE_FORMAT, e);
+        } catch (DataSourceLookupFailureException e) {
+            healthCode = AcsMonitoringUtilities.logError(AcsMonitoringUtilities.HealthCode.UNREACHABLE, LOGGER,
+                    ERROR_MESSAGE_FORMAT, e);
+        } catch (PermissionDeniedDataAccessException e) {
+            healthCode = AcsMonitoringUtilities.logError(AcsMonitoringUtilities.HealthCode.MISCONFIGURATION, LOGGER,
+                    ERROR_MESSAGE_FORMAT, e);
+        } catch (Exception e) {
+            healthCode = AcsMonitoringUtilities.logError(AcsMonitoringUtilities.HealthCode.ERROR, LOGGER,
+                    ERROR_MESSAGE_FORMAT, e);
+        }
+
+        return healthCode;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/monitoring/AcsHealthAggregator.java b/service/src/main/java/com/ge/predix/acs/monitoring/AcsHealthAggregator.java
new file mode 100644
index 0000000..9a54fc8
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/monitoring/AcsHealthAggregator.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthAggregator;
+import org.springframework.boot.actuate.health.Status;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+
+@Component
+// Modified version of org.springframework.boot.actuate.health.OrderedHealthAggregator
+public class AcsHealthAggregator implements HealthAggregator {
+
+    static final Status DEGRADED_STATUS = new Status(AcsMonitoringUtilities.HealthCode.DEGRADED.toString());
+
+    private List<String> statusOrder;
+
+    AcsHealthAggregator() {
+        this.setStatusOrder(Status.DOWN, Status.OUT_OF_SERVICE, DEGRADED_STATUS, Status.UP, Status.UNKNOWN);
+    }
+
+    void setStatusOrder(final Status... statusOrder) {
+        String[] order = new String[statusOrder.length];
+        for (int i = 0; i < statusOrder.length; ++i) {
+            order[i] = statusOrder[i].getCode();
+        }
+        this.setStatusOrder(Arrays.asList(order));
+    }
+
+    void setStatusOrder(final List<String> statusOrder) {
+        Assert.notNull(statusOrder, "StatusOrder must not be null");
+        this.statusOrder = statusOrder;
+    }
+
+    @Override
+    public final Health aggregate(final Map<String, Health> healths) {
+        List<Status> statusCandidates = new ArrayList<>();
+        for (Map.Entry<String, Health> entry : healths.entrySet()) {
+            Status status = entry.getValue().getStatus();
+            if (entry.getKey().toLowerCase().contains("cache")) {
+                if (status.equals(Status.DOWN)) {
+                    statusCandidates.add(DEGRADED_STATUS);
+                } else if (status.equals(Status.UNKNOWN)) {
+                    statusCandidates.add(Status.UP);
+                }
+            } else {
+                statusCandidates.add(entry.getValue().getStatus());
+            }
+        }
+        Status status = this.aggregateStatus(statusCandidates);
+        Map<String, Object> details = aggregateDetails(healths);
+        return new Health.Builder(status, details).build();
+    }
+
+    private Status aggregateStatus(final List<Status> candidates) {
+        // Only sort those status instances that we know about
+        List<Status> filteredCandidates = new ArrayList<>();
+        for (Status candidate : candidates) {
+            if (this.statusOrder.contains(candidate.getCode())) {
+                filteredCandidates.add(candidate);
+            }
+        }
+        // If no status is given return UNKNOWN
+        if (filteredCandidates.isEmpty()) {
+            return Status.UNKNOWN;
+        }
+        // Sort given Status instances by configured order
+        filteredCandidates.sort(new StatusComparator(this.statusOrder));
+        return filteredCandidates.get(0);
+    }
+
+    private static Map<String, Object> aggregateDetails(final Map<String, Health> healths) {
+        return new LinkedHashMap<>(healths);
+    }
+
+    private class StatusComparator implements Comparator<Status> {
+
+        private final List<String> statusOrder;
+
+        StatusComparator(final List<String> statusOrder) {
+            this.statusOrder = statusOrder;
+        }
+
+        @Override
+        public int compare(final Status s1, final Status s2) {
+            int i1 = this.statusOrder.indexOf(s1.getCode());
+            int i2 = this.statusOrder.indexOf(s2.getCode());
+            if (i1 < i2) {
+                return -1;
+            } else if (i1 == i2) {
+                return s1.getCode().compareTo(s2.getCode());
+            } else {
+                return 1;
+            }
+        }
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/monitoring/AcsMonitoringController.java b/service/src/main/java/com/ge/predix/acs/monitoring/AcsMonitoringController.java
new file mode 100644
index 0000000..992edee
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/monitoring/AcsMonitoringController.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.HEARTBEAT_URL;
+import static org.springframework.web.bind.annotation.RequestMethod.GET;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import io.swagger.annotations.ApiOperation;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@RestController
+public class AcsMonitoringController {
+
+    @ApiOperation(value = "Monitoring API that allows to check the ACS heartbeat", tags = { "Monitoring" })
+    @RequestMapping(method = GET, value = HEARTBEAT_URL)
+    public String getHeartBeat() {
+        return "alive";
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/monitoring/AcsMonitoringRepository.java b/service/src/main/java/com/ge/predix/acs/monitoring/AcsMonitoringRepository.java
new file mode 100644
index 0000000..2320d4b
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/monitoring/AcsMonitoringRepository.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Repository;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@Repository
+public class AcsMonitoringRepository {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AcsMonitoringRepository.class);
+
+    private JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public void setDataSource(final DataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    void queryPolicySetTable() {
+        String query = "select policy_set_id from policy_set limit 1";
+        List<String> queryResults = this.jdbcTemplate.query(query, (rs, rowNum) -> rs.getString(1));
+        LOGGER.info("Successfully executed health check query on ACS database: {} (result set size: {})", query,
+                queryResults.size());
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/monitoring/AcsMonitoringUtilities.java b/service/src/main/java/com/ge/predix/acs/monitoring/AcsMonitoringUtilities.java
new file mode 100644
index 0000000..9093b61
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/monitoring/AcsMonitoringUtilities.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import java.util.function.Supplier;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.Status;
+
+public final class AcsMonitoringUtilities {
+
+    enum HealthCode {
+        ERROR,
+        AVAILABLE,
+        UNAVAILABLE,
+        UNREACHABLE,
+        MISCONFIGURATION,
+        MIGRATION_INCOMPLETE,
+        INVALID_QUERY,
+        INVALID_JSON,
+        DEGRADED,
+        DISABLED,
+        HEALTH_CHECK_DISABLED,
+        IN_MEMORY
+    }
+
+    public static final String STATUS = "status";
+    private static final Logger LOGGER = LoggerFactory.getLogger(AcsMonitoringUtilities.class);
+    static final String ERROR_MESSAGE_FORMAT = "Unexpected exception while checking health: {}";
+    static final String DESCRIPTION_KEY = "description";
+    static final String CODE_KEY = "code";
+
+    private AcsMonitoringUtilities() {
+        throw new UnsupportedOperationException();
+    }
+
+    static Health health(final Status status, final HealthCode healthCode, final String description) {
+        Health.Builder healthBuilder = Health.status(status);
+        if (healthCode != HealthCode.AVAILABLE) {
+            healthBuilder.withDetail(CODE_KEY, healthCode);
+        }
+        healthBuilder.withDetail(DESCRIPTION_KEY, description);
+        return healthBuilder.build();
+    }
+
+    static Health health(final Supplier<HealthCode> check, final String description) {
+        try {
+            HealthCode healthCode = check.get();
+            if (healthCode == HealthCode.AVAILABLE) {
+                return health(Status.UP, healthCode, description);
+            }
+            return health(Status.DOWN, healthCode, description);
+        } catch (Exception e) {
+            return health(Status.DOWN,
+                    AcsMonitoringUtilities.logError(HealthCode.ERROR, LOGGER, ERROR_MESSAGE_FORMAT, e), description);
+        }
+    }
+
+    static HealthCode logError(final HealthCode healthCode, final Logger logger, final String format,
+            final Throwable throwable) {
+        logger.error(format, healthCode, throwable);
+        return healthCode;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/monitoring/CacheHealthIndicator.java b/service/src/main/java/com/ge/predix/acs/monitoring/CacheHealthIndicator.java
new file mode 100644
index 0000000..b2be35e
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/monitoring/CacheHealthIndicator.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.data.redis.connection.RedisConnection;
+
+public interface CacheHealthIndicator extends HealthIndicator {
+
+    RedisConnection getRedisConnection();
+}
diff --git a/service/src/main/java/com/ge/predix/acs/monitoring/DecisionCacheHealthIndicator.java b/service/src/main/java/com/ge/predix/acs/monitoring/DecisionCacheHealthIndicator.java
new file mode 100644
index 0000000..c2a848d
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/monitoring/DecisionCacheHealthIndicator.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.policy.evaluation.cache.PolicyEvaluationCache;
+
+@Component
+@Profile({ "cloud-redis", "redis" })
+// This class doesn't extend RedisHealthIndicator on purpose because we don't want to output Redis-specific properties
+public class DecisionCacheHealthIndicator extends AbstractCacheHealthIndicator {
+
+    @Autowired
+    public DecisionCacheHealthIndicator(final RedisConnectionFactory decisionRedisConnectionFactory,
+            @Value("${ENABLE_DECISION_CACHING:false}") final boolean cacheEnabled) {
+        super(decisionRedisConnectionFactory, PolicyEvaluationCache.DECISION, cacheEnabled);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/monitoring/GraphDbHealthIndicator.java b/service/src/main/java/com/ge/predix/acs/monitoring/GraphDbHealthIndicator.java
new file mode 100644
index 0000000..ad6ab8a
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/monitoring/GraphDbHealthIndicator.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.boot.actuate.health.Status;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.privilege.management.dao.GraphResourceRepository;
+import com.ge.predix.acs.privilege.management.dao.TitanMigrationManager;
+import com.thinkaurelius.titan.core.QueryException;
+import com.thinkaurelius.titan.core.TitanConfigurationException;
+import com.thinkaurelius.titan.diskstorage.ResourceUnavailableException;
+
+@Component
+@Profile({ "titan" })
+public class GraphDbHealthIndicator implements HealthIndicator {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(GraphDbHealthIndicator.class);
+    private static final String ERROR_MESSAGE_FORMAT = "Unexpected exception while checking graph database status: {}";
+    static final String DESCRIPTION = "Health check performed by attempting to create a version vertex and retrieve it "
+            + "from the underlying graph store";
+
+    private final GraphResourceRepository resourceHierarchicalRepository;
+
+    @Value("${TITAN_ENABLE_CASSANDRA:false}")
+    private boolean cassandraEnabled;
+
+    @Autowired
+    public GraphDbHealthIndicator(final GraphResourceRepository resourceHierarchicalRepository) {
+        this.resourceHierarchicalRepository = resourceHierarchicalRepository;
+    }
+
+    @Override
+    public Health health() {
+        Health health = AcsMonitoringUtilities.health(this::check, DESCRIPTION);
+        Status status = health.getStatus();
+        if (status.equals(Status.UP) && !this.cassandraEnabled) {
+            health = AcsMonitoringUtilities.health(status, AcsMonitoringUtilities.HealthCode.IN_MEMORY, DESCRIPTION);
+        }
+        return health;
+    }
+
+    private AcsMonitoringUtilities.HealthCode check() {
+        AcsMonitoringUtilities.HealthCode healthCode = AcsMonitoringUtilities.HealthCode.ERROR;
+
+        try {
+            LOGGER.debug("Checking graph database status");
+            if (this.resourceHierarchicalRepository
+                    .checkVersionVertexExists(TitanMigrationManager.INITIAL_ATTRIBUTE_GRAPH_VERSION)) {
+                healthCode = AcsMonitoringUtilities.HealthCode.AVAILABLE;
+            }
+        } catch (QueryException e) {
+            healthCode = AcsMonitoringUtilities.logError(AcsMonitoringUtilities.HealthCode.INVALID_QUERY, LOGGER,
+                    ERROR_MESSAGE_FORMAT, e);
+        } catch (ResourceUnavailableException e) {
+            healthCode = AcsMonitoringUtilities.logError(AcsMonitoringUtilities.HealthCode.UNAVAILABLE, LOGGER,
+                    ERROR_MESSAGE_FORMAT, e);
+        } catch (TitanConfigurationException e) {
+            healthCode = AcsMonitoringUtilities.logError(AcsMonitoringUtilities.HealthCode.MISCONFIGURATION, LOGGER,
+                    ERROR_MESSAGE_FORMAT, e);
+        } catch (Exception e) {
+            healthCode = AcsMonitoringUtilities.logError(AcsMonitoringUtilities.HealthCode.ERROR, LOGGER,
+                    ERROR_MESSAGE_FORMAT, e);
+        }
+
+        return healthCode;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/monitoring/ManagementSecurityRoleFilter.java b/service/src/main/java/com/ge/predix/acs/monitoring/ManagementSecurityRoleFilter.java
new file mode 100644
index 0000000..a7ede78
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/monitoring/ManagementSecurityRoleFilter.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+public class ManagementSecurityRoleFilter extends OncePerRequestFilter {
+
+    private static final SimpleGrantedAuthority ROLE_FOR_MONITORING = new SimpleGrantedAuthority("acs.monitoring");
+
+    @Override
+    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
+            final FilterChain filterChain) throws ServletException, IOException {
+
+        HttpServletRequestWrapper wrappedHttpServletRequest = new HttpServletRequestWrapper(request) {
+            @Override
+            public boolean isUserInRole(final String role) {
+                return SecurityContextHolder.getContext().getAuthentication().getAuthorities()
+                        .contains(ROLE_FOR_MONITORING);
+            }
+        };
+        filterChain.doFilter(wrappedHttpServletRequest, response);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/monitoring/MonitoringHttpMethodsFilter.java b/service/src/main/java/com/ge/predix/acs/monitoring/MonitoringHttpMethodsFilter.java
new file mode 100644
index 0000000..2130bef
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/monitoring/MonitoringHttpMethodsFilter.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.security.AbstractHttpMethodsFilter;
+
+@Component
+public class MonitoringHttpMethodsFilter extends AbstractHttpMethodsFilter {
+
+    private static final String HEARTBEAT_URI_REGEX = "\\A/monitoring/heartbeat/??\\Z";
+
+    private static Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods() {
+        Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods = new LinkedHashMap<>();
+        uriPatternsAndAllowedHttpMethods.put(HEARTBEAT_URI_REGEX,
+                new HashSet<>(Arrays.asList(HttpMethod.GET, HttpMethod.HEAD)));
+        return uriPatternsAndAllowedHttpMethods;
+    }
+
+    public MonitoringHttpMethodsFilter() {
+        super(uriPatternsAndAllowedHttpMethods());
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/monitoring/ResourceCacheHealthIndicator.java b/service/src/main/java/com/ge/predix/acs/monitoring/ResourceCacheHealthIndicator.java
new file mode 100644
index 0000000..24f274b
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/monitoring/ResourceCacheHealthIndicator.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.attribute.cache.AttributeCache;
+
+
+@Component
+@Profile({ "cloud-redis", "redis" })
+
+// This class doesn't extend RedisHealthIndicator on purpose because we don't want to output Redis-specific properties
+public class ResourceCacheHealthIndicator extends AbstractCacheHealthIndicator {
+
+    @Autowired
+    public ResourceCacheHealthIndicator(final RedisConnectionFactory resourceRedisConnectionFactory,
+            @Value("${ENABLE_RESOURCE_CACHING:false}") final boolean cacheEnabled) {
+        super(resourceRedisConnectionFactory, AttributeCache.RESOURCE, cacheEnabled);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/monitoring/SubjectCacheHealthIndicator.java b/service/src/main/java/com/ge/predix/acs/monitoring/SubjectCacheHealthIndicator.java
new file mode 100644
index 0000000..a4d2f02
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/monitoring/SubjectCacheHealthIndicator.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.attribute.cache.AttributeCache;
+
+@Component
+@Profile({ "cloud-redis", "redis" })
+// This class doesn't extend RedisHealthIndicator on purpose because we don't want to output Redis-specific properties
+public class SubjectCacheHealthIndicator extends AbstractCacheHealthIndicator {
+
+    @Autowired
+    public SubjectCacheHealthIndicator(final RedisConnectionFactory subjectRedisConnectionFactory,
+            @Value("${ENABLE_SUBJECT_CACHING:false}") final boolean cacheEnabled) {
+        super(subjectRedisConnectionFactory, AttributeCache.SUBJECT, cacheEnabled);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/monitoring/UaaHealthIndicator.java b/service/src/main/java/com/ge/predix/acs/monitoring/UaaHealthIndicator.java
new file mode 100644
index 0000000..4859161
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/monitoring/UaaHealthIndicator.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+
+@Component
+public class UaaHealthIndicator implements HealthIndicator {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(UaaHealthIndicator.class);
+    private static final String ERROR_MESSAGE_FORMAT = "Unexpected exception while checking UAA status: {}";
+
+    private final RestTemplate uaaTemplate;
+
+    @Value("${uaaCheckHealthUrl}")
+    private String uaaCheckHealthUrl;
+
+    @Autowired
+    public UaaHealthIndicator(final RestTemplate uaaTemplate) {
+        this.uaaTemplate = uaaTemplate;
+    }
+
+    @Override
+    public Health health() {
+        return AcsMonitoringUtilities.health(this::check, this.getDescription());
+    }
+
+    private AcsMonitoringUtilities.HealthCode check() {
+        AcsMonitoringUtilities.HealthCode healthCode;
+
+        try {
+            LOGGER.debug("Checking UAA status using URL: {}", this.uaaCheckHealthUrl);
+            this.uaaTemplate.getForObject(this.uaaCheckHealthUrl, String.class);
+            healthCode = AcsMonitoringUtilities.HealthCode.AVAILABLE;
+        } catch (RestClientException e) {
+            healthCode = AcsMonitoringUtilities.logError(AcsMonitoringUtilities.HealthCode.UNREACHABLE, LOGGER,
+                    ERROR_MESSAGE_FORMAT, e);
+        } catch (Exception e) {
+            healthCode = AcsMonitoringUtilities.logError(AcsMonitoringUtilities.HealthCode.ERROR, LOGGER,
+                    ERROR_MESSAGE_FORMAT, e);
+        }
+
+        return healthCode;
+    }
+
+    String getDescription() {
+        return String.format("Health check performed by attempting to hit '%s'", this.uaaCheckHealthUrl);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/AbstractPolicyEvaluationCache.java b/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/AbstractPolicyEvaluationCache.java
new file mode 100644
index 0000000..43a5a0d
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/AbstractPolicyEvaluationCache.java
@@ -0,0 +1,474 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.policy.evaluation.cache;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.Minutes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.attribute.connector.management.AttributeConnectorService;
+import com.ge.predix.acs.privilege.management.dao.ResourceEntity;
+import com.ge.predix.acs.privilege.management.dao.SubjectEntity;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+
+enum EntityType {
+
+    RESOURCE("resource"),
+    SUBJECT("subject"),
+    POLICY_SET("policy set");
+
+    private final String name;
+
+    EntityType(final String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String toString() {
+        return name;
+    }
+}
+
+@Component
+public abstract class AbstractPolicyEvaluationCache implements PolicyEvaluationCache {
+
+    @Autowired
+    private AttributeConnectorService connectorService;
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractPolicyEvaluationCache.class);
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+    /**
+     * This method will get the Policy Evaluation Result from the cache. It will check if any of the subject, policy
+     * sets or resolved resource URI's have a timestamp in the cache after the timestamp of the Policy Evaluation
+     * Result. If the key is not in the cache or the result is invalidated, it will return null. Also it will remove
+     * the Policy EvaluationResult so that subsequent evaluations won't find the key in the cache.
+     *
+     * @param evalRequestkey The Policy Evaluation key to retrieve.
+     * @return The Policy Evaluation Result if the key is in the cache and the result isn't invalidated, or null
+     */
+    @Override
+    public PolicyEvaluationResult get(final PolicyEvaluationRequestCacheKey evalRequestkey) {
+        //Get all result related entries
+        DecisionCacheEntries cachedEntries = new DecisionCacheEntries(evalRequestkey);
+
+        String cachedEvalResultString = cachedEntries.getDecisionString();
+        if (null == cachedEvalResultString) {
+            return null;
+        }
+        PolicyEvaluationResult cachedEvalResult = toPolicyEvaluationResult(cachedEvalResultString);
+
+        List<String> attributeInvalidationTimeStamps = new ArrayList<>();
+        List<String> policyInvalidationTimeStamps = new ArrayList<>();
+        attributeInvalidationTimeStamps.add(cachedEntries.getSubjectLastModified());
+        policyInvalidationTimeStamps.addAll(cachedEntries.getPolicySetsLastModified());
+
+        Set<String> cachedResolvedResourceUris = cachedEvalResult.getResolvedResourceUris();
+
+        //is requested resource id same as resolved resource uri ?
+        if (cachedResolvedResourceUris.size() == 1 && cachedResolvedResourceUris.iterator().next()
+                .equals(evalRequestkey.getResourceId())) {
+            attributeInvalidationTimeStamps.add(cachedEntries.getRequestedResourceLastModified());
+        } else {
+            List<String> cacheResolvedResourceKeys = cachedResolvedResourceUris.stream()
+                    .map(resolvedResourceUri -> resourceKey(evalRequestkey.getZoneId(), resolvedResourceUri))
+                    .collect(Collectors.toList());
+            attributeInvalidationTimeStamps.addAll(multiGet(cacheResolvedResourceKeys));
+        }
+
+        if (isCachedRequestInvalid(attributeInvalidationTimeStamps, policyInvalidationTimeStamps,
+                timestampToDateUTC(cachedEvalResult.getTimestamp()))) {
+            delete(cachedEntries.getDecisionKey());
+            LOGGER.debug("Cached decision for key '{}' is not valid.", cachedEntries.getDecisionKey());
+            return null;
+        }
+
+        return cachedEvalResult;
+    }
+
+    private PolicyEvaluationResult toPolicyEvaluationResult(final String cachedDecisionString) {
+        try {
+            return OBJECT_MAPPER.readValue(cachedDecisionString, PolicyEvaluationResult.class);
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to read policy evaluation result as JSON.", e);
+        }
+    }
+
+    private final class DecisionCacheEntries {
+        private final List<String> entryValues;
+        private final List<String> entryKeys;
+        private final List<String> policySetTimestamps = new ArrayList<>();
+        private final int lastValueIndex;
+        private final String decisionKey;
+
+        /**
+         * Execute a multi-get operation on all entries related to a cached result,
+         * and provides a immutable object for the values fetched.
+         */
+        DecisionCacheEntries(final PolicyEvaluationRequestCacheKey evalRequestKey) {
+            //Get all values with a batch get
+            this.decisionKey = evalRequestKey.toDecisionKey();
+            this.entryKeys = prepareKeys(evalRequestKey);
+            this.entryValues = multiGet(this.entryKeys);
+            this.lastValueIndex = this.entryValues.size() - 1;
+
+            logCacheGetDebugMessages(evalRequestKey, this.decisionKey, this.entryKeys, this.entryValues);
+
+            //create separate list of policySetTimes to prevent mutation on entryValues
+            for (int i = 0; i < evalRequestKey.getPolicySetIds().size(); i++) {
+                this.policySetTimestamps.add(this.entryValues.get(i));
+            }
+        }
+
+        //Prepare keys to fetch in one batch
+        private List<String> prepareKeys(final PolicyEvaluationRequestCacheKey evalRequestKey) {
+            List<String> keys = new ArrayList<>();
+
+            //Add 'n' Policy Set keys
+            LinkedHashSet<String> policySetIds = evalRequestKey.getPolicySetIds();
+            policySetIds.forEach(policySetId -> keys.add(policySetKey(evalRequestKey.getZoneId(), policySetId)));
+
+            // n+1
+            keys.add(subjectKey(evalRequestKey.getZoneId(), evalRequestKey.getSubjectId()));
+
+            //n+2
+            keys.add(resourceKey(evalRequestKey.getZoneId(), evalRequestKey.getResourceId()));
+
+            //n+3
+            keys.add(this.decisionKey);
+
+            return keys;
+        }
+
+        /**
+         * (eval result, eval time, resolved resource uri(s)).
+         */
+        String getDecisionString() {
+            return entryValues.get(this.lastValueIndex);
+        }
+
+        String getDecisionKey() {
+            return decisionKey;
+        }
+
+        String getSubjectLastModified() {
+            return entryValues.get(lastValueIndex - 2);
+        }
+
+        String getRequestedResourceLastModified() {
+            return entryValues.get(lastValueIndex - 1);
+        }
+
+        List<String> getPolicySetsLastModified() {
+            return this.policySetTimestamps;
+        }
+    }
+
+    private void logCacheGetDebugMessages(final PolicyEvaluationRequestCacheKey key, final String redisKey,
+            final List<String> keys, final List<String> values) {
+        LinkedHashSet<String> policySetIds = key.getPolicySetIds();
+        int idx = 0;
+        for (String policySetId : policySetIds) {
+            LOGGER.debug("Getting timestamp for policy set: '{}', key: '{}', timestamp:'{}'.", policySetId,
+                    keys.get(idx), values.get(idx++));
+        }
+        LOGGER.debug("Getting timestamp for subject: '{}', key: '{}', timestamp:'{}'.", key.getSubjectId(),
+                keys.get(idx), values.get(idx++));
+        LOGGER.debug("Getting timestamp for resource: '{}', key: '{}', timestamp:'{}'.", key.getResourceId(),
+                keys.get(idx), values.get(idx++));
+        LOGGER.debug("Getting policy evaluation from cache; key: '{}', value: '{}'.", redisKey, values.get(idx));
+    }
+
+    // Set's the policy evaluation key to the policy evaluation result in the cache
+    @Override
+    public void set(final PolicyEvaluationRequestCacheKey key, final PolicyEvaluationResult result) {
+        try {
+            setEntityTimestamps(key, result);
+            result.setTimestamp(currentDateUTC().getMillis());
+            String value = OBJECT_MAPPER.writeValueAsString(result);
+            set(key.toDecisionKey(), value);
+            LOGGER.debug("Setting policy evaluation to cache; key: '{}', value: '{}'.", key.toDecisionKey(), value);
+        } catch (IOException e) {
+            throw new IllegalArgumentException("Failed to write policy evaluation result as JSON.", e);
+        }
+    }
+
+    private void setEntityTimestamps(final PolicyEvaluationRequestCacheKey key, final PolicyEvaluationResult result) {
+        // This ensures that if the timestamp for any entity involved in this decision is not in the cache at the time
+        // of this evaluation, it will be put there so that in subsequent evaluations, we will use the cached
+        // decision.
+        // We reset the timestamp to now for entities only if they do not exist in the cache so that we don't
+        // invalidate previous cached decisions.
+        LOGGER.debug("Setting timestamp to now for entities if they do not exist in the cache"); 
+
+        String zoneId = key.getZoneId();
+        setSubjectIfNotExists(zoneId, key.getSubjectId());
+
+        Set<String> resolvedResourceUris = result.getResolvedResourceUris();
+        for (String resolvedResourceUri : resolvedResourceUris) {
+            setResourceIfNotExists(zoneId, resolvedResourceUri);
+        }
+
+        Set<String> policySetIds = key.getPolicySetIds();
+        for (String policySetId : policySetIds) {
+            setPolicySetIfNotExists(zoneId, policySetId);
+        }
+    }
+
+    @Override
+    public void reset() {
+        flushAll();
+    }
+
+    @Override
+    public void reset(final PolicyEvaluationRequestCacheKey key) {
+        Set<String> keys = keys(key.toDecisionKey());
+        delete(keys);
+    }
+
+    // Method which resets the timestamp for the given entity in the policy evaluation cache.
+    private void resetForEntity(final String zoneId, final String entityId, final EntityType entityType,
+            final BiFunction<String, String, String> getKey) {
+        String key = getKey.apply(zoneId, entityId);
+        String timestamp = timestampUTC();
+        logSetEntityTimestampsDebugMessage(timestamp, key, entityId, entityType);
+        set(key, timestamp);
+    }
+
+    private void setEntityIfNotExists(final String zoneId, final String entityId,
+            final BiFunction<String, String, String> getKey) {
+        String key = getKey.apply(zoneId, entityId);
+        setIfNotExists(key, timestampUTC());
+    }
+
+    private void logSetEntityTimestampsDebugMessage(final String timestamp, final String key, final String entityId,
+            final EntityType entityType) {
+        LOGGER.debug("Setting timestamp for {} '{}'; key: '{}', value: '{}'", entityType, entityId, key, timestamp);
+    }
+
+    @Override
+    public void resetForPolicySet(final String zoneId, final String policySetId) {
+        resetForEntity(zoneId, policySetId, EntityType.POLICY_SET, AbstractPolicyEvaluationCache::policySetKey);
+    }
+
+    private void setPolicySetIfNotExists(final String zoneId, final String policySetId) {
+        setEntityIfNotExists(zoneId, policySetId, AbstractPolicyEvaluationCache::policySetKey);
+    }
+
+    @Override
+    public void resetForResource(final String zoneId, final String resourceId) {
+        resetForEntity(zoneId, resourceId, EntityType.RESOURCE, AbstractPolicyEvaluationCache::resourceKey);
+    }
+
+    private void setResourceIfNotExists(final String zoneId, final String resourceId) {
+        setEntityIfNotExists(zoneId, resourceId, AbstractPolicyEvaluationCache::resourceKey);
+    }
+
+    @Override
+    public void resetForResourcesByIds(final String zoneId, final Set<String> resourceIds) {
+        Map<String, String> map = new HashMap<>();
+        for (String resourceId : resourceIds) {
+            createMutliSetEntityMap(zoneId, map, resourceId, EntityType.RESOURCE,
+                    AbstractPolicyEvaluationCache::resourceKey);
+        }
+        multiSet(map);
+    }
+
+    @Override
+    public void resetForResources(final String zoneId, final List<ResourceEntity> resourceEntities) {
+        Map<String, String> map = new HashMap<>();
+        for (ResourceEntity resourceEntity : resourceEntities) {
+            createMutliSetEntityMap(zoneId, map, resourceEntity.getResourceIdentifier(), EntityType.RESOURCE,
+                    AbstractPolicyEvaluationCache::resourceKey);
+        }
+        multiSet(map);
+    }
+
+    @Override
+    public void resetForSubject(final String zoneId, final String subjectId) {
+        resetForEntity(zoneId, subjectId, EntityType.SUBJECT, AbstractPolicyEvaluationCache::subjectKey);
+    }
+
+    private void setSubjectIfNotExists(final String zoneId, final String subjectId) {
+        setEntityIfNotExists(zoneId, subjectId, AbstractPolicyEvaluationCache::subjectKey);
+    }
+
+    @Override
+    public void resetForSubjectsByIds(final String zoneId, final Set<String> subjectIds) {
+        Map<String, String> map = new HashMap<>();
+        for (String subjectId : subjectIds) {
+            createMutliSetEntityMap(zoneId, map, subjectId, EntityType.SUBJECT,
+                    AbstractPolicyEvaluationCache::subjectKey);
+        }
+        multiSet(map);
+    }
+
+    @Override
+    public void resetForSubjects(final String zoneId, final List<SubjectEntity> subjectEntities) {
+        Map<String, String> map = new HashMap<>();
+        for (SubjectEntity subjectEntity : subjectEntities) {
+            createMutliSetEntityMap(zoneId, map, subjectEntity.getSubjectIdentifier(), EntityType.SUBJECT,
+                    AbstractPolicyEvaluationCache::subjectKey);
+        }
+        multiSet(map);
+    }
+
+    private void createMutliSetEntityMap(final String zoneId, final Map<String, String> map, final String subjectId,
+            final EntityType entityType, final BiFunction<String, String, String> getKey) {
+        String key = getKey.apply(zoneId, subjectId);
+        String timestamp = timestampUTC();
+        logSetEntityTimestampsDebugMessage(key, timestamp, subjectId, entityType);
+        map.put(key, timestamp);
+    }
+
+    private boolean isCachedRequestInvalid(final List<String> attributeInvalidationTimeStamps,
+            final List<String> policyInvalidationTimeStamps, final DateTime policyEvalTimestampUTC) {
+        if (haveEntitiesChanged(policyInvalidationTimeStamps, policyEvalTimestampUTC)) {
+            return true;
+        }
+
+        if (this.connectorService.isResourceAttributeConnectorConfigured() || this.connectorService
+                .isSubjectAttributeConnectorConfigured()) {
+            return haveConnectorCacheIntervalsLapsed(this.connectorService, policyEvalTimestampUTC);
+        } else {
+            return haveEntitiesChanged(attributeInvalidationTimeStamps, policyEvalTimestampUTC);
+        }
+    }
+
+    /**
+     * This method checks to see if any objects related to the policy evaluation have been changed since the Policy
+     * Evaluation Result was cached.
+     *
+     * @param values                 List of values which contain subject, policy sets and resolved resource URI's.
+     * @param policyEvalTimestampUTC The timestamp to compare against.
+     * @return true or false depending on whether any of the objects in values has a timestamp after
+     * policyEvalTimestampUTC.
+     */
+    boolean haveEntitiesChanged(final List<String> values, final DateTime policyEvalTimestampUTC) {
+        for (String value : values) {
+            if (null == value) {
+                return true;
+            }
+            DateTime invalidationTimestampUTC = timestampToDateUTC(value);
+            if (invalidationTimestampUTC.isAfter(policyEvalTimestampUTC)) {
+                LOGGER.debug("Privilege service attributes have timestamp '{}' which is after "
+                        + "policy evaluation timestamp '{}'", invalidationTimestampUTC, policyEvalTimestampUTC);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean haveConnectorCacheIntervalsLapsed(final AttributeConnectorService localConnectorService,
+            final DateTime policyEvalTimestampUTC) {
+        DateTime nowUTC = currentDateUTC();
+
+        int decisionAgeMinutes = Minutes.minutesBetween(policyEvalTimestampUTC, nowUTC).getMinutes();
+
+        boolean hasResourceConnectorIntervalLapsed = localConnectorService.isResourceAttributeConnectorConfigured()
+                && decisionAgeMinutes >= localConnectorService.getResourceAttributeConnector()
+                .getMaxCachedIntervalMinutes();
+
+        boolean hasSubjectConnectorIntervalLapsed = localConnectorService.isSubjectAttributeConnectorConfigured()
+                && decisionAgeMinutes >= localConnectorService.getSubjectAttributeConnector()
+                .getMaxCachedIntervalMinutes();
+
+        return hasResourceConnectorIntervalLapsed || hasSubjectConnectorIntervalLapsed;
+    }
+
+    static String policySetKey(final String zoneId, final String policySetId) {
+        return zoneId + ":set-id:" + Integer.toHexString(policySetId.hashCode());
+    }
+
+    static String resourceKey(final String zoneId, final String resourceId) {
+        return zoneId + ":res-id:" + Integer.toHexString(resourceId.hashCode());
+    }
+
+    static String subjectKey(final String zoneId, final String subjectId) {
+        return zoneId + ":sub-id:" + Integer.toHexString(subjectId.hashCode());
+    }
+
+    static boolean isPolicyEvalResultKey(final String key) {
+        return key.matches("^[^:]*:[^:]*:[^:]*:[^:]*$");
+    }
+
+    static boolean isPolicySetChangedKey(final String key) {
+        return key.matches("^[^:]*:set-id:[^:]*$");
+    }
+
+    static boolean isResourceChangedKey(final String key) {
+        return key.matches("^[^:]*:res-id:[^:]*$");
+    }
+
+    static boolean isSubjectChangedKey(final String key) {
+        return key.matches("^[^:]*:sub-id:[^:]*$");
+    }
+
+    private static DateTime currentDateUTC() {
+        return new DateTime().withZone(DateTimeZone.UTC);
+    }
+
+    private static String timestampUTC() {
+        try {
+            return OBJECT_MAPPER.writeValueAsString(currentDateUTC().getMillis());
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to write timestamp as JSON.", e);
+        }
+    }
+
+    private static DateTime timestampToDateUTC(final long timestamp) {
+        return new DateTime(timestamp).withZone(DateTimeZone.UTC);
+    }
+
+    private static DateTime timestampToDateUTC(final String timestamp) {
+        return new DateTime(Long.valueOf(timestamp)).withZone(DateTimeZone.UTC);
+    }
+
+    abstract void delete(String key);
+
+    abstract void delete(Collection<String> keys);
+
+    abstract void flushAll();
+
+    abstract Set<String> keys(String key);
+
+    abstract List<String> multiGet(List<String> keys);
+
+    abstract void multiSet(Map<String, String> map);
+
+    abstract void set(String key, String value);
+
+    abstract void setIfNotExists(String key, String value);
+}
diff --git a/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/InMemoryPolicyEvaluationCache.java b/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/InMemoryPolicyEvaluationCache.java
new file mode 100644
index 0000000..1138881
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/InMemoryPolicyEvaluationCache.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.policy.evaluation.cache;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ConcurrentReferenceHashMap;
+
+@Component
+@Profile({ "simple-cache" })
+public class InMemoryPolicyEvaluationCache extends AbstractPolicyEvaluationCache {
+
+    private final Map<String, String> evalCache = new ConcurrentReferenceHashMap<String, String>();
+
+    @Override
+    void delete(final String key) {
+        this.evalCache.remove(key);
+    }
+
+    @Override
+    void delete(final Collection<String> keys) {
+        for (String key : keys) {
+            delete(key);
+        }
+    }
+
+    @Override
+    void flushAll() {
+        this.evalCache.clear();
+    }
+
+    @Override
+    Set<String> keys(final String key) {
+        return this.evalCache.keySet();
+    }
+
+    @Override
+    List<String> multiGet(final List<String> keys) {
+        List<String> results = new ArrayList<>();
+        for (String key : keys) {
+            results.add(this.evalCache.get(key));
+        }
+        return results;
+    }
+
+    @Override
+    void multiSet(final Map<String, String> map) {
+        for (Entry<String, String> entry : map.entrySet()) {
+            set(entry.getKey(), entry.getValue());
+        }
+    }
+
+    @Override
+    void set(final String key, final String value) {
+        if (isPolicySetChangedKey(key) || isResourceChangedKey(key) || isSubjectChangedKey(key)
+                || isPolicyEvalResultKey(key)) {
+            this.evalCache.put(key, value);
+        } else {
+            throw new IllegalArgumentException("Unsupported key format.");
+        }
+    }
+
+    @Override
+    void setIfNotExists(final String key, final String value) {
+        if (!this.evalCache.containsKey(key)) {
+            this.evalCache.put(key, value);
+        }
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/NonCachingPolicyEvaluationCache.java b/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/NonCachingPolicyEvaluationCache.java
new file mode 100644
index 0000000..2eb14eb
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/NonCachingPolicyEvaluationCache.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.policy.evaluation.cache;
+
+import java.util.List;
+import java.util.Set;
+
+import com.ge.predix.acs.privilege.management.dao.ResourceEntity;
+import com.ge.predix.acs.privilege.management.dao.SubjectEntity;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+
+public class NonCachingPolicyEvaluationCache implements PolicyEvaluationCache {
+
+    @Override
+    public PolicyEvaluationResult get(final PolicyEvaluationRequestCacheKey key) {
+        return null;
+    }
+
+    @Override
+    public void set(final PolicyEvaluationRequestCacheKey key, final PolicyEvaluationResult value) {
+        // Purposely empty since it's required by the PolicyEvaluationCache interface but unused here
+    }
+
+    @Override
+    public void reset() {
+        // Purposely empty since it's required by the PolicyEvaluationCache interface but unused here
+    }
+
+    @Override
+    public void reset(final PolicyEvaluationRequestCacheKey key) {
+        // Purposely empty since it's required by the PolicyEvaluationCache interface but unused here
+    }
+
+    @Override
+    public void resetForPolicySet(final String zoneId, final String policySetId) {
+        // Purposely empty since it's required by the PolicyEvaluationCache interface but unused here
+    }
+
+    @Override
+    public void resetForResource(final String zoneId, final String resourceId) {
+        // Purposely empty since it's required by the PolicyEvaluationCache interface but unused here
+    }
+
+    @Override
+    public void resetForResources(final String zoneId, final List<ResourceEntity> entities) {
+        // Purposely empty since it's required by the PolicyEvaluationCache interface but unused here
+    }
+
+    @Override
+    public void resetForResourcesByIds(final String zoneId, final Set<String> resourceIds) {
+        // Purposely empty since it's required by the PolicyEvaluationCache interface but unused here
+    }
+
+    @Override
+    public void resetForSubject(final String zoneId, final String subjectId) {
+        // Purposely empty since it's required by the PolicyEvaluationCache interface but unused here
+    }
+
+    @Override
+    public void resetForSubjects(final String zoneId, final List<SubjectEntity> subjectEntities) {
+        // Purposely empty since it's required by the PolicyEvaluationCache interface but unused here
+    }
+
+    @Override
+    public void resetForSubjectsByIds(final String zoneId, final Set<String> subjectIds) {
+        // Purposely empty since it's required by the PolicyEvaluationCache interface but unused here
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/PolicyEvaluationCache.java b/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/PolicyEvaluationCache.java
new file mode 100644
index 0000000..644a962
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/PolicyEvaluationCache.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.policy.evaluation.cache;
+
+import java.util.List;
+import java.util.Set;
+
+import com.ge.predix.acs.privilege.management.dao.ResourceEntity;
+import com.ge.predix.acs.privilege.management.dao.SubjectEntity;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+
+public interface PolicyEvaluationCache {
+    String DECISION = "decision";
+
+
+    PolicyEvaluationResult get(PolicyEvaluationRequestCacheKey key);
+
+    void set(PolicyEvaluationRequestCacheKey key, PolicyEvaluationResult value);
+
+    void reset();
+
+    void reset(PolicyEvaluationRequestCacheKey key);
+
+    void resetForPolicySet(String zoneId, String policySetId);
+
+    void resetForResource(String zoneId, String resourceId);
+
+    void resetForResources(String zoneId, List<ResourceEntity> entities);
+
+    void resetForResourcesByIds(String zoneId, Set<String> resourceIds);
+
+    void resetForSubject(String zoneId, String subjectId);
+
+    void resetForSubjects(String zoneId, List<SubjectEntity> subjectEntities);
+
+    void resetForSubjectsByIds(String zoneId, Set<String> subjectIds);
+}
diff --git a/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/PolicyEvaluationRequestCacheKey.java b/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/PolicyEvaluationRequestCacheKey.java
new file mode 100644
index 0000000..8dfce58
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/PolicyEvaluationRequestCacheKey.java
@@ -0,0 +1,174 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.policy.evaluation.cache;
+
+import java.util.LinkedHashSet;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+
+public class PolicyEvaluationRequestCacheKey {
+
+    private final PolicyEvaluationRequestV1 request;
+    private final LinkedHashSet<String> policySetIds;
+    private final String resourceId;
+    private final String subjectId;
+    private final String zoneId;
+
+    public PolicyEvaluationRequestCacheKey(final PolicyEvaluationRequestV1 request,
+            final LinkedHashSet<String> policySetIds, final String zoneId) {
+        this.request = request;
+        this.policySetIds = policySetIds;
+        this.resourceId = request.getResourceIdentifier();
+        this.subjectId = request.getSubjectIdentifier();
+        this.zoneId = zoneId;
+    }
+
+    public PolicyEvaluationRequestCacheKey(final LinkedHashSet<String> policySetIds, final String resourceId,
+            final String subjectId, final String zoneId) {
+        this.request = null;
+        this.policySetIds = policySetIds;
+        this.resourceId = resourceId;
+        this.subjectId = subjectId;
+        this.zoneId = zoneId;
+    }
+
+    public String toDecisionKey() {
+        StringBuilder keyBuilder = new StringBuilder();
+        if (null == this.zoneId) {
+            keyBuilder.append("*:");
+        } else {
+            keyBuilder.append(this.zoneId);
+            keyBuilder.append(":");
+        }
+        if (null == this.subjectId) {
+            keyBuilder.append("*:");
+        } else {
+            keyBuilder.append(Integer.toHexString(this.subjectId.hashCode()));
+            keyBuilder.append(":");
+        }
+        if (null == this.resourceId) {
+            keyBuilder.append("*:");
+        } else {
+            keyBuilder.append(Integer.toHexString(this.resourceId.hashCode()));
+            keyBuilder.append(":");
+        }
+        if (null == this.request) {
+            keyBuilder.append("*");
+        } else {
+            keyBuilder.append(Integer.toHexString(this.request.hashCode()));
+        }
+        return keyBuilder.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(this.request).append(this.zoneId).toHashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof PolicyEvaluationRequestCacheKey) {
+            final PolicyEvaluationRequestCacheKey other = (PolicyEvaluationRequestCacheKey) obj;
+            return new EqualsBuilder().append(this.request, other.request).append(this.zoneId, other.zoneId).isEquals();
+        }
+        return false;
+    }
+
+    public PolicyEvaluationRequestV1 getRequest() {
+        return this.request;
+    }
+
+    public LinkedHashSet<String> getPolicySetIds() {
+        return this.policySetIds;
+    }
+
+    public String getResourceId() {
+        return this.resourceId;
+    }
+
+    public String getSubjectId() {
+        return this.subjectId;
+    }
+
+    public String getZoneId() {
+        return this.zoneId;
+    }
+
+    public static class Builder {
+        private PolicyEvaluationRequestV1 builderRequest;
+        private LinkedHashSet<String> builderPolicySetIds = new LinkedHashSet<>();
+        private String builderResourceId;
+        private String builderSubjectId;
+        private String builderZoneId;
+
+        public Builder request(final PolicyEvaluationRequestV1 request) {
+            this.builderRequest = request;
+            return this;
+        }
+
+        public Builder policySetIds(final LinkedHashSet<String> policySets) {
+            if (null != this.builderRequest && !this.builderRequest.getPolicySetsEvaluationOrder().isEmpty()) {
+                throw new IllegalStateException(
+                        "Cannot set policy sets evaluation order if set in the policy request.");
+            }
+            if (null != policySets) {
+                this.builderPolicySetIds = policySets;
+            }
+            return this;
+        }
+
+        public Builder resourceId(final String resourceId) {
+            if (null != this.builderRequest) {
+                throw new IllegalStateException("Cannot set resource id if policy request is set.");
+            }
+            this.builderResourceId = resourceId;
+            return this;
+        }
+
+        public Builder subjectId(final String subjectId) {
+            if (null != this.builderRequest) {
+                throw new IllegalStateException("Cannot set subject id if policy request is set.");
+            }
+            this.builderSubjectId = subjectId;
+            return this;
+        }
+
+        public Builder zoneId(final String zoneId) {
+            this.builderZoneId = zoneId;
+            return this;
+        }
+
+        public PolicyEvaluationRequestCacheKey build() {
+            if (null != this.builderRequest) {
+                if (this.builderRequest.getPolicySetsEvaluationOrder().isEmpty()) {
+                    return new PolicyEvaluationRequestCacheKey(this.builderRequest, this.builderPolicySetIds,
+                            this.builderZoneId);
+                } else {
+                    return new PolicyEvaluationRequestCacheKey(this.builderRequest,
+                            this.builderRequest.getPolicySetsEvaluationOrder(), this.builderZoneId);
+                }
+            }
+            return new PolicyEvaluationRequestCacheKey(this.builderPolicySetIds, this.builderResourceId,
+                    this.builderSubjectId, this.builderZoneId);
+        }
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/RedisPolicyEvaluationCache.java b/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/RedisPolicyEvaluationCache.java
new file mode 100644
index 0000000..65a0969
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/policy/evaluation/cache/RedisPolicyEvaluationCache.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.policy.evaluation.cache;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.redis.RedisConnectionFailureException;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+@Profile({ "cloud-redis", "redis" })
+public class RedisPolicyEvaluationCache extends AbstractPolicyEvaluationCache implements InitializingBean {
+    private static final Logger LOGGER = LoggerFactory.getLogger(RedisPolicyEvaluationCache.class);
+
+    @Value("${CACHED_EVAL_TTL_SECONDS:600}")
+    private long cachedEvalTimeToLiveSeconds;
+
+    @Autowired
+    private RedisTemplate<String, String> decisionCacheRedisTemplate;
+
+    @Override
+    public void afterPropertiesSet() {
+        LOGGER.info("Starting Redis policy evaluation cache.");
+        try {
+            String pingResult = this.decisionCacheRedisTemplate.getConnectionFactory().getConnection().ping();
+            LOGGER.info("Redis server ping: {}", pingResult);
+        } catch (RedisConnectionFailureException ex) {
+            LOGGER.error("Redis server ping failed.", ex);
+        }
+    }
+
+    @Override
+    void delete(final String key) {
+        this.decisionCacheRedisTemplate.delete(key);
+    }
+
+    @Override
+    void delete(final Collection<String> keys) {
+        this.decisionCacheRedisTemplate.delete(keys);
+    }
+
+    @Override
+    void flushAll() {
+        this.decisionCacheRedisTemplate.getConnectionFactory().getConnection().flushAll();
+    }
+
+    @Override
+    Set<String> keys(final String key) {
+        return this.decisionCacheRedisTemplate.keys(key);
+    }
+
+    @Override
+    List<String> multiGet(final List<String> keys) {
+        return this.decisionCacheRedisTemplate.opsForValue().multiGet(keys);
+    }
+
+    void multiSet(final Map<String, String> map) {
+        this.decisionCacheRedisTemplate.opsForValue().multiSet(map);
+    }
+
+    @Override
+    void set(final String key, final String value) {
+        if (isPolicyEvalResultKey(key)) {
+            this.decisionCacheRedisTemplate.opsForValue().set(key, value, this.cachedEvalTimeToLiveSeconds,
+                    TimeUnit.SECONDS);
+        } else {
+            this.decisionCacheRedisTemplate.opsForValue().set(key, value);
+        }
+    }
+
+    public void setCachedEvalTimeToLiveSeconds(final long cachedEvalTimeToLiveSeconds) {
+        this.cachedEvalTimeToLiveSeconds = cachedEvalTimeToLiveSeconds;
+    }
+
+    void setIfNotExists(final String key, final String value) {
+        this.decisionCacheRedisTemplate.boundValueOps(key).setIfAbsent(value);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/EntityManagementControllerAdvice.java b/service/src/main/java/com/ge/predix/acs/privilege/management/EntityManagementControllerAdvice.java
new file mode 100644
index 0000000..8bd859f
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/EntityManagementControllerAdvice.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+import org.json.simple.JSONObject;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+
+@ControllerAdvice(basePackageClasses = { SubjectPrivilegeManagementController.class,
+        ResourcePrivilegeManagementController.class })
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class EntityManagementControllerAdvice {
+
+    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
+    public ResponseEntity<JSONObject> handleIncorrectParamType() {
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST).contentType(MediaType.APPLICATION_JSON)
+                .body(new JSONObject() {{
+                    put(PrivilegeManagementUtility.INCORRECT_PARAMETER_TYPE_ERROR,
+                            HttpStatus.BAD_REQUEST.getReasonPhrase());
+                    put(PrivilegeManagementUtility.INCORRECT_PARAMETER_TYPE_MESSAGE,
+                            "Request Parameter " + PrivilegeManagementUtility.INHERITED_ATTRIBUTES_REQUEST_PARAMETER
+                                    + " must be a boolean value");
+                }});
+    }
+}
\ No newline at end of file
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/PrivilegeConverter.java b/service/src/main/java/com/ge/predix/acs/privilege/management/PrivilegeConverter.java
new file mode 100644
index 0000000..bd81f22
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/PrivilegeConverter.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+import java.util.Set;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.privilege.management.dao.ResourceEntity;
+import com.ge.predix.acs.privilege.management.dao.SubjectEntity;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.utils.JsonUtils;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+public class PrivilegeConverter {
+    private final JsonUtils jsonUtils = new JsonUtils();
+
+    public ResourceEntity toResourceEntity(final ZoneEntity zone, final BaseResource resource) {
+        if (resource == null) {
+            return null;
+        }
+
+        ResourceEntity resourceEntity = new ResourceEntity(zone, resource.getResourceIdentifier());
+        resourceEntity.setAttributes(resource.getAttributes());
+        String attributesAsJson = this.jsonUtils.serialize(resource.getAttributes());
+        resourceEntity.setAttributesAsJson(attributesAsJson);
+        resourceEntity.setParents(resource.getParents());
+        return resourceEntity;
+    }
+
+    @SuppressWarnings("unchecked")
+    public BaseResource toResource(final ResourceEntity resourceEntity) {
+        if (resourceEntity == null) {
+            return null;
+        }
+
+        BaseResource resource = new BaseResource(resourceEntity.getResourceIdentifier());
+
+        Set<Attribute> deserialize = this.jsonUtils.deserialize(resourceEntity.getAttributesAsJson(), Set.class,
+                Attribute.class);
+        resource.setAttributes(deserialize);
+        resource.setParents(resourceEntity.getParents());
+        return resource;
+    }
+
+    public SubjectEntity toSubjectEntity(final ZoneEntity zone, final BaseSubject subject) {
+        if (subject == null) {
+            return null;
+        }
+
+        SubjectEntity subjectEntity = new SubjectEntity(zone, subject.getSubjectIdentifier());
+        subjectEntity.setAttributes(subject.getAttributes());
+        String attributesAsJson = this.jsonUtils.serialize(subject.getAttributes());
+        subjectEntity.setAttributesAsJson(attributesAsJson);
+        subjectEntity.setParents(subject.getParents());
+        return subjectEntity;
+    }
+
+    @SuppressWarnings("unchecked")
+    public BaseSubject toSubject(final SubjectEntity subjectEntity) {
+        if (subjectEntity == null) {
+            return null;
+        }
+
+        BaseSubject subject = new BaseSubject(subjectEntity.getSubjectIdentifier());
+
+        Set<Attribute> deserialize = this.jsonUtils.deserialize(subjectEntity.getAttributesAsJson(), Set.class,
+                Attribute.class);
+        subject.setAttributes(deserialize);
+        subject.setParents(subjectEntity.getParents());
+        return subject;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/PrivilegeManagementException.java b/service/src/main/java/com/ge/predix/acs/privilege/management/PrivilegeManagementException.java
new file mode 100644
index 0000000..2223158
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/PrivilegeManagementException.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings("javadoc")
+public class PrivilegeManagementException extends RuntimeException {
+
+    private static final long serialVersionUID = 3366459522709591127L;
+
+    public PrivilegeManagementException() {
+        super();
+    }
+
+    /**
+     * @param message
+     * @param cause
+     * @param enableSuppression
+     * @param writableStackTrace
+     */
+    public PrivilegeManagementException(final String message, final Throwable cause, final boolean enableSuppression,
+            final boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+    /**
+     * @param message
+     * @param cause
+     */
+    public PrivilegeManagementException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * @param message
+     */
+    public PrivilegeManagementException(final String message) {
+        super(message);
+    }
+
+    /**
+     * @param cause
+     */
+    public PrivilegeManagementException(final Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/PrivilegeManagementService.java b/service/src/main/java/com/ge/predix/acs/privilege/management/PrivilegeManagementService.java
new file mode 100644
index 0000000..337d997
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/PrivilegeManagementService.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * CRUD interface operations for privilege management.
+ *
+ * @author acs-engineers@ge.com
+ */
+public interface PrivilegeManagementService {
+
+    void appendResources(List<BaseResource> resources);
+
+    List<BaseResource> getResources();
+
+    BaseResource getByResourceIdentifier(String resourceIdentifier);
+
+    BaseResource getByResourceIdentifierWithInheritedAttributes(String resourceIdentifier);
+
+    boolean upsertResource(BaseResource resource);
+
+    boolean deleteResource(String resourceIdentifier);
+
+    void appendSubjects(List<BaseSubject> subjects);
+
+    List<BaseSubject> getSubjects();
+
+    BaseSubject getBySubjectIdentifier(String subjectIdentifier);
+
+    BaseSubject getBySubjectIdentifierWithInheritedAttributes(String subjectIdentifier);
+
+    BaseSubject getBySubjectIdentifierAndScopes(String subjectIdentifier, Set<Attribute> scopes);
+
+    boolean upsertSubject(BaseSubject subject);
+
+    boolean deleteSubject(String subjectIdentifier);
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/PrivilegeManagementServiceImpl.java b/service/src/main/java/com/ge/predix/acs/privilege/management/PrivilegeManagementServiceImpl.java
new file mode 100644
index 0000000..e045685
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/PrivilegeManagementServiceImpl.java
@@ -0,0 +1,431 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.policy.evaluation.cache.PolicyEvaluationCache;
+import com.ge.predix.acs.privilege.management.dao.ResourceEntity;
+import com.ge.predix.acs.privilege.management.dao.ResourceRepositoryProxy;
+import com.ge.predix.acs.privilege.management.dao.SubjectEntity;
+import com.ge.predix.acs.privilege.management.dao.SubjectRepositoryProxy;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.ge.predix.acs.zone.resolver.ZoneResolver;
+
+/**
+ * The implementation of privilege management.
+ *
+ * @author acs-engineers@ge.com
+ */
+@Component
+@SuppressWarnings("nls")
+public class PrivilegeManagementServiceImpl implements PrivilegeManagementService {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(PrivilegeManagementServiceImpl.class);
+
+    @Autowired
+    private PolicyEvaluationCache cache;
+
+    @Autowired
+    private SubjectRepositoryProxy subjectRepository;
+
+    @Autowired
+    private ResourceRepositoryProxy resourceRepository;
+
+    @Autowired
+    private ZoneResolver zoneResolver;
+
+    private final PrivilegeConverter privilegeConverter = new PrivilegeConverter();
+
+    @Override
+    public void appendResources(final List<BaseResource> resources) {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+
+        if (CollectionUtils.isEmpty(resources)) {
+            throw new PrivilegeManagementException("Null Or Empty list of resources");
+        }
+        // fail fast if identifiers are missing or null
+        validResourcesOrFail(resources);
+
+        List<ResourceEntity> entities = new ArrayList<>();
+        appendResourcesInTransaction(resources, zone, entities);
+    }
+
+    @Transactional
+    private void appendResourcesInTransaction(final List<BaseResource> resources, final ZoneEntity zone,
+            final List<ResourceEntity> entities) {
+        for (BaseResource resource : resources) {
+            ResourceEntity persistedResource = this.resourceRepository
+                    .getByZoneAndResourceIdentifier(zone, resource.getResourceIdentifier());
+
+            ResourceEntity entity = this.privilegeConverter.toResourceEntity(zone, resource);
+            if (persistedResource != null) {
+                LOGGER.debug("Found an existing resource with resourceIdentifier = {}, zone = {}. Upserting the same.",
+                        resource.getResourceIdentifier(), zone);
+                entity.setId(persistedResource.getId());
+            }
+            entities.add(entity);
+        }
+
+        try {
+            this.cache.resetForResources(zone.getName(), entities);
+            this.resourceRepository.save(entities);
+        } catch (Exception e) {
+
+            String message = String.format("Unable to persist Resource(s) for zone = %s. Transaction was rolled back.",
+                    zone.toString());
+            if (constrainViolation(e)) {
+                message = String.format("Duplicate Resource(s) identified by zone = %s.", zone.toString());
+            }
+            LOGGER.error(message, e);
+            throw new PrivilegeManagementException(message, e);
+        }
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public List<BaseResource> getResources() {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+
+        List<BaseResource> resources = new ArrayList<>();
+        List<ResourceEntity> resourceEntities = this.resourceRepository.findByZone(zone);
+
+        if (!CollectionUtils.isEmpty(resourceEntities)) {
+            for (ResourceEntity resourceEntity : resourceEntities) {
+                resources.add(this.privilegeConverter.toResource(resourceEntity));
+            }
+        }
+        return resources;
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public BaseResource getByResourceIdentifier(final String resourceIdentifier) {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+        ResourceEntity resourceEntity = this.resourceRepository
+                .getByZoneAndResourceIdentifier(zone, resourceIdentifier);
+        return createResource(resourceIdentifier, zone, resourceEntity);
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public BaseResource getByResourceIdentifierWithInheritedAttributes(final String resourceIdentifier) {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+        ResourceEntity resourceEntity = this.resourceRepository
+                .getResourceWithInheritedAttributes(zone, resourceIdentifier);
+        return createResource(resourceIdentifier, zone, resourceEntity);
+    }
+
+    private BaseResource createResource(final String resourceIdentifier, final ZoneEntity zone,
+            final ResourceEntity resourceEntity) {
+        BaseResource resource = this.privilegeConverter.toResource(resourceEntity);
+        if (resource == null) {
+            LOGGER.debug("Unable to find the resource for resourceIdentifier = {} , zone = {}.", resourceIdentifier,
+                    zone);
+        }
+        return resource;
+    }
+
+    @Override
+    public boolean upsertResource(final BaseResource resource) {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+        validateResourceOrFail(resource);
+
+        ResourceEntity updatedResource = this.privilegeConverter.toResourceEntity(zone, resource);
+
+        ResourceEntity persistedResource = upsertResourceInTransaction(resource, zone, updatedResource);
+
+        // true if non previous persisted entity was there.
+        return persistedResource == null;
+    }
+
+    @Transactional
+    private ResourceEntity upsertResourceInTransaction(final BaseResource resource, final ZoneEntity zone,
+            final ResourceEntity updatedResource) {
+        ResourceEntity persistedResource = this.resourceRepository
+                .getByZoneAndResourceIdentifier(zone, resource.getResourceIdentifier());
+
+        if (persistedResource != null) {
+            LOGGER.debug("Found an existing resource with resourceIdentifier = {}, " + "zone = {}. Upserting the same.",
+                    resource.getResourceIdentifier(), zone);
+            updatedResource.setId(persistedResource.getId());
+            this.cache.resetForResourcesByIds(zone.getName(),
+                    this.resourceRepository.getResourceEntityAndDescendantsIds(updatedResource));
+        } else {
+            LOGGER.debug(
+                    "Found no existing resource. Creating a new one with the resourceIdentifier = {}," + " zone = {}.",
+                    resource.getResourceIdentifier(), zone);
+            this.cache.resetForResourcesByIds(zone.getName(),
+                    Collections.singleton(updatedResource.getResourceIdentifier()));
+        }
+
+        try {
+            this.resourceRepository.save(updatedResource);
+        } catch (Exception e) {
+            String message = String
+                    .format("Unable to persist Resource identified by resourceIdentifier = %s , zone = %s.",
+                            resource.getResourceIdentifier(), zone.toString());
+            if (constrainViolation(e)) {
+                message = String.format("Duplicate Resource identified by resourceIdentifier = %s, zone = %s.",
+                        resource.getResourceIdentifier(), zone.toString());
+            }
+            LOGGER.error(message, e);
+            throw new PrivilegeManagementException(message, e);
+        }
+        return persistedResource;
+    }
+
+    /**
+     * @param e
+     * @return
+     */
+    private boolean constrainViolation(final Exception e) {
+        Class<? extends Exception> exceptionType = e.getClass();
+        return DataIntegrityViolationException.class.isAssignableFrom(exceptionType);
+    }
+
+    @Override
+    @Transactional
+    public boolean deleteResource(final String resourceIdentifier) {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+        boolean deleted = false;
+        ResourceEntity resourceEntity = this.resourceRepository
+                .getByZoneAndResourceIdentifier(zone, resourceIdentifier);
+        if (resourceEntity != null) {
+            this.cache.resetForResourcesByIds(zone.getName(),
+                    this.resourceRepository.getResourceEntityAndDescendantsIds(resourceEntity));
+            this.resourceRepository.delete(resourceEntity.getId());
+            deleted = true;
+            LOGGER.info("Deleted resource with resourceId = {}, zone = {}.", resourceIdentifier, zone);
+        }
+        return deleted;
+    }
+
+    @Override
+    public void appendSubjects(final List<BaseSubject> subjects) {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+        if (CollectionUtils.isEmpty(subjects)) {
+            throw new PrivilegeManagementException("Null Or Empty list of subjects.");
+        }
+        // fail fast if identifiers are missing or null
+        validSubjectUrisOrFail(subjects);
+
+        List<SubjectEntity> subjectEntities = new ArrayList<>();
+
+        appendSubjectsInTransaction(subjects, zone, subjectEntities);
+    }
+
+    @Transactional
+    private void appendSubjectsInTransaction(final List<BaseSubject> subjects, final ZoneEntity zone,
+            final List<SubjectEntity> subjectEntities) {
+        for (BaseSubject subject : subjects) {
+            SubjectEntity persistedSubject = this.subjectRepository
+                    .getByZoneAndSubjectIdentifier(zone, subject.getSubjectIdentifier());
+            SubjectEntity entity = this.privilegeConverter.toSubjectEntity(zone, subject);
+            if (persistedSubject != null) {
+                entity.setId(persistedSubject.getId());
+            }
+            subjectEntities.add(entity);
+        }
+        try {
+            this.cache.resetForSubjects(zone.getName(), subjectEntities);
+            this.subjectRepository.save(subjectEntities);
+        } catch (Exception e) {
+            String message = String.format("Unable to persist Subject(s) for zone = %s. Transaction was rolled back.",
+                    zone.toString());
+            if (constrainViolation(e)) {
+                message = String.format("Duplicate Subject(s) identified by zone = %s", zone.toString());
+            }
+            LOGGER.error(message, e);
+            throw new PrivilegeManagementException(message, e);
+        }
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public List<BaseSubject> getSubjects() {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+        List<BaseSubject> subjects = new ArrayList<>();
+
+        List<SubjectEntity> subjectEntities = this.subjectRepository.findByZone(zone);
+        if (!CollectionUtils.isEmpty(subjectEntities)) {
+            for (SubjectEntity subjectEntity : subjectEntities) {
+                subjects.add(this.privilegeConverter.toSubject(subjectEntity));
+            }
+        }
+        return subjects;
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public BaseSubject getBySubjectIdentifier(final String subjectIdentifier) {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+        SubjectEntity subjectEntity = this.subjectRepository.getByZoneAndSubjectIdentifier(zone, subjectIdentifier);
+        return createSubject(subjectIdentifier, zone, subjectEntity);
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public BaseSubject getBySubjectIdentifierWithInheritedAttributes(final String subjectIdentifier) {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+        SubjectEntity subjectEntity = this.subjectRepository.getSubjectWithInheritedAttributes(zone, subjectIdentifier);
+        return createSubject(subjectIdentifier, zone, subjectEntity);
+    }
+
+    private BaseSubject createSubject(final String subjectIdentifier, final ZoneEntity zone,
+            final SubjectEntity subjectEntity) {
+        BaseSubject subject = this.privilegeConverter.toSubject(subjectEntity);
+        if (subject == null) {
+            LOGGER.debug("Unable to find the subject for subjectIdentifier = {}, zone = {}.", subjectIdentifier, zone);
+        }
+        return subject;
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public BaseSubject getBySubjectIdentifierAndScopes(final String subjectIdentifier, final Set<Attribute> scopes) {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+        SubjectEntity subjectEntity = this.subjectRepository
+                .getSubjectWithInheritedAttributesForScopes(zone, subjectIdentifier, scopes);
+        return createSubject(subjectIdentifier, zone, subjectEntity);
+    }
+
+    @Override
+    public boolean upsertSubject(final BaseSubject subject) {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+        validateSubjectOrFail(subject);
+
+        SubjectEntity updatedSubject = this.privilegeConverter.toSubjectEntity(zone, subject);
+
+        SubjectEntity persistedSubject = upsertSubjectInTransaction(subject, zone, updatedSubject);
+
+        // Return false if the persistedSubject is null, which means we updated an existing subject.
+        if (null != persistedSubject) {
+            return false;
+        }
+        return true;
+    }
+
+    @Transactional
+    private SubjectEntity upsertSubjectInTransaction(final BaseSubject subject, final ZoneEntity zone,
+            final SubjectEntity updatedSubject) {
+        SubjectEntity persistedSubject = this.subjectRepository
+                .getByZoneAndSubjectIdentifier(zone, subject.getSubjectIdentifier());
+
+        if (persistedSubject != null) {
+            updatedSubject.setId(persistedSubject.getId());
+            this.cache.resetForSubjectsByIds(zone.getName(),
+                    this.subjectRepository.getSubjectEntityAndDescendantsIds(updatedSubject));
+        } else {
+            this.cache.resetForSubjectsByIds(zone.getName(),
+                    Collections.singleton(updatedSubject.getSubjectIdentifier()));
+        }
+
+        try {
+            this.subjectRepository.save(updatedSubject);
+        } catch (Exception e) {
+            String message = String
+                    .format("Unable to persist Subject identified by subjectIidentifier = %s , zone = %s.",
+                            subject.getSubjectIdentifier(), zone.toString());
+            if (constrainViolation(e)) {
+                message = String.format("Duplicate Subject identified by subjectIidentifier = %s, zone = %s.",
+                        subject.getSubjectIdentifier(), zone.toString());
+            }
+            LOGGER.error(message, e);
+            throw new PrivilegeManagementException(message, e);
+        }
+        return persistedSubject;
+    }
+
+    @Override
+    @Transactional
+    public boolean deleteSubject(final String subjectIdentifier) {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+
+        boolean deleted = false;
+
+        SubjectEntity subjectEntity = this.subjectRepository.getByZoneAndSubjectIdentifier(zone, subjectIdentifier);
+        if (subjectEntity != null) {
+            this.cache.resetForSubjectsByIds(zone.getName(),
+                    this.subjectRepository.getSubjectEntityAndDescendantsIds(subjectEntity));
+            this.subjectRepository.delete(subjectEntity.getId());
+            deleted = true;
+            LOGGER.info("Deleted subject with subjectIdentifier={}, zone = {}.", subjectIdentifier, zone);
+        }
+        return deleted;
+    }
+
+    private void validSubjectUrisOrFail(final List<BaseSubject> subjects) {
+        for (BaseSubject s : subjects) {
+            validateSubjectOrFail(s);
+        }
+    }
+
+    /**
+     * @param s
+     */
+    private void validateSubjectOrFail(final BaseSubject s) {
+        if (s == null) {
+            throw new PrivilegeManagementException("Subject is null.");
+        }
+
+        if (!s.isIdentifierValid()) {
+            throw new PrivilegeManagementException(
+                    String.format("Subject missing subjectIdentifier = %s this is mandatory for POST API",
+                            s.getSubjectIdentifier()));
+        }
+    }
+
+    private void validResourcesOrFail(final List<BaseResource> resources) {
+
+        for (BaseResource r : resources) {
+            validateResourceOrFail(r);
+        }
+    }
+
+    private void validateResourceOrFail(final BaseResource r) {
+        if (r == null) {
+            throw new PrivilegeManagementException("Resource is null.");
+        }
+
+        if (!r.isIdentifierValid()) {
+            throw new PrivilegeManagementException(
+                    String.format("Resource missing resourceIdentifier = %s ,this is mandatory for POST API",
+                            r.getResourceIdentifier()));
+        }
+    }
+
+    public void setCache(final PolicyEvaluationCache cache) {
+        this.cache = cache;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/PrivilegeManagementUtility.java b/service/src/main/java/com/ge/predix/acs/privilege/management/PrivilegeManagementUtility.java
new file mode 100644
index 0000000..82acc93
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/PrivilegeManagementUtility.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+public final class PrivilegeManagementUtility {
+
+    private PrivilegeManagementUtility() {
+        throw new AssertionError();
+    }
+
+    public static final String INHERITED_ATTRIBUTES_REQUEST_PARAMETER = "includeInheritedAttributes";
+    public static final String INCORRECT_PARAMETER_TYPE_ERROR = "error";
+    public static final String INCORRECT_PARAMETER_TYPE_MESSAGE = "message";
+}
\ No newline at end of file
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/ResourceHttpMethodsFilter.java b/service/src/main/java/com/ge/predix/acs/privilege/management/ResourceHttpMethodsFilter.java
new file mode 100644
index 0000000..1dd00d6
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/ResourceHttpMethodsFilter.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.security.AbstractHttpMethodsFilter;
+
+@Component
+public class ResourceHttpMethodsFilter extends AbstractHttpMethodsFilter {
+
+    private static final String UPDATE_RESOURCE_URI_REGEX = "\\A/v1/resource/[^/]+?/??\\Z";
+    private static final String CREATE_GET_RESOURCE_URI_REGEX = "\\A/v1/resource/??\\Z";
+
+    private static Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods() {
+        Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods = new LinkedHashMap<>();
+        uriPatternsAndAllowedHttpMethods.put(UPDATE_RESOURCE_URI_REGEX,
+                new HashSet<>(Arrays.asList(HttpMethod.PUT, HttpMethod.GET, HttpMethod.DELETE, HttpMethod.HEAD)));
+        uriPatternsAndAllowedHttpMethods.put(CREATE_GET_RESOURCE_URI_REGEX,
+                new HashSet<>(Arrays.asList(HttpMethod.POST, HttpMethod.GET, HttpMethod.HEAD)));
+        return uriPatternsAndAllowedHttpMethods;
+    }
+
+    public ResourceHttpMethodsFilter() {
+        super(uriPatternsAndAllowedHttpMethods());
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/ResourcePrivilegeManagementController.java b/service/src/main/java/com/ge/predix/acs/privilege/management/ResourcePrivilegeManagementController.java
new file mode 100644
index 0000000..e949060
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/ResourcePrivilegeManagementController.java
@@ -0,0 +1,200 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.MANAGED_RESOURCES_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.MANAGED_RESOURCE_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.V1;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.created;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.noContent;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.notFound;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.ok;
+import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
+import static org.springframework.web.bind.annotation.RequestMethod.GET;
+import static org.springframework.web.bind.annotation.RequestMethod.POST;
+import static org.springframework.web.bind.annotation.RequestMethod.PUT;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.ge.predix.acs.commons.web.BaseRestApi;
+import com.ge.predix.acs.commons.web.RestApiException;
+import com.ge.predix.acs.commons.web.UriTemplateUtils;
+import com.ge.predix.acs.rest.BaseResource;
+
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@RestController
+public class ResourcePrivilegeManagementController extends BaseRestApi {
+    @Autowired
+    private PrivilegeManagementService service;
+
+    private Boolean titanProfileActive = null;
+
+    private Boolean getTitanProfileActive() {
+        if (this.titanProfileActive == null) {
+            this.titanProfileActive = Arrays.asList(this.getEnvironment().getActiveProfiles()).contains("titan");
+        }
+
+        return this.titanProfileActive;
+    }
+
+    private void failIfParentsSpecified(final List<BaseResource> resources) {
+        if (this.getTitanProfileActive()) {
+            return;
+        }
+
+        for (BaseResource resource : resources) {
+            if (!CollectionUtils.isEmpty(resource.getParents())) {
+                throw new RestApiException(HttpStatus.NOT_IMPLEMENTED, PARENTS_ATTR_NOT_SUPPORTED_MSG);
+            }
+        }
+    }
+
+    @ApiOperation(value = "Creates a list of resources for the given zone. "
+            + "Existing resources will be updated with the provided values.", tags = { "Attribute Management" })
+    @ApiResponses(value = { @ApiResponse(code = 204, message = "Resource objects appended successfully."), })
+    @RequestMapping(method = POST, value = { V1 + MANAGED_RESOURCES_URL }, consumes = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<Void> appendResources(@RequestBody final List<BaseResource> resources) {
+        try {
+            this.failIfParentsSpecified(resources);
+
+            this.service.appendResources(resources);
+
+            return noContent();
+        } catch (RestApiException e) {
+            // NOTE: This block is necessary to avoid accidentally
+            // converting the HTTP status code to an unintended one
+            throw e;
+        } catch (Exception e) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY, e);
+        }
+    }
+
+    @ApiOperation(value = "Retrieves the list of all resources for the given zone.", tags = { "Attribute Management" })
+    @RequestMapping(method = GET, value = { V1 + MANAGED_RESOURCES_URL }, produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<List<BaseResource>> getResources() {
+
+        List<BaseResource> resources = this.service.getResources();
+        return ok(resources);
+    }
+
+    @ApiOperation(value = "Retrieves the resource for the given zone. The resourceIdentifier must be URL encoded in "
+            + "application/x-www-form-urlencoded format with UTF-8.", tags = { "Attribute Management" })
+    @RequestMapping(method = GET, value = V1 + MANAGED_RESOURCE_URL)
+    public ResponseEntity<BaseResource> getResourceV1(
+            @PathVariable("resourceIdentifier") final String resourceIdentifier,
+            @RequestParam(name = "includeInheritedAttributes",
+                    defaultValue = "false") final boolean includeInheritedAttributes) {
+        BaseResource resource;
+        if (includeInheritedAttributes) {
+            resource = this.service.getByResourceIdentifierWithInheritedAttributes(resourceIdentifier);
+        } else {
+            resource = this.service.getByResourceIdentifier(resourceIdentifier);
+        }
+
+        if (resource == null) {
+            return notFound();
+        }
+        return ok(resource);
+    }
+
+    @ApiOperation(value = "Creates/Updates a given resource for a given zone. "
+            + "The resourceIdentifier must be URL encoded in application/x-www-form-urlencoded format with " + "UTF-8.",
+            tags = { "Attribute Management" })
+    @RequestMapping(method = PUT, value = { V1 + MANAGED_RESOURCE_URL }, consumes = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<BaseResource> putResourceV1(@RequestBody final BaseResource resource,
+            @PathVariable("resourceIdentifier") final String resourceIdentifier) {
+        try {
+            this.failIfParentsSpecified(Collections.singletonList(resource));
+
+            // resource identifier is optional, setting it to URI resource id if
+            // missing from payload
+            if (StringUtils.isEmpty(resource.getResourceIdentifier())) {
+                resource.setResourceIdentifier(resourceIdentifier);
+            }
+
+            validResourceIdentifierOrFail(resource, resourceIdentifier);
+
+            return doPutResource(resource, resourceIdentifier);
+        } catch (RestApiException e) {
+            // NOTE: This block is necessary to avoid accidentally
+            // converting the HTTP status code to an unintended one
+            throw e;
+        } catch (Exception e) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY, e);
+        }
+
+    }
+
+    private ResponseEntity<BaseResource> doPutResource(final BaseResource resource, final String resourceIdentifier) {
+        try {
+            boolean createdResource = this.service.upsertResource(resource);
+
+            URI resourceUri = UriTemplateUtils.expand(MANAGED_RESOURCE_URL, "resourceIdentifier:" + resourceIdentifier);
+
+            if (createdResource) {
+                return created(resourceUri.getPath(), false);
+            }
+
+            // CHECK if path returns the right info
+            return created(resourceUri.getPath(), true);
+        } catch (Exception e) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY, e);
+        }
+    }
+
+    @ApiOperation(value = "Deletes the resource for a given zone. The resourceIdentifier must be URL encoded in "
+            + "application/x-www-form-urlencoded format with UTF-8.", tags = { "Attribute Management" })
+    @RequestMapping(method = DELETE, value = V1 + MANAGED_RESOURCE_URL)
+    public ResponseEntity<Void> deleteResourceV1(@PathVariable("resourceIdentifier") final String resourceIdentifier) {
+        this.service.deleteResource(resourceIdentifier);
+
+        return noContent();
+    }
+
+    private void validResourceIdentifierOrFail(final BaseResource resource, final String resourceIdentifier) {
+        if (!resourceIdentifier.equals(resource.getResourceIdentifier())) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY,
+                    String.format("Resource identifier = %s, does not match the one provided in URI = %s",
+                            resource.getResourceIdentifier(), resourceIdentifier));
+        }
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/SubjectHttpMethodsFilter.java b/service/src/main/java/com/ge/predix/acs/privilege/management/SubjectHttpMethodsFilter.java
new file mode 100644
index 0000000..c59c118
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/SubjectHttpMethodsFilter.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.security.AbstractHttpMethodsFilter;
+
+@Component
+public class SubjectHttpMethodsFilter extends AbstractHttpMethodsFilter {
+
+    private static final String UPDATE_SUBJECT_URI_REGEX = "\\A/v1/subject/[^/]+?/??\\Z";
+    private static final String CREATE_GET_SUBJECT_URI_REGEX = "\\A/v1/subject/??\\Z";
+
+    private static Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods() {
+        Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods = new LinkedHashMap<>();
+        uriPatternsAndAllowedHttpMethods.put(UPDATE_SUBJECT_URI_REGEX,
+                new HashSet<>(Arrays.asList(HttpMethod.PUT, HttpMethod.GET, HttpMethod.DELETE, HttpMethod.HEAD)));
+        uriPatternsAndAllowedHttpMethods.put(CREATE_GET_SUBJECT_URI_REGEX,
+                new HashSet<>(Arrays.asList(HttpMethod.POST, HttpMethod.GET, HttpMethod.HEAD)));
+        return uriPatternsAndAllowedHttpMethods;
+    }
+
+    public SubjectHttpMethodsFilter() {
+        super(uriPatternsAndAllowedHttpMethods());
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/SubjectPrivilegeManagementController.java b/service/src/main/java/com/ge/predix/acs/privilege/management/SubjectPrivilegeManagementController.java
new file mode 100644
index 0000000..ac48c39
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/SubjectPrivilegeManagementController.java
@@ -0,0 +1,177 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.SUBJECTS_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.SUBJECT_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.V1;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.created;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.noContent;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.notFound;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.ok;
+import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
+import static org.springframework.web.bind.annotation.RequestMethod.GET;
+import static org.springframework.web.bind.annotation.RequestMethod.POST;
+import static org.springframework.web.bind.annotation.RequestMethod.PUT;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.ge.predix.acs.commons.web.BaseRestApi;
+import com.ge.predix.acs.commons.web.RestApiException;
+import com.ge.predix.acs.commons.web.UriTemplateUtils;
+import com.ge.predix.acs.rest.BaseSubject;
+
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+
+@RestController
+public class SubjectPrivilegeManagementController extends BaseRestApi {
+    @Autowired
+    private PrivilegeManagementService service;
+
+    private Boolean titanProfileActive = null;
+
+    private Boolean getTitanProfileActive() {
+        if (this.titanProfileActive == null) {
+            this.titanProfileActive = Arrays.asList(this.getEnvironment().getActiveProfiles()).contains("titan");
+        }
+
+        return this.titanProfileActive;
+    }
+
+    private void failIfParentsSpecified(final List<BaseSubject> subjects) {
+        if (this.getTitanProfileActive()) {
+            return;
+        }
+
+        for (BaseSubject subject : subjects) {
+            if (!CollectionUtils.isEmpty(subject.getParents())) {
+                throw new RestApiException(HttpStatus.NOT_IMPLEMENTED, PARENTS_ATTR_NOT_SUPPORTED_MSG);
+            }
+        }
+    }
+
+    @ApiOperation(value = "Creates a list of subjects. Existing subjects will be updated with the provided values.",
+            tags = { "Attribute Management" })
+    @ApiResponses(value = { @ApiResponse(code = 204, message = "Subject objects added successfully."), })
+    @RequestMapping(method = POST, value = { V1 + SUBJECTS_URL }, consumes = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<Void> appendsubjects(@RequestBody final List<BaseSubject> subjects) {
+        try {
+            this.failIfParentsSpecified(subjects);
+
+            this.service.appendSubjects(subjects);
+            return noContent();
+        } catch (RestApiException e) {
+            // NOTE: This block is necessary to avoid accidentally
+            // converting the HTTP status code to an unintended one
+            throw e;
+        } catch (Exception e) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY, e);
+        }
+    }
+
+    @ApiOperation(value = "Retrieves the list of subjects for the given zone.", tags = { "Attribute Management" })
+    @RequestMapping(method = GET, value = { V1 + SUBJECTS_URL }, produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<List<BaseSubject>> getSubjects() {
+        List<BaseSubject> subjects = this.service.getSubjects();
+        return ok(subjects);
+    }
+
+    @ApiOperation(value = "Retrieves the subject for the given zone. The subjectIdentifier must be URL encoded in "
+            + "application/x-www-form-urlencoded format with UTF-8.", tags = { "Attribute Management" })
+    @RequestMapping(method = GET, value = { V1 + SUBJECT_URL })
+    public ResponseEntity<BaseSubject> getSubject(@PathVariable("subjectIdentifier") final String subjectIdentifier,
+            @RequestParam(name = "includeInheritedAttributes",
+                    defaultValue = "false") final boolean includeInheritedAttributes) {
+        BaseSubject subject;
+        if (includeInheritedAttributes) {
+            subject = this.service.getBySubjectIdentifierWithInheritedAttributes(subjectIdentifier);
+        } else {
+            subject = this.service.getBySubjectIdentifier(subjectIdentifier);
+        }
+
+        if (subject == null) {
+            return notFound();
+        }
+        return ok(subject);
+    }
+
+    @ApiOperation(value = "Creates/Updates a given Subject.", tags = { "Attribute Management" })
+    @RequestMapping(method = PUT, value = V1 + SUBJECT_URL, consumes = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<BaseSubject> putSubject(@RequestBody final BaseSubject subject,
+            @PathVariable("subjectIdentifier") final String subjectIdentifier) {
+        try {
+            this.failIfParentsSpecified(Collections.singletonList(subject));
+
+            if (StringUtils.isEmpty(subject.getSubjectIdentifier())) {
+                subject.setSubjectIdentifier(subjectIdentifier);
+            }
+
+            validSubjectIdentifierOrFail(subject, subjectIdentifier);
+
+            boolean createdSubject = this.service.upsertSubject(subject);
+
+            URI subjectUri = UriTemplateUtils.expand(SUBJECT_URL, "subjectIdentifier:" + subjectIdentifier);
+
+            if (createdSubject) {
+                return created(subjectUri.getRawPath(), false);
+            }
+
+            return created(subjectUri.getRawPath(), true);
+        } catch (RestApiException e) {
+            // NOTE: This block is necessary to avoid accidentally
+            // converting the HTTP status code to an unintended one
+            throw e;
+        } catch (Exception e) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY, e);
+        }
+    }
+
+    @ApiOperation(value = "Deletes the subject for a given zone. The subjectIdentifier must be URL encoded in "
+            + "application/x-www-form-urlencoded format with UTF-8.", tags = { "Attribute Management" })
+    @RequestMapping(method = DELETE, value = { V1 + SUBJECT_URL })
+    public ResponseEntity<Void> deleteSubject(@PathVariable("subjectIdentifier") final String subjectIdentifier) {
+        this.service.deleteSubject(subjectIdentifier);
+        return noContent();
+    }
+
+    private void validSubjectIdentifierOrFail(final BaseSubject subject, final String subjectIdentifier) {
+        if (!subjectIdentifier.equals(subject.getSubjectIdentifier())) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY,
+                    String.format("Subject identifier = %s, does not match the one provided in URI = %s",
+                            subject.getSubjectIdentifier(), subjectIdentifier));
+        }
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/AttributeLimitExceededException.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/AttributeLimitExceededException.java
new file mode 100644
index 0000000..53d6fb2
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/AttributeLimitExceededException.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+public class AttributeLimitExceededException extends RuntimeException {
+
+    private static final long serialVersionUID = -9111322155771470957L;
+
+    public AttributeLimitExceededException() {
+        super();
+    }
+
+    public AttributeLimitExceededException(final String message) {
+        super(message);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/AttributePredicate.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/AttributePredicate.java
new file mode 100644
index 0000000..6d3b90d
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/AttributePredicate.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import java.util.Set;
+import java.util.function.BiPredicate;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.utils.JsonUtils;
+
+public final class AttributePredicate {
+    private static final JsonUtils JSON_UTILS = new JsonUtils();
+
+    private AttributePredicate() {
+        // Prevents instantiation.
+    }
+
+    static BiPredicate<String, Set<Attribute>> elementOf() {
+        return new BiPredicate<String, Set<Attribute>>() {
+            @Override
+            public boolean test(final String t, final Set<Attribute> u) {
+                Attribute element = JSON_UTILS.deserialize(t, Attribute.class);
+                return u.contains(element);
+            }
+        };
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/GraphGenericRepository.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/GraphGenericRepository.java
new file mode 100644
index 0000000..fc9af19
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/GraphGenericRepository.java
@@ -0,0 +1,506 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import static com.ge.predix.acs.privilege.management.dao.AttributePredicate.elementOf;
+import static org.apache.tinkerpop.gremlin.process.traversal.P.eq;
+import static org.apache.tinkerpop.gremlin.process.traversal.P.test;
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.in;
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.outE;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.structure.Direction;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.util.Assert;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.Parent;
+import com.ge.predix.acs.utils.JsonUtils;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.google.common.collect.Sets;
+import com.thinkaurelius.titan.core.SchemaViolationException;
+
+public abstract class GraphGenericRepository<E extends ZonableEntity> implements JpaRepository<E, Long> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(GraphGenericRepository.class);
+    private static final JsonUtils JSON_UTILS = new JsonUtils();
+
+    public static final String ATTRIBUTES_PROPERTY_KEY = "attributes";
+    public static final String PARENT_EDGE_LABEL = "parent";
+    public static final String SCOPE_PROPERTY_KEY = "scope";
+    public static final String ZONE_NAME_PROPERTY_KEY = "zoneName";
+    public static final String ZONE_ID_KEY = "zoneId";
+    public static final String VERSION_PROPERTY_KEY = "schemaVersion";
+    public static final String VERSION_VERTEX_LABEL = "version";
+
+    @Autowired
+    private GraphTraversalSource graphTraversal;
+
+    @Value("${TITAN_TRAVERSAL_LIMIT:256}")
+    private long traversalLimit = 256;
+
+    @Override
+    public void deleteAllInBatch() {
+        deleteAll();
+    }
+
+    @Override
+    public void deleteInBatch(final Iterable<E> entities) {
+        delete(entities);
+    }
+
+    @Override
+    public List<E> findAll() {
+        try {
+            return this.graphTraversal.V().has(ZONE_ID_KEY).has(getEntityIdKey()).toList().stream()
+                    .map(this::vertexToEntity).collect(Collectors.toList());
+        } finally {
+            this.graphTraversal.tx().commit();
+        }
+    }
+
+    @Override
+    public List<E> findAll(final Sort arg0) {
+        throw new NotImplementedException("This repository does not support sortable find all queries.");
+    }
+
+    @Override
+    public List<E> findAll(final Iterable<Long> ids) {
+        try {
+            return this.graphTraversal.V(iterableToArray(ids)).toList().stream().map(this::vertexToEntity)
+                    .collect(Collectors.toList());
+        } finally {
+            this.graphTraversal.tx().commit();
+        }
+    }
+
+    private static Object[] iterableToArray(final Iterable<?> iterable) {
+        List<Object> idList = new ArrayList<>();
+        iterable.forEach(idList::add);
+        return idList.toArray();
+    }
+
+    @Override
+    public void flush() {
+        // Nothing to do.
+    }
+
+    @Override
+    public E getOne(final Long id) {
+        return findOne(id);
+    }
+
+    @Override
+    public <S extends E> List<S> save(final Iterable<S> entities) {
+        List<S> savedEntities = new ArrayList<>();
+        this.commitTransaction(() -> entities.forEach(item -> savedEntities.add(saveCommon(item))));
+        return savedEntities;
+    }
+
+    @Override
+    public <S extends E> S saveAndFlush(final S entity) {
+        return save(entity);
+    }
+
+    @Override
+    public Page<E> findAll(final Pageable pageable) {
+        throw new NotImplementedException("This repository does not support pageable find all queries.");
+    }
+
+    @Override
+    public long count() {
+        try {
+            return this.graphTraversal.V().has(getEntityIdKey()).count().next();
+        } finally {
+            this.graphTraversal.tx().commit();
+        }
+    }
+
+    @Override
+    public void delete(final Long id) {
+        this.commitTransaction(() -> this.graphTraversal.V(id).drop().iterate());
+    }
+
+    @Override
+    public void delete(final E entity) {
+        this.commitTransaction(() -> this.graphTraversal.V(entity.getId()).drop().iterate());
+    }
+
+    @Override
+    public void delete(final Iterable<? extends E> entities) {
+        List<Long> ids = new ArrayList<>();
+        entities.forEach(item -> ids.add(item.getId()));
+        this.commitTransaction(() -> this.graphTraversal.V(ids.toArray()).drop().iterate());
+    }
+
+    @Override
+    public void deleteAll() {
+        this.commitTransaction(() -> this.graphTraversal.V().has(getEntityIdKey()).drop().iterate());
+    }
+
+    @Override
+    public boolean exists(final Long id) {
+        try {
+            GraphTraversal<Vertex, Vertex> traversal = this.graphTraversal.V(id);
+            return traversal.hasNext();
+        } finally {
+            this.graphTraversal.tx().commit();
+        }
+    }
+
+    @Override
+    public E findOne(final Long id) {
+        try {
+            GraphTraversal<Vertex, Vertex> traversal = this.graphTraversal.V(id);
+            if (traversal.hasNext()) {
+                Vertex vertex = traversal.next();
+                return vertexToEntity(vertex);
+            }
+            return null;
+        } finally {
+            this.graphTraversal.tx().commit();
+        }
+    }
+
+    @Override
+    public <S extends E> S save(final S entity) {
+        S saveCommon;
+        try {
+            saveCommon = saveCommon(entity);
+            this.graphTraversal.tx().commit();
+        } catch (Exception e) {
+            this.graphTraversal.tx().rollback();
+            throw e;
+        }
+        return saveCommon;
+    }
+
+    private <S extends E> S saveCommon(final S entity) {
+        // Create the entity if the id is null otherwise update an existing entity.
+        if ((null == entity.getId()) || (0 == entity.getId())) {
+            verifyEntityNotSelfReferencing(entity);
+            String entityId = getEntityId(entity);
+            Assert.notNull(entity.getZone(), "ZonableEntity must have a non-null zone.");
+            String zoneId = entity.getZone().getName();
+            Assert.hasText(zoneId, "zoneName is required.");
+            Vertex entityVertex = this.graphTraversal.addV().property(T.label, getEntityLabel())
+                    .property(ZONE_ID_KEY, zoneId).property(getEntityIdKey(), entityId).next();
+
+            updateVertexProperties(entity, entityVertex);
+            saveParentRelationships(entity, entityVertex, false);
+            entity.setId((Long) entityVertex.id());
+        } else {
+            verifyEntityReferencesNotCyclic(entity);
+            GraphTraversal<Vertex, Vertex> traversal = this.graphTraversal.V(entity.getId());
+            Vertex entityVertex = traversal.next();
+            updateVertexProperties(entity, entityVertex);
+            saveParentRelationships(entity, entityVertex, true);
+        }
+        return entity;
+    }
+
+    private void verifyEntityNotSelfReferencing(final E entity) {
+        if (entity.getParents().contains(new Parent(getEntityId(entity)))) {
+            throw new SchemaViolationException(
+                    String.format("The entity '%s' references itself as a parent.", getEntityId(entity)));
+        }
+    }
+
+    private void verifyEntityReferencesNotCyclic(final E entity) {
+        // First verify the entity does not reference itself as a parent.
+        verifyEntityNotSelfReferencing(entity);
+
+        // Now check for potential cyclic references.
+        this.graphTraversal.V(entity.getId()).has(getEntityIdKey()).emit().repeat(in().has(getEntityIdKey()))
+                .until(eq(null)).values(getEntityIdKey()).toStream().forEach(id -> {
+            if (entity.getParents().contains(new Parent((String) id))) {
+                throw new SchemaViolationException(
+                        String.format("Updating entity '%s' with parent '%s' introduces a cyclic reference.",
+                                getEntityId(entity), id));
+            }
+        });
+    }
+
+    void saveParentRelationships(final E entity, final Vertex vertex, final boolean update) {
+        if (update) { // If this is an update remove all existing edges.
+            vertex.edges(Direction.OUT, PARENT_EDGE_LABEL).forEachRemaining(Edge::remove);
+        }
+        entity.getParents().forEach(parent -> saveParentRelationship(entity, vertex, parent));
+    }
+
+    private void saveParentRelationship(final E entity, final Vertex vertex, final Parent parent) {
+        GraphTraversal<Vertex, Vertex> traversal = this.graphTraversal.V().has(ZONE_ID_KEY, entity.getZone().getName())
+                .has(getEntityIdKey(), parent.getIdentifier());
+        if (!traversal.hasNext()) {
+            throw new IllegalStateException(
+                    String.format("No parent exists in zone '%s' with '%s' value of '%s'.", entity.getZone().getName(),
+                            getEntityIdKey(), parent.getIdentifier()));
+        }
+        Edge parentEdge = vertex.addEdge(PARENT_EDGE_LABEL, traversal.next());
+        parent.getScopes().forEach(scope -> parentEdge.property(SCOPE_PROPERTY_KEY, JSON_UTILS.serialize(scope)));
+    }
+
+    Set<ParentEntity> getParentEntities(final E entity) {
+        Set<ParentEntity> parents = new HashSet<>();
+        entity.getParents().forEach(parent -> {
+            GraphTraversal<Vertex, Vertex> traversal = this.graphTraversal.V()
+                    .has(ZONE_ID_KEY, entity.getZone().getName()).has(getEntityIdKey(), parent.getIdentifier());
+            if (!traversal.hasNext()) {
+                throw new IllegalStateException(String.format("No parent exists in zone '%s' with '%s' value of '%s'.",
+                        entity.getZone().getName(), getEntityIdKey(), parent.getIdentifier()));
+            }
+            parents.add(new ParentEntity(vertexToEntity(traversal.next()), parent.getScopes()));
+        });
+        return parents;
+    }
+
+    public boolean checkVersionVertexExists(final int versionNumber) {
+        try {
+            Vertex versionVertex = null;
+            // Value has to be provided to the has() method for index to be used. Composite indexes only work on
+            // equality comparisons.
+            GraphTraversal<Vertex, Vertex> traversal = this.graphTraversal.V()
+                    .has(VERSION_VERTEX_LABEL, VERSION_PROPERTY_KEY, versionNumber);
+            if (traversal.hasNext()) {
+                versionVertex = traversal.next();
+                // There should be only one version entity with a given version
+                Assert.isTrue(!traversal.hasNext(), "There are two schema version vertices in the graph");
+            }
+            return versionVertex != null;
+        } finally {
+            this.graphTraversal.tx().commit();
+        }
+    }
+
+    public void createVersionVertex(final int version) {
+       this.commitTransaction(() -> this.graphTraversal.addV().property(T.label, VERSION_VERTEX_LABEL)
+           .property(VERSION_PROPERTY_KEY, version).next());
+    }
+
+    public List<E> findByZone(final ZoneEntity zoneEntity) {
+        try {
+            String zoneName = zoneEntity.getName();
+            GraphTraversal<Vertex, Vertex> traversal = this.graphTraversal.V()
+                    .has(getEntityLabel(), ZONE_ID_KEY, zoneName).has(getEntityIdKey());
+            List<E> entities = new ArrayList<>();
+            while (traversal.hasNext()) {
+                entities.add(vertexToEntity(traversal.next()));
+            }
+            return entities;
+        } finally {
+            this.graphTraversal.tx().commit();
+        }
+    }
+
+    public static String getPropertyOrEmptyString(final Vertex vertex, final String propertyKey) {
+        VertexProperty<String> property = vertex.property(propertyKey);
+        if (property.isPresent()) {
+            return property.value();
+        }
+        return "";
+    }
+
+    public static String getPropertyOrFail(final Vertex vertex, final String propertyKey) {
+        VertexProperty<String> property = vertex.property(propertyKey);
+        if (property.isPresent()) {
+            return property.value();
+        }
+        throw new IllegalStateException(
+                String.format("The vertex with id '%s' does not conatin the expected property '%s'.", vertex.id(),
+                        propertyKey));
+    }
+
+    public static String getPropertyOrNull(final Vertex vertex, final String propertyKey) {
+        VertexProperty<String> property = vertex.property(propertyKey);
+        if (property.isPresent()) {
+            return property.value();
+        }
+        return null;
+    }
+
+    /**
+     * Returns the entity with attributes only on the requested vertex. No parent attributes are included.
+     */
+    public E getEntity(final ZoneEntity zone, final String identifier) {
+        try {
+            GraphTraversal<Vertex, Vertex> traversal = this.graphTraversal.V().has(ZONE_ID_KEY, zone.getName())
+                    .has(getEntityIdKey(), identifier);
+            if (!traversal.hasNext()) {
+                return null;
+            }
+            Vertex vertex = traversal.next();
+            E entity = vertexToEntity(vertex);
+
+            // There should be only one entity with a given entity id.
+            Assert.isTrue(!traversal.hasNext(),
+                    String.format("There are two entities with the same %s.", getEntityIdKey()));
+            return entity;
+        } finally {
+            this.graphTraversal.tx().commit();
+        }
+    }
+
+    public E getEntityWithInheritedAttributes(final ZoneEntity zone, final String identifier,
+            final Set<Attribute> scopes) {
+        try {
+            GraphTraversal<Vertex, Vertex> traversal = this.graphTraversal.V().has(ZONE_ID_KEY, zone.getName())
+                    .has(getEntityIdKey(), identifier);
+            if (!traversal.hasNext()) {
+                return null;
+            }
+            Vertex vertex = traversal.next();
+            E entity = vertexToEntity(vertex);
+            searchAttributesWithScopes(entity, vertex, scopes);
+
+            // There should be only one entity with a given entity id.
+            Assert.isTrue(!traversal.hasNext(),
+                    String.format("There are two entities with the same %s.", getEntityIdKey()));
+            return entity;
+        } finally {
+            this.graphTraversal.tx().commit();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void searchAttributesWithScopes(final E entity, final Vertex vertex, final Set<Attribute> scopes) {
+        Set<Attribute> attributes = new HashSet<>();
+
+        // First add all attributes inherited from non-scoped relationships.
+        this.graphTraversal.V(vertex.id()).has(ATTRIBUTES_PROPERTY_KEY).emit()
+                .repeat(outE().hasNot(SCOPE_PROPERTY_KEY).otherV().simplePath().has(ATTRIBUTES_PROPERTY_KEY))
+                .until(eq(null)).limit(this.traversalLimit + 1).values(ATTRIBUTES_PROPERTY_KEY).toStream()
+                .forEach(it -> {
+                    Set<Attribute> deserializedAttributes = JSON_UTILS
+                            .deserialize((String) it, Set.class, Attribute.class);
+                    if (deserializedAttributes != null) {
+                        attributes.addAll(deserializedAttributes);
+                        // This enforces the limit on the count of attributes returned from the traversal, instead of
+                        // number of vertices traversed. To do the latter will require traversing the graph twice.
+                        checkTraversalLimitOrFail(entity, attributes);
+                    }
+                });
+
+        this.graphTraversal.V(vertex.id()).has(ATTRIBUTES_PROPERTY_KEY).emit()
+                .repeat(outE().has(SCOPE_PROPERTY_KEY, test(elementOf(), scopes)).otherV().simplePath()
+                        .has(ATTRIBUTES_PROPERTY_KEY)).until(eq(null)).limit(this.traversalLimit + 1)
+                .values(ATTRIBUTES_PROPERTY_KEY).toStream().forEach(it -> {
+            Set<Attribute> deserializedAttributes = JSON_UTILS.deserialize((String) it, Set.class, Attribute.class);
+            if (deserializedAttributes != null) {
+                attributes.addAll(deserializedAttributes);
+                checkTraversalLimitOrFail(entity, attributes);
+            }
+        });
+        entity.setAttributes(attributes);
+        entity.setAttributesAsJson(JSON_UTILS.serialize(attributes));
+    }
+
+    private void checkTraversalLimitOrFail(final E e, final Set<Attribute> attributes) {
+        if (attributes.size() > this.traversalLimit) {
+            String exceptionMessage = String
+                    .format("The number of attributes on this " + e.getEntityType() + " '" + e.getEntityId()
+                            + "' has exceeded the maximum limit of %d", this.traversalLimit);
+            throw new AttributeLimitExceededException(exceptionMessage);
+        }
+    }
+
+    public Set<Parent> getParents(final Vertex vertex, final String identifierKey) {
+        Set<Parent> parentSet = new HashSet<>();
+        vertex.edges(Direction.OUT, PARENT_EDGE_LABEL).forEachRemaining(edge -> {
+            String parentIdentifier = getPropertyOrFail(edge.inVertex(), identifierKey);
+            Attribute scope;
+            Parent parent;
+            try {
+                scope = JSON_UTILS.deserialize((String) edge.property(SCOPE_PROPERTY_KEY).value(), Attribute.class);
+                // use ParentEntity ?
+                parent = new Parent(parentIdentifier, Sets.newHashSet(scope));
+            } catch (Exception e) {
+                LOGGER.debug("Error deserializing attribute", e);
+                parent = new Parent(parentIdentifier);
+            }
+            parentSet.add(parent);
+
+        });
+        return parentSet;
+    }
+
+    public Set<String> getEntityAndDescendantsIds(final E entity) {
+        if (entity == null) {
+            return Collections.emptySet();
+        }
+
+        return this.graphTraversal.V(entity.getId()).has(getEntityIdKey()).emit().repeat(in().has(getEntityIdKey()))
+                .until(eq(null)).values(getEntityIdKey()).toStream().map(Object::toString).collect(Collectors.toSet());
+    }
+
+    abstract String getEntityId(E entity);
+
+    abstract String getEntityIdKey();
+
+    abstract String getEntityLabel();
+
+    abstract String getRelationshipKey();
+
+    abstract void updateVertexProperties(E entity, Vertex vertex);
+
+    abstract E vertexToEntity(Vertex vertex);
+
+    public GraphTraversalSource getGraphTraversal() {
+        return graphTraversal;
+    }
+
+    public void setGraphTraversal(final GraphTraversalSource graphTraversal) {
+        this.graphTraversal = graphTraversal;
+    }
+
+    public long getTraversalLimit() {
+        return this.traversalLimit;
+    }
+
+    public void setTraversalLimit(final long traversalLimit) {
+        this.traversalLimit = traversalLimit;
+    }
+    private void commitTransaction(final Runnable graphQuery) {
+        try {
+            graphQuery.run();
+            this.graphTraversal.tx().commit();
+        } catch (final Exception e) {
+            this.graphTraversal.tx().rollback();
+            throw e;
+        }
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/GraphResourceRepository.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/GraphResourceRepository.java
new file mode 100644
index 0000000..a522216
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/GraphResourceRepository.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.Parent;
+import com.ge.predix.acs.utils.JsonUtils;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+public class GraphResourceRepository extends GraphGenericRepository<ResourceEntity>
+        implements ResourceRepository, ResourceHierarchicalRepository {
+
+    private static final JsonUtils JSON_UTILS = new JsonUtils();
+
+    private static final String EMPTY_ATTRIBUTES = "{}";
+    private static final String HAS_RESOURCE_RELATIONSHIP_KEY = "hasResource";
+
+    public static final String RESOURCE_LABEL = "resource";
+    public static final String RESOURCE_ID_KEY = "resourceId";
+    private static final String MESSAGE = "method not supported";
+
+    @Override
+    public ResourceEntity getByZoneAndResourceIdentifier(final ZoneEntity zone, final String resourceIdentifier) {
+        return getEntity(zone, resourceIdentifier);
+    }
+
+    @Override
+    public ResourceEntity getResourceWithInheritedAttributes(final ZoneEntity zone, final String resourceIdentifier) {
+        return getEntityWithInheritedAttributes(zone, resourceIdentifier, Collections.emptySet());
+    }
+
+    @Override
+    String getEntityId(final ResourceEntity entity) {
+        return entity.getResourceIdentifier();
+    }
+
+    @Override
+    String getEntityIdKey() {
+        return RESOURCE_ID_KEY;
+    }
+
+    @Override
+    String getEntityLabel() {
+        return RESOURCE_LABEL;
+    }
+
+    @Override
+    String getRelationshipKey() {
+        return HAS_RESOURCE_RELATIONSHIP_KEY;
+    }
+
+    @Override
+    void updateVertexProperties(final ResourceEntity entity, final Vertex vertex) {
+        String resourceAttributesJson = entity.getAttributesAsJson();
+        if (StringUtils.isEmpty(resourceAttributesJson)) {
+            resourceAttributesJson = EMPTY_ATTRIBUTES;
+        }
+        vertex.property(ATTRIBUTES_PROPERTY_KEY, resourceAttributesJson);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    ResourceEntity vertexToEntity(final Vertex vertex) {
+        String resourceIdentifier = getPropertyOrFail(vertex, RESOURCE_ID_KEY);
+        String zoneName = getPropertyOrFail(vertex, ZONE_ID_KEY);
+        ZoneEntity zoneEntity = new ZoneEntity();
+        zoneEntity.setName(zoneName);
+        ResourceEntity resourceEntity = new ResourceEntity(zoneEntity, resourceIdentifier);
+        resourceEntity.setId((Long) vertex.id());
+        String attributesAsJson = getPropertyOrEmptyString(vertex, ATTRIBUTES_PROPERTY_KEY);
+        resourceEntity.setAttributesAsJson(attributesAsJson);
+        resourceEntity.setAttributes(JSON_UTILS.deserialize(attributesAsJson, Set.class, Attribute.class));
+        Set<Parent> parentSet = getParents(vertex, RESOURCE_ID_KEY);
+        resourceEntity.setParents(parentSet);
+        return resourceEntity;
+    }
+
+    @Override
+    public Set<String> getResourceEntityAndDescendantsIds(final ResourceEntity entity) {
+        return getEntityAndDescendantsIds(entity);
+    }
+
+    @Override
+    public <S extends ResourceEntity> List<S> findAll(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends ResourceEntity> List<S> findAll(final Example<S> example, final Sort sort) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends ResourceEntity> S findOne(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends ResourceEntity> Page<S> findAll(final Example<S> example, final Pageable pageable) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends ResourceEntity> long count(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends ResourceEntity> boolean exists(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/GraphSubjectRepository.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/GraphSubjectRepository.java
new file mode 100644
index 0000000..d77888c
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/GraphSubjectRepository.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.Parent;
+import com.ge.predix.acs.utils.JsonUtils;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+public class GraphSubjectRepository extends GraphGenericRepository<SubjectEntity>
+        implements SubjectRepository, SubjectHierarchicalRepository {
+    private static final JsonUtils JSON_UTILS = new JsonUtils();
+
+    private static final String EMPTY_ATTRIBUTES = "{}";
+    private static final String HAS_SUBJECT_RELATIONSHIP_KEY = "hasSubject";
+
+    public static final String SUBJECT_LABEL = "subject";
+    public static final String SUBJECT_ID_KEY = "subjectId";
+    private static final String MESSAGE = "method not supported";
+
+    @Override
+    public SubjectEntity getByZoneAndSubjectIdentifier(final ZoneEntity zone, final String subjectIdentifier) {
+        return getEntity(zone, subjectIdentifier);
+    }
+
+    @Override
+    public SubjectEntity getSubjectWithInheritedAttributes(final ZoneEntity zone, final String subjectIdentifier) {
+        return getEntityWithInheritedAttributes(zone, subjectIdentifier, Collections.emptySet());
+    }
+
+    @Override
+    public SubjectEntity getSubjectWithInheritedAttributesForScopes(final ZoneEntity zone,
+            final String subjectIdentifier, final Set<Attribute> scopes) {
+        return getEntityWithInheritedAttributes(zone, subjectIdentifier, scopes);
+    }
+
+    @Override
+    String getEntityId(final SubjectEntity entity) {
+        return entity.getSubjectIdentifier();
+    }
+
+    @Override
+    String getEntityIdKey() {
+        return SUBJECT_ID_KEY;
+    }
+
+    @Override
+    String getEntityLabel() {
+        return SUBJECT_LABEL;
+    }
+
+    @Override
+    String getRelationshipKey() {
+        return HAS_SUBJECT_RELATIONSHIP_KEY;
+    }
+
+    @Override
+    void updateVertexProperties(final SubjectEntity entity, final Vertex vertex) {
+        String subjectAttributesJson = entity.getAttributesAsJson();
+        if (StringUtils.isEmpty(subjectAttributesJson)) {
+            subjectAttributesJson = EMPTY_ATTRIBUTES;
+        }
+        vertex.property(ATTRIBUTES_PROPERTY_KEY, subjectAttributesJson);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    SubjectEntity vertexToEntity(final Vertex vertex) {
+        String subjectIdentifier = getPropertyOrFail(vertex, SUBJECT_ID_KEY);
+        String zoneName = getPropertyOrFail(vertex, ZONE_ID_KEY);
+        ZoneEntity zoneEntity = new ZoneEntity();
+        zoneEntity.setName(zoneName);
+        SubjectEntity subjectEntity = new SubjectEntity(zoneEntity, subjectIdentifier);
+        subjectEntity.setId((long) vertex.id());
+        String attributesAsJson = getPropertyOrEmptyString(vertex, ATTRIBUTES_PROPERTY_KEY);
+        subjectEntity.setAttributesAsJson(attributesAsJson);
+        subjectEntity.setAttributes(JSON_UTILS.deserialize(attributesAsJson, Set.class, Attribute.class));
+        Set<Parent> parentSet = getParents(vertex, SUBJECT_ID_KEY);
+        subjectEntity.setParents(parentSet);
+        return subjectEntity;
+    }
+
+    @Override
+    public Set<String> getSubjectEntityAndDescendantsIds(final SubjectEntity entity) {
+        return getEntityAndDescendantsIds(entity);
+    }
+
+    @Override
+    public <S extends SubjectEntity> List<S> findAll(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends SubjectEntity> List<S> findAll(final Example<S> example, final Sort sort) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends SubjectEntity> S findOne(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends SubjectEntity> Page<S> findAll(final Example<S> example, final Pageable pageable) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends SubjectEntity> long count(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends SubjectEntity> boolean exists(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ParentEntity.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ParentEntity.java
new file mode 100644
index 0000000..2c48363
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ParentEntity.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import java.util.Set;
+
+import com.ge.predix.acs.model.Attribute;
+
+public class ParentEntity {
+
+    private ZonableEntity childEntity;
+    private Set<Attribute> scopes;
+
+    ParentEntity() {
+        // Default constructor.
+    }
+
+    public ParentEntity(final ZonableEntity entity) {
+        this.childEntity = entity;
+    }
+
+    public ParentEntity(final ZonableEntity entity, final Set<Attribute> scopes) {
+        this.childEntity = entity;
+        this.scopes = scopes;
+    }
+
+    public ZonableEntity getChildEntity() {
+        return this.childEntity;
+    }
+
+    public void setChildEntity(final ZonableEntity entity) {
+        this.childEntity = entity;
+    }
+
+    public Set<Attribute> getScopes() {
+        return this.scopes;
+    }
+
+    public void setScopes(final Set<Attribute> scopes) {
+        this.scopes = scopes;
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ResourceEntity.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ResourceEntity.java
new file mode 100644
index 0000000..d104b28
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ResourceEntity.java
@@ -0,0 +1,180 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import java.util.Collections;
+import java.util.Set;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.Parent;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+@Entity
+@Table(
+        name = "resource",
+        uniqueConstraints = { @UniqueConstraint(columnNames = { "authorization_zone_id", "resource_identifier" }) })
+public class ResourceEntity implements ZonableEntity {
+    @Id
+    @Column(name = "id")
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private long id;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "authorization_zone_id", referencedColumnName = "id", nullable = false, updatable = false)
+    private ZoneEntity zone;
+
+    @Column(name = "resource_identifier", nullable = false)
+    private String resourceIdentifier;
+
+    /**
+     * Clob representing set of attributes as a JSON body.
+     */
+    @Column(name = "attributes", columnDefinition = "CLOB NOT NULL")
+    private String attributesAsJson = "{}";
+
+    @Transient
+    private Set<Attribute> attributes = Collections.emptySet();
+
+    @Transient
+    private Set<Parent> parents = Collections.emptySet();
+
+    /**
+     * Note about all these Id's and identifiers:
+     *
+     * id: surrogate id generated by the rbdms, intended to FK references, etc. resourceIdentifier: The actual
+     * resource URI being protected, Ex: /asset/sanramon
+     */
+    public ResourceEntity(final ZoneEntity zone, final String resourceIdentifier) {
+        this.zone = zone;
+        this.resourceIdentifier = resourceIdentifier;
+    }
+
+    public ResourceEntity() {
+        // required for jackson serialization
+    }
+
+    @Override
+    public Long getId() {
+        return this.id;
+    }
+
+    @Override
+    public void setId(final Long id) {
+        this.id = id;
+    }
+
+    @Override
+    public ZoneEntity getZone() {
+        return this.zone;
+    }
+
+    @Override
+    public void setZone(final ZoneEntity zone) {
+        this.zone = zone;
+    }
+
+    public String getResourceIdentifier() {
+        return this.resourceIdentifier;
+    }
+
+    public void setResourceIdentifier(final String resourceIdentifier) {
+        this.resourceIdentifier = resourceIdentifier;
+    }
+
+    @Override
+    public String getAttributesAsJson() {
+        return this.attributesAsJson;
+    }
+
+    @Override
+    public void setAttributesAsJson(final String attributesAsJson) {
+        this.attributesAsJson = attributesAsJson;
+    }
+
+    @Override
+    public Set<Attribute> getAttributes() {
+        return this.attributes;
+    }
+
+    @Override
+    public void setAttributes(final Set<Attribute> attributes) {
+        if (attributes == null) {
+            this.attributes = Collections.emptySet();
+        } else {
+            this.attributes = attributes;
+        }
+    }
+
+    @Override
+    public Set<Parent> getParents() {
+        return this.parents;
+    }
+
+    @Override
+    public void setParents(final Set<Parent> parents) {
+        this.parents = parents;
+    }
+
+    @Override
+    public String toString() {
+        return "ResourceEntity [id=" + this.id + ", zone=" + this.zone + ", resourceIdentifier="
+                + this.resourceIdentifier + ", attributesAsJson=" + this.attributesAsJson + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(this.resourceIdentifier).append(this.zone).append(this.attributesAsJson)
+                .append(this.parents).toHashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof ResourceEntity) {
+            ResourceEntity other = (ResourceEntity) obj;
+            return new EqualsBuilder().append(this.resourceIdentifier, other.resourceIdentifier)
+                    .append(this.zone, other.zone).append(this.attributesAsJson, other.attributesAsJson)
+                    .append(this.parents, other.parents).isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public String getEntityId() {
+        return this.getResourceIdentifier();
+    }
+
+    @Override
+    public String getEntityType() {
+        return "resource";
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ResourceHierarchicalRepository.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ResourceHierarchicalRepository.java
new file mode 100644
index 0000000..9666e28
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ResourceHierarchicalRepository.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+import java.util.Set;
+
+public interface ResourceHierarchicalRepository {
+
+    ResourceEntity getResourceWithInheritedAttributes(ZoneEntity zone, String resourceIdentifier);
+
+    Set<String> getResourceEntityAndDescendantsIds(ResourceEntity entity); 
+}
\ No newline at end of file
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ResourceMigrationManager.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ResourceMigrationManager.java
new file mode 100644
index 0000000..ef2be0d
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ResourceMigrationManager.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+
+public class ResourceMigrationManager {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ResourceMigrationManager.class);
+
+    public void doResourceMigration(final ResourceRepository resourceRepository,
+            final GraphResourceRepository resourceHierarchicalRepository, final int pageSize) {
+        int numOfResourcesSaved = 0;
+        Pageable pageRequest = new PageRequest(0, pageSize, new Sort("id"));
+        long numOfResourceEntitiesToMigrate = resourceRepository.count();
+        Page<ResourceEntity> pageOfResources;
+
+        do {
+            pageOfResources = resourceRepository.findAll(pageRequest);
+            List<ResourceEntity> resourceListToSave = pageOfResources.getContent();
+            numOfResourcesSaved += pageOfResources.getNumberOfElements();
+
+            resourceListToSave.forEach(item -> {
+                // Clear the auto-generated id field prior to migrating to graphDB
+                item.setId((long) 0);
+                LOGGER.trace("doResourceMigration Resource-Id: {} Zone-name: {} Zone-id: {}",
+                        item.getResourceIdentifier(), item.getZone().getName(), item.getZone().getId());
+            });
+
+            resourceHierarchicalRepository.save(resourceListToSave);
+
+            LOGGER.info("Total resources migrated so far: {}/{}", numOfResourcesSaved, numOfResourceEntitiesToMigrate);
+            pageRequest = pageOfResources.nextPageable();
+        } while (pageOfResources.hasNext());
+
+        LOGGER.info("Number of resource entities migrated: {}", numOfResourcesSaved);
+        LOGGER.info("Resource migration to Titan completed.");
+    }
+
+    public void rollbackMigratedData(final GraphResourceRepository resourceHierarchicalRepository) {
+        LOGGER.info("Initiating rollback for resourceHierarchicalRepository");
+        resourceHierarchicalRepository.deleteAll();
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ResourceRepository.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ResourceRepository.java
new file mode 100644
index 0000000..2b59084
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ResourceRepository.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface ResourceRepository extends JpaRepository<ResourceEntity, Long> {
+
+    List<ResourceEntity> findByZone(ZoneEntity zone);
+
+    ResourceEntity getByZoneAndResourceIdentifier(ZoneEntity zone, String resourceIdentifier);
+
+}
\ No newline at end of file
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ResourceRepositoryProxy.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ResourceRepositoryProxy.java
new file mode 100644
index 0000000..958b555
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ResourceRepositoryProxy.java
@@ -0,0 +1,218 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.env.Environment;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+@Component
+public class ResourceRepositoryProxy implements ResourceRepository, ResourceHierarchicalRepository, InitializingBean {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ResourceRepositoryProxy.class);
+    private static final String MESSAGE = "method not supported";
+
+    @Autowired(required = false)
+    private GraphResourceRepository graphRepository;
+
+    @Autowired
+    @Qualifier("resourceRepository") // This is the bean id registered by Spring data JPA.
+    private ResourceRepository nonGraphRepository;
+
+    @Autowired
+    private Environment environment;
+
+    // This is set to the active repository being proxied to, based on the active profile. See afterPropertiesSet()
+    private ResourceRepository activeRepository;
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        if (Arrays.asList(this.environment.getActiveProfiles()).contains("titan")) {
+            this.activeRepository = this.graphRepository;
+            LOGGER.info("Resource hierarchical repository enabled.");
+        } else {
+            this.activeRepository = this.nonGraphRepository;
+            LOGGER.info("Resource non-hierarchical repository enabled.");
+        }
+    }
+
+    @Override
+    public List<ResourceEntity> findAll() {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public List<ResourceEntity> findAll(final Sort arg0) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public List<ResourceEntity> findAll(final Iterable<Long> arg0) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends ResourceEntity> Page<S> findAll(final Example<S> example, final Pageable pageable) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends ResourceEntity> List<S> findAll(final Example<S> example, final Sort sort) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends ResourceEntity> List<S> findAll(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends ResourceEntity> List<S> save(final Iterable<S> arg0) {
+        return this.activeRepository.save(arg0);
+    }
+
+    @Override
+    public void flush() {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends ResourceEntity> S saveAndFlush(final S arg0) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public void deleteInBatch(final Iterable<ResourceEntity> arg0) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public void deleteAllInBatch() {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public ResourceEntity getOne(final Long arg0) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public Page<ResourceEntity> findAll(final Pageable arg0) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends ResourceEntity> S save(final S arg0) {
+        return this.activeRepository.save(arg0);
+    }
+
+    @Override
+    public ResourceEntity findOne(final Long arg0) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends ResourceEntity> S findOne(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public boolean exists(final Long arg0) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public long count() {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends ResourceEntity> long count(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends ResourceEntity> boolean exists(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public void delete(final Long arg0) {
+        this.activeRepository.delete(arg0);
+    }
+
+    @Override
+    public void delete(final ResourceEntity arg0) {
+        this.activeRepository.delete(arg0);
+
+    }
+
+    @Override
+    public void delete(final Iterable<? extends ResourceEntity> arg0) {
+        this.activeRepository.delete(arg0);
+    }
+
+    @Override
+    public void deleteAll() {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public ResourceEntity getResourceWithInheritedAttributes(final ZoneEntity zone, final String resourceIdentifier) {
+        if (this.activeRepository == this.graphRepository) { // i.e. titan is enabled
+            return this.graphRepository.getResourceWithInheritedAttributes(zone, resourceIdentifier);
+        } else {
+            return this.nonGraphRepository.getByZoneAndResourceIdentifier(zone, resourceIdentifier);
+        }
+    }
+
+    @Override
+    public List<ResourceEntity> findByZone(final ZoneEntity zone) {
+        return this.activeRepository.findByZone(zone);
+    }
+
+    @Override
+    public ResourceEntity getByZoneAndResourceIdentifier(final ZoneEntity zone, final String resourceIdentifier) {
+        return this.activeRepository.getByZoneAndResourceIdentifier(zone, resourceIdentifier);
+    }
+
+    @Override
+    public Set<String> getResourceEntityAndDescendantsIds(final ResourceEntity entity) {
+        if (this.activeRepository == this.graphRepository) { // i.e. titan is enabled
+            return this.graphRepository.getResourceEntityAndDescendantsIds(entity);
+        } else {
+            return Collections.singleton(entity.getResourceIdentifier());
+        }
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/SubjectEntity.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/SubjectEntity.java
new file mode 100644
index 0000000..c97713d
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/SubjectEntity.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import java.util.Collections;
+import java.util.Set;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.Parent;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+@Entity
+@Table(
+        name = "subject",
+        uniqueConstraints = { @UniqueConstraint(columnNames = { "authorization_zone_id", "subject_identifier" }) })
+public class SubjectEntity implements ZonableEntity {
+    @Id
+    @Column(name = "id")
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private long id;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "authorization_zone_id", referencedColumnName = "id", nullable = false, updatable = false)
+    private ZoneEntity zone;
+
+    @Column(name = "subject_identifier", nullable = false)
+    private String subjectIdentifier;
+
+    /**
+     * Clob representing set of attributes as a JSON body.
+     */
+    @Column(name = "attributes", columnDefinition = "CLOB NOT NULL")
+    private String attributesAsJson = "{}";
+
+    @Transient
+    private Set<Attribute> attributes = Collections.emptySet();
+
+    @Transient
+    private Set<Parent> parents = Collections.emptySet();
+
+    /**
+     * Note about all these Id's and identifiers:
+     *
+     * id: surrogate id generated by the rbdms, intended to FK references, etc. subjectIdentifier: The actual subject
+     * identifier being protected, Ex: dave@ge.com
+     */
+
+    public SubjectEntity() {
+    }
+
+    public SubjectEntity(final ZoneEntity zone, final String subjectIdentifier) {
+        this.zone = zone;
+        this.subjectIdentifier = subjectIdentifier;
+    }
+
+    @Override
+    public Long getId() {
+        return this.id;
+    }
+
+    @Override
+    public void setId(final Long id) {
+        this.id = id;
+    }
+
+    @Override
+    public ZoneEntity getZone() {
+        return this.zone;
+    }
+
+    @Override
+    public void setZone(final ZoneEntity zone) {
+        this.zone = zone;
+    }
+
+    public String getSubjectIdentifier() {
+        return this.subjectIdentifier;
+    }
+
+    public void setSubjectIdentifier(final String subjectIdentifier) {
+        this.subjectIdentifier = subjectIdentifier;
+    }
+
+    @Override
+    public String getAttributesAsJson() {
+        return this.attributesAsJson;
+    }
+
+    @Override
+    public void setAttributesAsJson(final String attributesAsJson) {
+        this.attributesAsJson = attributesAsJson;
+    }
+
+    @Override
+    public Set<Attribute> getAttributes() {
+        return this.attributes;
+    }
+
+    @Override
+    public void setAttributes(final Set<Attribute> attributes) {
+        if (attributes == null) {
+            this.attributes = Collections.emptySet();
+        } else {
+            this.attributes = attributes;
+        }
+    }
+
+    @Override
+    public Set<Parent> getParents() {
+        return this.parents;
+    }
+
+    @Override
+    public void setParents(final Set<Parent> parents) {
+        this.parents = parents;
+    }
+
+    public void setId(final long id) {
+        this.id = id;
+    }
+
+    @Override
+    public String toString() {
+        return "SubjectEntity [id=" + this.id + ", zone=" + this.zone + ", subjectIdentifier=" + this.subjectIdentifier
+                + ", attributesAsJson=" + this.attributesAsJson + ", parents=" + this.parents + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(this.subjectIdentifier).append(this.zone).append(this.attributesAsJson)
+                .append(this.parents).toHashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof SubjectEntity) {
+            SubjectEntity other = (SubjectEntity) obj;
+            return new EqualsBuilder().append(this.subjectIdentifier, other.subjectIdentifier)
+                    .append(this.zone, other.zone).append(this.attributesAsJson, other.attributesAsJson)
+                    .append(this.parents, other.parents).isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public String getEntityId() {
+        return this.getSubjectIdentifier();
+    }
+
+    @Override
+    public String getEntityType() {
+        return "subject";
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/SubjectHierarchicalRepository.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/SubjectHierarchicalRepository.java
new file mode 100644
index 0000000..6c2c3be
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/SubjectHierarchicalRepository.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+import java.util.Set;
+
+public interface SubjectHierarchicalRepository {
+    SubjectEntity getSubjectWithInheritedAttributesForScopes(ZoneEntity zone, String subjectIdentifier,
+                                                             Set<Attribute> scopes);
+
+    SubjectEntity getSubjectWithInheritedAttributes(ZoneEntity zone, String subjectIdentifier);
+
+    Set<String> getSubjectEntityAndDescendantsIds(SubjectEntity entity); 
+}
\ No newline at end of file
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/SubjectMigrationManager.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/SubjectMigrationManager.java
new file mode 100644
index 0000000..3a81578
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/SubjectMigrationManager.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Component;
+
+@Component
+@Profile("titan")
+public class SubjectMigrationManager {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SubjectMigrationManager.class);
+
+    public void doSubjectMigration(final SubjectRepository subjectRepository,
+            final GraphSubjectRepository subjectHierarchicalRepository, final int pageSize) {
+        int numOfSubjectsSaved = 0;
+        Pageable pageRequest = new PageRequest(0, pageSize, new Sort("id"));
+        long numOfSubjectEntitiesToMigrate = subjectRepository.count();
+        Page<SubjectEntity> pageOfSubjects;
+
+        do {
+            pageOfSubjects = subjectRepository.findAll(pageRequest);
+            List<SubjectEntity> subjectListToSave = pageOfSubjects.getContent();
+            numOfSubjectsSaved += pageOfSubjects.getNumberOfElements();
+            subjectListToSave.forEach(item -> {
+                item.setId(0);
+                LOGGER.trace("doSubjectMigration Subject-Id: {} Zone-name: {} Zone-id: {}", item.getSubjectIdentifier(),
+                        item.getZone().getName(), item.getZone().getId());
+            });
+
+            subjectHierarchicalRepository.save(subjectListToSave);
+            LOGGER.info("Total subjects migrated so far: {}/{}", numOfSubjectsSaved, numOfSubjectEntitiesToMigrate);
+            pageRequest = pageOfSubjects.nextPageable();
+        } while (pageOfSubjects.hasNext());
+
+        LOGGER.info("Number of subject entities migrated: {}", numOfSubjectsSaved);
+        LOGGER.info("Subject migration to Titan completed.");
+    }
+
+    public void rollbackMigratedData(final GraphSubjectRepository subjectHierarchicalRepository) {
+        LOGGER.info("Initiating rollback for subjectHierarchicalRepository");
+        subjectHierarchicalRepository.deleteAll();
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/SubjectRepository.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/SubjectRepository.java
new file mode 100644
index 0000000..909a933
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/SubjectRepository.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface SubjectRepository extends JpaRepository<SubjectEntity, Long> {
+
+    List<SubjectEntity> findByZone(ZoneEntity zone);
+
+    SubjectEntity getByZoneAndSubjectIdentifier(ZoneEntity zone, String subjectIdentifier);
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/SubjectRepositoryProxy.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/SubjectRepositoryProxy.java
new file mode 100644
index 0000000..131059a
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/SubjectRepositoryProxy.java
@@ -0,0 +1,229 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.env.Environment;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+@Component
+public class SubjectRepositoryProxy implements SubjectRepository, SubjectHierarchicalRepository, InitializingBean {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SubjectRepositoryProxy.class);
+    private static final String MESSAGE = "method not supported";
+
+    @Autowired(required = false)
+    private GraphSubjectRepository graphRepository;
+
+    @Autowired
+    @Qualifier("subjectRepository") // This is the bean id registered by Spring data JPA.
+    private SubjectRepository nonGraphRepository;
+
+    @Autowired
+    private Environment environment;
+
+    // This is set to the active repository being proxied to, based on the active profile. See afterPropertiesSet()
+    private SubjectRepository activeRepository;
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        if (Arrays.asList(this.environment.getActiveProfiles()).contains("titan")) {
+            this.activeRepository = this.graphRepository;
+            LOGGER.info("Subject hierarchical repository enabled.");
+        } else {
+            this.activeRepository = this.nonGraphRepository;
+            LOGGER.info("Subject non-hierarchical repository enabled.");
+        }
+    }
+
+    @Override
+    public List<SubjectEntity> findAll() {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public List<SubjectEntity> findAll(final Sort sort) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public List<SubjectEntity> findAll(final Iterable<Long> ids) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends SubjectEntity> List<S> save(final Iterable<S> entities) {
+        return this.activeRepository.save(entities);
+    }
+
+    @Override
+    public void flush() {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends SubjectEntity> S saveAndFlush(final S entity) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public void deleteInBatch(final Iterable<SubjectEntity> entities) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public void deleteAllInBatch() {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public SubjectEntity getOne(final Long id) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends SubjectEntity> List<S> findAll(final Example<S> example, final Sort sort) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends SubjectEntity> List<S> findAll(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public Page<SubjectEntity> findAll(final Pageable pageable) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends SubjectEntity> Page<S> findAll(final Example<S> example, final Pageable pageable) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends SubjectEntity> S save(final S entity) {
+        return this.activeRepository.save(entity);
+    }
+
+    @Override
+    public SubjectEntity findOne(final Long id) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends SubjectEntity> S findOne(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+
+    @Override
+    public boolean exists(final Long id) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public long count() {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public <S extends SubjectEntity> long count(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public void delete(final Long id) {
+        this.activeRepository.delete(id);
+    }
+
+    @Override
+    public void delete(final SubjectEntity entity) {
+        this.activeRepository.delete(entity);
+    }
+
+    @Override
+    public void delete(final Iterable<? extends SubjectEntity> entities) {
+        this.activeRepository.delete(entities);
+    }
+
+    @Override
+    public void deleteAll() {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+
+    @Override
+    public SubjectEntity getSubjectWithInheritedAttributesForScopes(final ZoneEntity zone,
+            final String subjectIdentifier, final Set<Attribute> scopes) {
+        if (this.activeRepository == this.graphRepository) { // i.e. titan is enabled
+            return this.graphRepository.getSubjectWithInheritedAttributesForScopes(zone, subjectIdentifier, scopes);
+        } else {
+            return this.nonGraphRepository.getByZoneAndSubjectIdentifier(zone, subjectIdentifier);
+        }
+    }
+
+    @Override
+    public SubjectEntity getSubjectWithInheritedAttributes(final ZoneEntity zone, final String subjectIdentifier) {
+        if (this.activeRepository == this.graphRepository) { // i.e. titan is enabled
+            return this.graphRepository.getSubjectWithInheritedAttributes(zone, subjectIdentifier);
+        } else {
+            return this.nonGraphRepository.getByZoneAndSubjectIdentifier(zone, subjectIdentifier);
+        }
+    }
+
+    @Override
+    public List<SubjectEntity> findByZone(final ZoneEntity zone) {
+        return this.activeRepository.findByZone(zone);
+    }
+
+    @Override
+    public SubjectEntity getByZoneAndSubjectIdentifier(final ZoneEntity zone, final String subjectIdentifier) {
+        return this.activeRepository.getByZoneAndSubjectIdentifier(zone, subjectIdentifier);
+    }
+
+    @Override
+    public Set<String> getSubjectEntityAndDescendantsIds(final SubjectEntity entity) {
+        if (this.activeRepository == this.graphRepository) { // i.e. titan is enabled
+            return this.graphRepository.getSubjectEntityAndDescendantsIds(entity);
+        } else {
+            return Collections.singleton(entity.getSubjectIdentifier());
+        }
+    }
+
+    @Override
+    public <S extends SubjectEntity> boolean exists(final Example<S> example) {
+        throw new UnsupportedOperationException(MESSAGE);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/TitanMigrationManager.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/TitanMigrationManager.java
new file mode 100644
index 0000000..1a144aa
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/TitanMigrationManager.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.concurrent.Executors;
+
+@Component
+@Profile("titan")
+public final class TitanMigrationManager {
+    public static final int INITIAL_ATTRIBUTE_GRAPH_VERSION = 1;
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(TitanMigrationManager.class);
+
+    @Autowired
+    @Qualifier("resourceRepository")
+    private ResourceRepository resourceRepository;
+
+    @Autowired
+    @Qualifier("resourceHierarchicalRepository")
+    private GraphResourceRepository resourceHierarchicalRepository;
+
+    @Autowired
+    @Qualifier("subjectRepository")
+    private SubjectRepository subjectRepository;
+
+    @Autowired
+    @Qualifier("subjectHierarchicalRepository")
+    private GraphSubjectRepository subjectHierarchicalRepository;
+
+    private Boolean isMigrationComplete = false;
+
+    static final int PAGE_SIZE = 1024;
+
+    private final ResourceMigrationManager resourceMigrationManager = new ResourceMigrationManager();
+    private final SubjectMigrationManager subjectMigrationManager = new SubjectMigrationManager();
+
+    @PostConstruct
+    public void doMigration() {
+        // This version vertex is common to both subject and resource repositories. So this check is sufficient to
+        // trigger migrations in both repos.
+        if (!this.resourceHierarchicalRepository.checkVersionVertexExists(INITIAL_ATTRIBUTE_GRAPH_VERSION)) {
+
+            // Migration needs to be performed in a separate thread to prevent cloud-foundry health check timeout,
+            // which restarts the service. (Max timeout is 180 seconds which is not enough)
+            Executors.newSingleThreadExecutor().execute(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        LOGGER.info("Starting attribute migration process to Titan.");
+
+                        //Rollback in the beginning to start with a clean state
+                        resourceMigrationManager.rollbackMigratedData(resourceHierarchicalRepository);
+                        subjectMigrationManager.rollbackMigratedData(subjectHierarchicalRepository);
+
+                        //Run migration
+                        resourceMigrationManager.doResourceMigration(resourceRepository,
+                                resourceHierarchicalRepository, PAGE_SIZE);
+                        subjectMigrationManager.doSubjectMigration(subjectRepository,
+                                subjectHierarchicalRepository, PAGE_SIZE);
+
+                        //Create version vertex, to record completion.
+                        resourceHierarchicalRepository.createVersionVertex(INITIAL_ATTRIBUTE_GRAPH_VERSION);
+                        isMigrationComplete = true;
+
+                        LOGGER.info("Titan attribute migration complete. Created version: "
+                                + INITIAL_ATTRIBUTE_GRAPH_VERSION);
+                    } catch (Exception e) {
+                        LOGGER.error("Exception during attribute migration: ", e);
+                    }
+                }
+            });
+        } else {
+            isMigrationComplete = true;
+            LOGGER.info("Attribute Graph migration not required.");
+        }
+    }
+
+    public boolean isMigrationComplete() {
+        return this.isMigrationComplete;
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ZonableEntity.java b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ZonableEntity.java
new file mode 100644
index 0000000..1a9028e
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/privilege/management/dao/ZonableEntity.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.Parent;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+import java.util.Set;
+
+public interface ZonableEntity {
+    Long getId();
+
+    String getEntityId();
+
+    String getEntityType();
+
+    void setId(Long id);
+
+    ZoneEntity getZone();
+
+    void setZone(ZoneEntity zone);
+
+    Set<Attribute> getAttributes();
+
+    void setAttributes(Set<Attribute> attributes);
+
+    String getAttributesAsJson();
+
+    void setAttributesAsJson(String attributesAsJson);
+
+    Set<Parent> getParents();
+
+    void setParents(Set<Parent> parentIdentifiers);
+}
diff --git a/service/src/main/java/com/ge/predix/acs/request/context/AcsRequestContext.java b/service/src/main/java/com/ge/predix/acs/request/context/AcsRequestContext.java
new file mode 100644
index 0000000..b505289
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/request/context/AcsRequestContext.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.request.context;
+
+import java.util.Map;
+
+public class AcsRequestContext {
+
+    private final Map<ACSRequestContextAttribute, Object> unModifiableRequestContextMap;
+
+    public enum ACSRequestContextAttribute {
+        ZONE_ENTITY;
+    }
+
+    // Hide Constructor
+    AcsRequestContext(final Map<ACSRequestContextAttribute, Object> unModifiableRequestContextMap) {
+        this.unModifiableRequestContextMap = unModifiableRequestContextMap;
+    }
+
+    public Object get(final ACSRequestContextAttribute acsRequestContextEnum) {
+        return this.unModifiableRequestContextMap.get(acsRequestContextEnum);
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/request/context/AcsRequestContextHolder.java b/service/src/main/java/com/ge/predix/acs/request/context/AcsRequestContextHolder.java
new file mode 100644
index 0000000..9111f44
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/request/context/AcsRequestContextHolder.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.request.context;
+
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.request.context.AcsRequestContext.ACSRequestContextAttribute;
+import com.ge.predix.acs.zone.management.dao.ZoneRepository;
+import com.ge.predix.uaa.token.lib.ZoneOAuth2Authentication;
+
+/**
+ * A ThreadLocal store for the ACS Request Context.
+ *
+ * @author acs-engineers@ge.com
+ */
+@Component
+public final class AcsRequestContextHolder implements ApplicationContextAware {
+    private static final Logger LOGGER = LoggerFactory.getLogger(AcsRequestContextHolder.class);
+    private static final InheritableThreadLocal<AcsRequestContext> ACS_REQUEST_CONTEXT_STORE = new
+            InheritableThreadLocal<>();
+
+    // Hide Constructor
+    private AcsRequestContextHolder() {
+    }
+
+    // Can only be called by the filter...
+    // Filter calls it to create the ACSRequestContext for a request
+    static void initialize() {
+        ACS_REQUEST_CONTEXT_STORE.set(new AcsRequestContextBuilder().zoneEntityOrFail().build());
+    }
+
+    // Can only be called by the filter...
+    static void clear() {
+        ACS_REQUEST_CONTEXT_STORE.remove();
+    }
+
+    // Only public interface to be used by the clients to access the AcsRequestContext specific to the
+    // Request...
+    public static AcsRequestContext getAcsRequestContext() {
+        return ACS_REQUEST_CONTEXT_STORE.get();
+    }
+
+    @Override
+    public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
+        LOGGER.trace("AcsRequestContextHolder Initialized");
+        AcsRequestContextBuilder.initAcsRequestContextBuilderRepos(applicationContext);
+    }
+
+    /**
+     * The Fluent implementation to create the {@link AcsRequestContext} for the current AcsRequest...
+     *
+     * @author acs-engineers@ge.com
+     */
+    private static final class AcsRequestContextBuilder {
+        private static ZoneRepository zoneRepository;
+        private static final Logger LOGGER = LoggerFactory.getLogger(AcsRequestContextBuilder.class);
+        private final Map<ACSRequestContextAttribute, Object> requestContextMap;
+
+        AcsRequestContextBuilder() {
+            this.requestContextMap = new EnumMap<>(ACSRequestContextAttribute.class);
+        }
+
+        static void initAcsRequestContextBuilderRepos(final ApplicationContext applicationContext) {
+            zoneRepository = applicationContext.getBean(ZoneRepository.class);
+            LOGGER.info("AcsRequestContextBuilder JPA Repositories Initialized");
+        }
+
+        AcsRequestContextBuilder zoneEntityOrFail() {
+            ZoneOAuth2Authentication zoneAuth = (ZoneOAuth2Authentication) SecurityContextHolder.getContext()
+                    .getAuthentication();
+            this.requestContextMap
+                    .put(ACSRequestContextAttribute.ZONE_ENTITY, zoneRepository.getBySubdomain(zoneAuth.getZoneId()));
+            return this;
+        }
+
+        AcsRequestContext build() {
+            return new AcsRequestContext(Collections.unmodifiableMap(this.requestContextMap));
+        }
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/request/context/AcsRequestEnrichingFilter.java b/service/src/main/java/com/ge/predix/acs/request/context/AcsRequestEnrichingFilter.java
new file mode 100644
index 0000000..8f8e8d0
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/request/context/AcsRequestEnrichingFilter.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.request.context;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+@Component
+public class AcsRequestEnrichingFilter extends OncePerRequestFilter {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AcsRequestEnrichingFilter.class);
+
+    @Override
+    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
+            final FilterChain filterChain) throws ServletException, IOException {
+        try {
+            try {
+                AcsRequestContextHolder.initialize();
+                LOGGER.trace("Initialized the Acs Request Context");
+
+            } catch (RuntimeException e) { // Ensure that the filter chain is not aborted.
+                LOGGER.error("AcsRequestContext Initialization failed:Let the request go on irrespective.", e);
+            }
+            filterChain.doFilter(request, response);
+        } finally {
+            // Very Critical...to recycle the Thread to the pool safely
+            AcsRequestContextHolder.clear();
+            LOGGER.trace("Cleared the Acs Request Context");
+        }
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/security/AbstractHttpMethodsFilter.java b/service/src/main/java/com/ge/predix/acs/security/AbstractHttpMethodsFilter.java
new file mode 100644
index 0000000..4f68413
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/security/AbstractHttpMethodsFilter.java
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.security;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.util.MimeType;
+import org.springframework.util.MimeTypeUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import com.google.common.net.HttpHeaders;
+
+public abstract class AbstractHttpMethodsFilter extends OncePerRequestFilter {
+
+    private final Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods;
+
+    private static final Logger LOGGER_INSTANCE = LoggerFactory.getLogger(AbstractHttpMethodsFilter.class);
+    private static final Set<MimeType> ACCEPTABLE_MIME_TYPES =
+            new HashSet<>(Arrays.asList(MimeTypeUtils.ALL, MimeTypeUtils.APPLICATION_JSON, MimeTypeUtils.TEXT_PLAIN));
+
+    public AbstractHttpMethodsFilter(final Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods) {
+        this.uriPatternsAndAllowedHttpMethods = Collections.unmodifiableMap(uriPatternsAndAllowedHttpMethods);
+    }
+
+    private static void addCommonResponseHeaders(final HttpServletResponse response) {
+        if (!response.containsHeader(HttpHeaders.X_CONTENT_TYPE_OPTIONS)) {
+            response.addHeader(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff");
+        }
+    }
+
+    private static void sendMethodNotAllowedError(final HttpServletResponse response) throws IOException {
+        addCommonResponseHeaders(response);
+        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, HttpStatus.METHOD_NOT_ALLOWED.getReasonPhrase());
+    }
+
+    private static void sendNotAcceptableError(final HttpServletResponse response) throws IOException {
+        addCommonResponseHeaders(response);
+        response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE, HttpStatus.NOT_ACCEPTABLE.getReasonPhrase());
+    }
+
+    @Override
+    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
+            final FilterChain filterChain) throws ServletException, IOException {
+
+        String requestMethod = request.getMethod();
+
+        if (HttpMethod.TRACE.matches(requestMethod)) {
+            sendMethodNotAllowedError(response);
+            return;
+        }
+
+        String requestUri = request.getRequestURI();
+
+        if (!HttpMethod.OPTIONS.matches(requestMethod)) {
+            for (Map.Entry<String,
+                    Set<HttpMethod>> uriPatternsAndAllowedHttpMethodsEntry : this.uriPatternsAndAllowedHttpMethods
+                            .entrySet()) {
+                if (Pattern.compile(uriPatternsAndAllowedHttpMethodsEntry.getKey()).matcher(requestUri).matches()) {
+                    if (!uriPatternsAndAllowedHttpMethodsEntry.getValue().contains(HttpMethod.resolve(requestMethod))) {
+                        sendMethodNotAllowedError(response);
+                        return;
+                    }
+
+                    String acceptHeaderValue = request.getHeader(HttpHeaders.ACCEPT);
+                    if (acceptHeaderValue != null) {
+                        try {
+                            List<MimeType> parsedMimeTypes = MimeTypeUtils.parseMimeTypes(acceptHeaderValue);
+                            boolean foundAcceptableMimeType = false;
+                            for (MimeType parsedMimeType : parsedMimeTypes) {
+                                // When checking for acceptable MIME types, strip out the character set
+                                if (ACCEPTABLE_MIME_TYPES.contains(
+                                        new MimeType(parsedMimeType.getType(), parsedMimeType.getSubtype()))) {
+                                    foundAcceptableMimeType = true;
+                                    break;
+                                }
+                            }
+                            if (!foundAcceptableMimeType) {
+                                LOGGER_INSTANCE.error("Malformed Accept header sent in request: {}", acceptHeaderValue);
+                                sendNotAcceptableError(response);
+                                return;
+                            }
+                        } catch (Exception e) {
+                            sendNotAcceptableError(response);
+                            return;
+                        }
+                    }
+
+                    break;
+                }
+            }
+        }
+        filterChain.doFilter(request, response);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/security/EmptyHttpMethodsFilter.java b/service/src/main/java/com/ge/predix/acs/security/EmptyHttpMethodsFilter.java
new file mode 100644
index 0000000..86911ff
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/security/EmptyHttpMethodsFilter.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.security;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.web.filter.OncePerRequestFilter;
+
+public class EmptyHttpMethodsFilter extends OncePerRequestFilter {
+
+    @Override
+    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
+            final FilterChain filterChain) throws ServletException, IOException {
+
+        filterChain.doFilter(request, response);
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/security/NoAuthenticationEntryPoint.java b/service/src/main/java/com/ge/predix/acs/security/NoAuthenticationEntryPoint.java
new file mode 100644
index 0000000..1f8b390
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/security/NoAuthenticationEntryPoint.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.security;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+@Component
+public class NoAuthenticationEntryPoint implements AuthenticationEntryPoint {
+
+    @Override
+    public void commence(final HttpServletRequest request, final HttpServletResponse response,
+            final AuthenticationException authException) throws IOException, ServletException {
+        // Purposely empty
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/InvalidACSRequestException.java b/service/src/main/java/com/ge/predix/acs/service/InvalidACSRequestException.java
new file mode 100644
index 0000000..e14ad64
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/InvalidACSRequestException.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+public class InvalidACSRequestException extends RuntimeException {
+
+    private static final long serialVersionUID = 8975205406157160522L;
+
+    public InvalidACSRequestException() {
+    }
+
+    public InvalidACSRequestException(final String message) {
+        super(message);
+    }
+
+    public InvalidACSRequestException(final Throwable cause) {
+        super(cause);
+    }
+
+    public InvalidACSRequestException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    public InvalidACSRequestException(final String message, final Throwable cause, final boolean enableSuppression,
+            final boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/admin/PolicyHttpMethodsFilter.java b/service/src/main/java/com/ge/predix/acs/service/policy/admin/PolicyHttpMethodsFilter.java
new file mode 100644
index 0000000..5f68dbf
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/admin/PolicyHttpMethodsFilter.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.admin;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.security.AbstractHttpMethodsFilter;
+
+@Component
+public class PolicyHttpMethodsFilter extends AbstractHttpMethodsFilter {
+
+    private static final String UPDATE_POLICY_URI_REGEX = "\\A/v1/policy-set/[^/]+?/??\\Z";
+    private static final String GET_POLICY_URI_REGEX = "\\A/v1/policy-set/??\\Z";
+
+    private static Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods() {
+        Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods = new LinkedHashMap<>();
+        uriPatternsAndAllowedHttpMethods.put(UPDATE_POLICY_URI_REGEX,
+                new HashSet<>(Arrays.asList(HttpMethod.PUT, HttpMethod.GET, HttpMethod.DELETE, HttpMethod.HEAD)));
+        uriPatternsAndAllowedHttpMethods.put(GET_POLICY_URI_REGEX,
+                new HashSet<>(Arrays.asList(HttpMethod.GET, HttpMethod.HEAD)));
+        return uriPatternsAndAllowedHttpMethods;
+    }
+
+    public PolicyHttpMethodsFilter() {
+        super(uriPatternsAndAllowedHttpMethods());
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/admin/PolicyManagementController.java b/service/src/main/java/com/ge/predix/acs/service/policy/admin/PolicyManagementController.java
new file mode 100644
index 0000000..88c153f
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/admin/PolicyManagementController.java
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.admin;
+
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.POLICY_SETS_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.POLICY_SET_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.V1;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.created;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.noContent;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.notFound;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.ok;
+import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
+import static org.springframework.web.bind.annotation.RequestMethod.GET;
+import static org.springframework.web.bind.annotation.RequestMethod.PUT;
+
+import java.net.URI;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.ge.predix.acs.commons.web.BaseRestApi;
+import com.ge.predix.acs.commons.web.RestApiException;
+import com.ge.predix.acs.commons.web.UriTemplateUtils;
+import com.ge.predix.acs.model.PolicySet;
+
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+
+/**
+ * @author acs-engineers@ge.com
+ */
+@RestController
+@RequestMapping(value = { V1 })
+public class PolicyManagementController extends BaseRestApi {
+
+    @Autowired
+    private PolicyManagementService service;
+
+    @ApiOperation(value = "Creates/Updates a policy set for the given zone.", tags = { "Policy Set Management" })
+    @ApiResponses(value = { @ApiResponse(code = 201,
+            message = "Policy set creation successful. Policy set URI is returned in 'Location' header."), })
+    @RequestMapping(method = PUT, value = POLICY_SET_URL, consumes = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<String> createPolicySet(@RequestBody final PolicySet policySet,
+            @PathVariable("policySetId") final String policySetId) {
+
+        validatePolicyIdOrFail(policySet, policySetId);
+
+        try {
+            this.service.upsertPolicySet(policySet);
+            URI policySetUri = UriTemplateUtils.expand(POLICY_SET_URL, "policySetId:" + policySet.getName());
+            return created(policySetUri.getPath());
+        } catch (PolicyManagementException e) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage(), e);
+        }
+    }
+
+    @ApiOperation(value = "Retrieves a policy set for the given zone.", tags = { "Policy Set Management" })
+    @RequestMapping(method = GET, value = POLICY_SET_URL)
+    @ResponseBody
+    public ResponseEntity<PolicySet> getPolicySet(@PathVariable("policySetId") final String name) {
+        PolicySet result = this.service.getPolicySet(name);
+
+        if (null != result) {
+            return ok(result);
+        }
+
+        return notFound();
+    }
+
+    @ApiOperation(value = "Deletes a policy set for the given zone.", tags = { "Policy Set Management" })
+    @RequestMapping(method = DELETE, value = POLICY_SET_URL)
+    public ResponseEntity<Void> deletePolicySet(@PathVariable("policySetId") final String name) {
+        this.service.deletePolicySet(name);
+        return noContent();
+    }
+
+    @ApiOperation(value = "Returns all the policy sets for the given zone.", tags = { "Policy Set Management" })
+    @RequestMapping(method = GET, value = POLICY_SETS_URL, produces = MediaType.APPLICATION_JSON_VALUE)
+    @ResponseBody
+    public ResponseEntity<List<PolicySet>> getAllPolicySets() {
+        List<PolicySet> allPolicySets = this.service.getAllPolicySets();
+        return ok(allPolicySets);
+    }
+
+    /**
+     * @param name
+     * @param policySetId
+     */
+    private void validatePolicyIdOrFail(final PolicySet policySet, final String policySetId) {
+        if (policySet == null) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY, "Policy Set cannot be empty or null");
+        }
+
+        String name = policySet.getName();
+        if (!StringUtils.isEmpty(name) && !policySetId.equals(name)) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY,
+                    String.format("Policy Set name in the payload = %s, does not match the one provided in URI = %s",
+                            name, policySetId));
+        }
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/admin/PolicyManagementException.java b/service/src/main/java/com/ge/predix/acs/service/policy/admin/PolicyManagementException.java
new file mode 100644
index 0000000..2574d3c
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/admin/PolicyManagementException.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.admin;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+public class PolicyManagementException extends RuntimeException {
+
+    private static final long serialVersionUID = 6154656025814293326L;
+
+    public PolicyManagementException(final String message, final Throwable cause, final boolean enableSuppression,
+            final boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+    public PolicyManagementException(final Throwable cause) {
+        super(cause);
+    }
+
+    public PolicyManagementException() {
+        super();
+    }
+
+    public PolicyManagementException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    public PolicyManagementException(final String message) {
+        super(message);
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/admin/PolicyManagementService.java b/service/src/main/java/com/ge/predix/acs/service/policy/admin/PolicyManagementService.java
new file mode 100644
index 0000000..8328beb
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/admin/PolicyManagementService.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.admin;
+
+import com.ge.predix.acs.model.PolicySet;
+
+import java.util.List;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+public interface PolicyManagementService {
+
+    /**
+     * @throws IllegalArgumentException
+     *             If policy format is not valid or PolicySet.name is not set.
+     */
+    void upsertPolicySet(PolicySet policySet) throws IllegalArgumentException;
+
+    /**
+     * @return PolicySet for given name, null if no such set exists.
+     */
+    PolicySet getPolicySet(String policySetName);
+
+    /**
+     * Deletes a policy set.
+     *
+     * @param policySetID
+     *            policy set ID
+     */
+    void deletePolicySet(String policySetID);
+
+    /**
+     * Get all policy sets.
+     *
+     * @return the list of policySets
+     */
+    List<PolicySet> getAllPolicySets();
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/admin/PolicyManagementServiceImpl.java b/service/src/main/java/com/ge/predix/acs/service/policy/admin/PolicyManagementServiceImpl.java
new file mode 100644
index 0000000..d294fff
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/admin/PolicyManagementServiceImpl.java
@@ -0,0 +1,183 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.admin;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.transaction.Transactional;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.commons.exception.UntrustedIssuerException;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.policy.evaluation.cache.PolicyEvaluationCache;
+import com.ge.predix.acs.service.policy.admin.dao.PolicySetEntity;
+import com.ge.predix.acs.service.policy.admin.dao.PolicySetRepository;
+import com.ge.predix.acs.service.policy.validation.PolicySetValidationException;
+import com.ge.predix.acs.service.policy.validation.PolicySetValidator;
+import com.ge.predix.acs.utils.JsonUtils;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.ge.predix.acs.zone.resolver.ZoneResolver;
+
+/**
+ * @author acs-engineers@ge.com
+ */
+@Component
+@SuppressWarnings("nls")
+public class PolicyManagementServiceImpl implements PolicyManagementService {
+    private static final Logger LOGGER = LoggerFactory.getLogger(PolicyManagementServiceImpl.class);
+
+    @Autowired
+    private PolicyEvaluationCache cache;
+    @Autowired
+    private PolicySetRepository policySetRepository;
+    @Autowired
+    private ZoneResolver zoneResolver;
+    @Autowired
+    private PolicySetValidator policySetValidator;
+    private final JsonUtils jsonUtils = new JsonUtils();
+
+    @Override
+    public void upsertPolicySet(final PolicySet policySet) {
+
+        String policySetName = policySet.getName();
+
+        try {
+            ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+
+            validatePolicySet(zone, policySet);
+
+            String policySetPayload = this.jsonUtils.serialize(policySet);
+            upsertPolicySetInTransaction(policySetName, zone, policySetPayload);
+        } catch (Exception e) {
+            handleException(e, policySetName);
+        }
+    }
+
+    @Transactional
+    private void upsertPolicySetInTransaction(final String policySetName, final ZoneEntity zone,
+            final String policySetPayload) {
+        PolicySetEntity existingPolicySetEntity = this.policySetRepository.getByZoneAndPolicySetId(zone, policySetName);
+        PolicySetEntity policySetEntity = new PolicySetEntity(zone, policySetName, policySetPayload);
+
+        // If policy Set already exists, set PK of entity for update
+        if (null != existingPolicySetEntity) {
+            LOGGER.debug("Found an existing policy set policySetName = {}, zone = {}, upserting now .", policySetName,
+                    zone);
+            policySetEntity.setId(existingPolicySetEntity.getId());
+        } else {
+            LOGGER.debug("No existing policy set found for policySetName = {},  zone = {}, inserting now .",
+                    policySetName, zone);
+        }
+
+        this.cache.resetForPolicySet(zone.getName(), policySetName);
+        this.policySetRepository.save(policySetEntity);
+    }
+
+    private void handleException(final Exception e, final String policySetName) {
+
+        String message = String
+                .format("Creation of Policy set %s failed with the following error %s", policySetName, e.getMessage());
+        LOGGER.error(message, e);
+
+        if (e instanceof UntrustedIssuerException || e instanceof PolicyManagementException) {
+            throw (RuntimeException) e;
+        }
+        throw new PolicyManagementException(message, e);
+    }
+
+    @Override
+    public PolicySet getPolicySet(final String policySetName) {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+        PolicySetEntity policySetEntity = this.policySetRepository.getByZoneAndPolicySetId(zone, policySetName);
+        if (policySetEntity != null) {
+            return this.jsonUtils.deserialize(policySetEntity.getPolicySetJson(), PolicySet.class);
+        }
+        LOGGER.debug("No policy set found for policySetName = {},  zone = {}.", policySetName, zone);
+        return null;
+    }
+
+    @Override
+    public List<PolicySet> getAllPolicySets() {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+        ArrayList<PolicySet> result = new ArrayList<>();
+        List<PolicySetEntity> policySetEnityList = this.policySetRepository.findByZone(zone);
+        for (PolicySetEntity policySetEntity : policySetEnityList) {
+            result.add(this.jsonUtils.deserialize(policySetEntity.getPolicySetJson(), PolicySet.class));
+        }
+        return result;
+    }
+
+    @Override
+    public void deletePolicySet(final String policySetId) {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+        PolicySetEntity policySetEntity = this.policySetRepository.getByZoneAndPolicySetId(zone, policySetId);
+        if (policySetEntity != null) {
+            LOGGER.info("Found an existing policy set policySetName={}, zone={}, deleting now.", policySetId,
+                    zone.getName());
+
+            PolicySet policySet = this.jsonUtils.deserialize(policySetEntity.getPolicySetJson(), PolicySet.class);
+            if (policySet != null) {
+                this.policySetValidator.removeCachedConditions(policySet);
+            }
+
+            // Since we only support one policy set and we don't want to load that policy set when checking for a
+            // cached invalidation, we use a hard-coded value for the policy set key.
+            this.cache.resetForPolicySet(zone.getName(), policySetId);
+            this.policySetRepository.delete(policySetEntity);
+        } else {
+            LOGGER.debug("Cound not find an existing policy set " + "policySetName={}, zone={}, Could not delete it.",
+                    policySetId, zone.getName());
+        }
+    }
+
+    private void validatePolicySet(final ZoneEntity zone, final PolicySet policySet) {
+        String policySetName = policySet.getName();
+        if (StringUtils.isEmpty(policySetName)) {
+            throw new PolicyManagementException("Failed to add policy set because the policy set name is missing");
+        }
+
+        // Validate whether the policy set name is URI acceptable.
+        try {
+            URI policySetNameURI = new URI(policySetName);
+            LOGGER.debug("Creating policy set '{}'.", policySetNameURI);
+        } catch (URISyntaxException e1) {
+            LOGGER.debug("Failed to add policy set for policySetName = {},  " + "zone = {}, inserting now.",
+                    policySet.getName(), zone);
+            throw new PolicyManagementException(
+                    String.format("Failed to add policy set because the policy set name '%s' is not URI friendly.",
+                            policySet.getName()), e1);
+        }
+
+        try {
+            this.policySetValidator.validatePolicySet(policySet);
+        } catch (PolicySetValidationException e) {
+            LOGGER.debug("Policy Validation Failed policySetName = {}, zone = {}.", policySet.getName(), zone);
+            throw new PolicyManagementException(e.getMessage(), e);
+        }
+
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/admin/dao/PolicySetEntity.java b/service/src/main/java/com/ge/predix/acs/service/policy/admin/dao/PolicySetEntity.java
new file mode 100644
index 0000000..41d6399
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/admin/dao/PolicySetEntity.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.admin.dao;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings({ "nls", "javadoc" })
+@Entity
+@Table(
+        name = "policy_set",
+        uniqueConstraints = { @UniqueConstraint(columnNames = { "authorization_zone_id", "policy_set_id" }) })
+public class PolicySetEntity {
+
+    @Id
+    @Column(name = "id")
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private long id;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "authorization_zone_id", referencedColumnName = "id", nullable = false, updatable = false)
+    private ZoneEntity zone;
+
+    /** ID unique per client-id and issuer combination. */
+    @Column(name = "policy_set_id", nullable = false)
+    private String policySetId;
+
+    /** Clob representing the JSON policy set. */
+    @Column(name = "policy_set_json", columnDefinition = "CLOB NOT NULL")
+    private String policySetJson;
+
+    public PolicySetEntity(final ZoneEntity zone, final String policySetID, final String policySetJson) {
+        super();
+        this.zone = zone;
+        this.policySetId = policySetID;
+        this.policySetJson = policySetJson;
+    }
+
+    public PolicySetEntity() {
+        super();
+    }
+
+    public long getId() {
+        return this.id;
+    }
+
+    public void setId(final long id) {
+        this.id = id;
+    }
+
+    public String getPolicySetID() {
+        return this.policySetId;
+    }
+
+    public String getPolicySetJson() {
+        return this.policySetJson;
+    }
+
+    @Override
+    public String toString() {
+        return "PolicySetEntity [id=" + this.id + ", zone=" + this.zone + ", policySetId=" + this.policySetId
+                + ", policySetJson=" + this.policySetJson + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(this.zone).append(this.policySetId).toHashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+
+        if (obj instanceof PolicySetEntity) {
+            final PolicySetEntity other = (PolicySetEntity) obj;
+            return new EqualsBuilder().append(this.zone, other.zone).append(this.policySetId, other.policySetId)
+                    .isEquals();
+        }
+        return false;
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/admin/dao/PolicySetRepository.java b/service/src/main/java/com/ge/predix/acs/service/policy/admin/dao/PolicySetRepository.java
new file mode 100644
index 0000000..e83d87e
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/admin/dao/PolicySetRepository.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.admin.dao;
+
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings("javadoc")
+public interface PolicySetRepository extends JpaRepository<PolicySetEntity, Long> {
+    /**
+     * JPA CRUD operations generated by Spring JPA repository implementation.
+     */
+
+    PolicySetEntity getByZoneAndPolicySetId(ZoneEntity zone, String policySetId);
+
+    List<PolicySetEntity> findByZone(ZoneEntity zone);
+
+    Long countByZone(ZoneEntity zone);
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/AttributesHandler.java b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/AttributesHandler.java
new file mode 100644
index 0000000..54d7bab
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/AttributesHandler.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.ge.predix.acs.model.Attribute;
+
+/**
+ * @author acs-engineers@ge.com
+ */
+public class AttributesHandler {
+    private final Map<String, String> attributeMap = new HashMap<>();
+
+    /**
+     * Default constructor.
+     *
+     * @param attributes
+     *            attribute list
+     */
+    public AttributesHandler(final List<Attribute> attributes) {
+        for (Attribute attributeValue : attributes) {
+            this.attributeMap.put(getKey(attributeValue.getIssuer(), attributeValue.getName()),
+                    attributeValue.getValue());
+        }
+    }
+
+    /**
+     * @param issuer
+     * @param name
+     * @return
+     */
+    private String getKey(final String issuer, final String name) {
+        return issuer + name;
+    }
+
+    /**
+     * Get an attribute value.
+     *
+     * @param issuer
+     *            attribute issuer
+     * @param name
+     *            attribute name
+     * @return attribute value
+     */
+    public String attributes(final String issuer, final String name) {
+        return this.attributeMap.get(getKey(issuer, name));
+    }
+
+    /**
+     * @param issuer
+     *            claim issuer
+     * @param name
+     *            claim name
+     * @param value
+     *            claim value
+     * @return true if has claim
+     */
+    public boolean hasAttribute(final String issuer, final String name, final String value) {
+        return this.attributeMap.containsKey(getKey(issuer, name));
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/EvaluationHttpMethodsFilter.java b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/EvaluationHttpMethodsFilter.java
new file mode 100644
index 0000000..4db858e
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/EvaluationHttpMethodsFilter.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.security.AbstractHttpMethodsFilter;
+
+@Component
+public class EvaluationHttpMethodsFilter extends AbstractHttpMethodsFilter {
+
+    private static final String EVALUATION_URI_REGEX = "\\A/v1/policy-evaluation/??\\Z";
+
+    private static Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods() {
+        Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods = new LinkedHashMap<>();
+        uriPatternsAndAllowedHttpMethods.put(EVALUATION_URI_REGEX, Collections.singleton(HttpMethod.POST));
+        return uriPatternsAndAllowedHttpMethods;
+    }
+
+    public EvaluationHttpMethodsFilter() {
+        super(uriPatternsAndAllowedHttpMethods());
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/MatchedPolicy.java b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/MatchedPolicy.java
new file mode 100644
index 0000000..cfa68d0
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/MatchedPolicy.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import java.util.Set;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Policy;
+
+/**
+ * Holding class for a matched policy and its resource attributes, based on any attributeUriTemplate in policy target.
+ */
+public class MatchedPolicy {
+
+    private Policy policy;
+    private Set<Attribute> resourceAttributes;
+    private Set<Attribute> subjectAttributes;
+
+    public MatchedPolicy(final Policy policy, final Set<Attribute> resourceAttributes) {
+        this.policy = policy;
+        this.resourceAttributes = resourceAttributes;
+    }
+
+    public MatchedPolicy(final Policy policy, final Set<Attribute> resourceAttributes,
+            final Set<Attribute> subjectAttributes) {
+        this.policy = policy;
+        this.resourceAttributes = resourceAttributes;
+        this.subjectAttributes = subjectAttributes;
+    }
+
+    public Policy getPolicy() {
+        return this.policy;
+    }
+
+    public void setPolicy(final Policy policy) {
+        this.policy = policy;
+    }
+
+    public Set<Attribute> getResourceAttributes() {
+        return this.resourceAttributes;
+    }
+
+    public void setResourceAttributes(final Set<Attribute> resourceAttributes) {
+        this.resourceAttributes = resourceAttributes;
+    }
+
+    public Set<Attribute> getSubjectAttributes() {
+        return this.subjectAttributes;
+    }
+
+    public void setSubjectAttributes(final Set<Attribute> subjectAttributes) {
+        this.subjectAttributes = subjectAttributes;
+    }
+
+    @Override
+    public String toString() {
+        return "MatchedPolicy [policy=" + this.policy + ", resourceAttributes=" + this.resourceAttributes
+                + ", subjectAttributes=" + this.subjectAttributes + "]";
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluation.java b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluation.java
new file mode 100644
index 0000000..af97fe5
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluation.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import org.springframework.http.ResponseEntity;
+
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+
+/**
+ * RESTful API interface of the Policy Evaluation API.
+ *
+ * @author acs-engineers@ge.com
+ */
+@FunctionalInterface
+public interface PolicyEvaluation {
+
+    /**
+     * Takes the given access control request and determines if it is allowed or denied, according to the stored
+     * policies. It Delegates the policy evaluation to the PolicyEvaluationServices.
+     *
+     * @param request
+     *            Includes the resource url, action and subject to be checked.
+     * @return a policy evaluation result with allow or deny
+     */
+    ResponseEntity<PolicyEvaluationResult> evalPolicyV1(PolicyEvaluationRequestV1 request);
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationController.java b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationController.java
new file mode 100644
index 0000000..0b53a03
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationController.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.POLICY_EVALUATION_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.V1;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.ok;
+import static org.springframework.web.bind.annotation.RequestMethod.POST;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.ge.predix.acs.commons.web.BaseRestApi;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+
+/**
+ * Policy evaluator REST controller.
+ *
+ * @author acs-engineers@ge.com
+ */
+@RestController
+public class PolicyEvaluationController extends BaseRestApi {
+
+    @Autowired
+    private PolicyEvaluationService service;
+
+    @ApiOperation(value = "Evaluates all applicable policies and returns decision result",
+            tags = { "Policy Evaluation" }, response = PolicyEvaluationResult.class)
+    @ApiResponses(value = { @ApiResponse(code = 200, message = "Policy evaluation was successful",
+            response = PolicyEvaluationResult.class), })
+    @RequestMapping(method = POST, value = V1 + POLICY_EVALUATION_URL, consumes = MediaType.APPLICATION_JSON_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    @ResponseBody
+    public ResponseEntity<PolicyEvaluationResult> evalPolicyV1(@RequestBody final PolicyEvaluationRequestV1 request) {
+        return ok(this.service.evalPolicy(request));
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationException.java b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationException.java
new file mode 100644
index 0000000..c5618c9
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationException.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+public class PolicyEvaluationException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    public PolicyEvaluationException() {
+    }
+
+    public PolicyEvaluationException(final String message) {
+        super(message);
+    }
+
+    public PolicyEvaluationException(final Throwable cause) {
+        super(cause);
+    }
+
+    public PolicyEvaluationException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    public PolicyEvaluationException(final String message, final Throwable cause, final boolean enableSuppression,
+            final boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationService.java b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationService.java
new file mode 100644
index 0000000..76e8d84
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationService.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+
+@FunctionalInterface
+public interface PolicyEvaluationService {
+    PolicyEvaluationResult evalPolicy(PolicyEvaluationRequestV1 request);
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationServiceImpl.java b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationServiceImpl.java
new file mode 100644
index 0000000..33f26cd
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationServiceImpl.java
@@ -0,0 +1,336 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.attribute.readers.AttributeRetrievalException;
+import com.ge.predix.acs.commons.policy.condition.ConditionAssertionFailedException;
+import com.ge.predix.acs.commons.policy.condition.ConditionScript;
+import com.ge.predix.acs.commons.policy.condition.ResourceHandler;
+import com.ge.predix.acs.commons.policy.condition.SubjectHandler;
+import com.ge.predix.acs.commons.policy.condition.groovy.AttributeMatcher;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Condition;
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.model.Policy;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.model.Target;
+import com.ge.predix.acs.policy.evaluation.cache.PolicyEvaluationCache;
+import com.ge.predix.acs.policy.evaluation.cache.PolicyEvaluationRequestCacheKey;
+import com.ge.predix.acs.policy.evaluation.cache.PolicyEvaluationRequestCacheKey.Builder;
+import com.ge.predix.acs.privilege.management.dao.AttributeLimitExceededException;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+import com.ge.predix.acs.service.policy.admin.PolicyManagementService;
+import com.ge.predix.acs.service.policy.matcher.MatchResult;
+import com.ge.predix.acs.service.policy.matcher.PolicyMatchCandidate;
+import com.ge.predix.acs.service.policy.matcher.PolicyMatcher;
+import com.ge.predix.acs.service.policy.validation.PolicySetValidationException;
+import com.ge.predix.acs.service.policy.validation.PolicySetValidator;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.ge.predix.acs.zone.resolver.ZoneResolver;
+
+@Component
+@SuppressWarnings({ "javadoc", "nls" })
+public class PolicyEvaluationServiceImpl implements PolicyEvaluationService {
+    private static final Logger LOGGER = LoggerFactory.getLogger(PolicyEvaluationServiceImpl.class);
+
+    @Autowired
+    private PolicyEvaluationCache cache;
+    @Autowired
+    private PolicyManagementService policyService;
+    @Autowired
+    private PolicyMatcher policyMatcher;
+    @Autowired
+    private PolicySetValidator policySetValidator;
+    @Autowired
+    private ZoneResolver zoneResolver;
+
+    @Override
+    public PolicyEvaluationResult evalPolicy(final PolicyEvaluationRequestV1 request) {
+        ZoneEntity zone = this.zoneResolver.getZoneEntityOrFail();
+        String uri = request.getResourceIdentifier();
+        String subjectIdentifier = request.getSubjectIdentifier();
+        String action = request.getAction();
+        LinkedHashSet<String> policySetsEvaluationOrder = request.getPolicySetsEvaluationOrder();
+
+        if (uri == null || subjectIdentifier == null || action == null) {
+            LOGGER.error("Policy evaluation request is missing required input parameters: "
+                    + "resourceURI='{}' subjectIdentifier='{}' action='{}'", uri, subjectIdentifier, action);
+
+            throw new IllegalArgumentException("Policy evaluation request is missing required input parameters. "
+                    + "Please review and resubmit the request.");
+        }
+
+        List<PolicySet> allPolicySets = this.policyService.getAllPolicySets();
+
+        if (allPolicySets.isEmpty()) {
+            return new PolicyEvaluationResult(Effect.NOT_APPLICABLE);
+        }
+
+        LinkedHashSet<PolicySet> filteredPolicySets = filterPolicySetsByPriority(subjectIdentifier, uri, allPolicySets,
+                policySetsEvaluationOrder);
+
+        // At this point empty evaluation order means we have only one policy set.
+        // Fixing policy evaluation order so we could build a cache key.
+        PolicyEvaluationRequestCacheKey key;
+        if (policySetsEvaluationOrder.isEmpty()) {
+            key = new Builder().zoneId(zone.getName())
+                    .policySetIds(Stream.of(filteredPolicySets.iterator().next().getName())
+                            .collect(Collectors.toCollection(LinkedHashSet::new)))
+                    .request(request).build();
+        } else {
+            key = new Builder().zoneId(zone.getName()).request(request).build();
+        }
+
+        PolicyEvaluationResult result = null;
+        try {
+            result = this.cache.get(key);
+        } catch (Exception e) {
+            LOGGER.error(String.format("Unable to get cache key '%s'", key), e);
+        }
+
+        if (null == result) {
+            result = new PolicyEvaluationResult(Effect.NOT_APPLICABLE);
+
+            HashSet<Attribute> supplementalResourceAttributes;
+            if (null == request.getResourceAttributes()) {
+                supplementalResourceAttributes = new HashSet<>();
+            } else {
+                supplementalResourceAttributes = new HashSet<>(request.getResourceAttributes());
+            }
+            HashSet<Attribute> supplementalSubjectAttributes;
+            if (null == request.getSubjectAttributes()) {
+                supplementalSubjectAttributes = new HashSet<>();
+            } else {
+                supplementalSubjectAttributes = new HashSet<>(request.getSubjectAttributes());
+            }
+
+            for (PolicySet policySet : filteredPolicySets) {
+                result = evalPolicySet(policySet, subjectIdentifier, uri, action, supplementalResourceAttributes,
+                        supplementalSubjectAttributes);
+                if (result.getEffect() != Effect.NOT_APPLICABLE) {
+                    break;
+                }
+            }
+
+            LOGGER.info("Processed Policy Evaluation for: " + "resourceUri='{}', subjectIdentifier='{}', action='{}',"
+                    + " result='{}'", uri, subjectIdentifier, action, result.getEffect());
+
+            // A policy evaluation result with an INDETERMINATE effect is almost always due to transient errors.
+            // Caching such results will inevitably cause users to get back a stale result for a period of time
+            // even when the transient error is fixed.
+            if (result.getEffect() != Effect.INDETERMINATE) {
+                try {
+                    this.cache.set(key, result);
+                } catch (Exception e) {
+                    LOGGER.error(
+                            String.format("Unable to set cache key '%s' to value '%s' due to exception", key, result),
+                            e);
+                }
+            }
+        }
+        return result;
+    }
+
+    LinkedHashSet<PolicySet> filterPolicySetsByPriority(final String subjectIdentifier, final String uri,
+            final List<PolicySet> allPolicySets, final LinkedHashSet<String> policySetsEvaluationOrder)
+            throws IllegalArgumentException {
+
+        if (policySetsEvaluationOrder.isEmpty()) {
+            if (allPolicySets.size() > 1) {
+                LOGGER.error(
+                        "Found more than one policy set during policy evaluation and "
+                                + "no evaluation order is provided. subjectIdentifier='{}', resourceURI='{}'",
+                        subjectIdentifier, uri);
+                throw new IllegalArgumentException("More than one policy set exists for this zone. "
+                        + "Please provide an ordered list of policy set names to consider for this evaluation and "
+                        + "resubmit the request.");
+            } else {
+                return new LinkedHashSet<>(allPolicySets);
+            }
+        }
+
+        Map<String, PolicySet> allPolicySetsMap = allPolicySets.stream()
+                .collect(Collectors.toMap(PolicySet::getName, Function.identity()));
+        LinkedHashSet<PolicySet> filteredPolicySets = new LinkedHashSet<>();
+        for (String policySetId : policySetsEvaluationOrder) {
+            PolicySet policySet = allPolicySetsMap.get(policySetId);
+            if (policySet == null) {
+                LOGGER.error("No existing policy set matches policy set in the evaluation order of the request. "
+                        + "Subject: {}, Resource: {}", subjectIdentifier, uri);
+                throw new IllegalArgumentException(
+                        "No existing policy set matches policy set in the evaluaion order of the request. "
+                                + "Please review the policy evauation order and resubmit the request.");
+            } else {
+                filteredPolicySets.add(policySet);
+            }
+        }
+        return filteredPolicySets;
+    }
+
+    private PolicyEvaluationResult evalPolicySet(final PolicySet policySet, final String subjectIdentifier,
+            final String resourceURI, final String action, final Set<Attribute> supplementalResourceAttributes,
+            final Set<Attribute> supplementalSubjectAttributes) {
+
+        PolicyEvaluationResult result;
+        try {
+            MatchResult matchResult = matchPolicies(subjectIdentifier, resourceURI, action, policySet.getPolicies(),
+                    supplementalResourceAttributes, supplementalSubjectAttributes);
+
+            Effect effect = Effect.NOT_APPLICABLE;
+            Set<String> resolvedResourceUris = matchResult.getResolvedResourceUris();
+            resolvedResourceUris.add(resourceURI);
+
+            Set<Attribute> resourceAttributes = Collections.emptySet();
+            Set<Attribute> subjectAttributes = Collections.emptySet();
+            List<MatchedPolicy> matchedPolicies = matchResult.getMatchedPolicies();
+            for (MatchedPolicy matchedPolicy : matchedPolicies) {
+                Policy policy = matchedPolicy.getPolicy();
+                resourceAttributes = matchedPolicy.getResourceAttributes();
+                subjectAttributes = matchedPolicy.getSubjectAttributes();
+                Target target = policy.getTarget();
+                String resourceURITemplate = null;
+                if (target != null && target.getResource() != null) {
+                    resourceURITemplate = target.getResource().getUriTemplate();
+                }
+
+                boolean conditionEvaluationResult = true;
+                if (!policy.getConditions().isEmpty()) {
+                    conditionEvaluationResult = evaluateConditions(subjectAttributes, resourceAttributes, resourceURI,
+                            policy.getConditions(), resourceURITemplate);
+                }
+                LOGGER.debug("Checking condition of policy '{}': Condition evaluated to ? -> {}, policy effect {}",
+                        policy.getName(), conditionEvaluationResult, policy.getEffect());
+
+                LOGGER.debug("Condition Eval: {} Result: {}", policy.getConditions(), conditionEvaluationResult);
+                if (conditionEvaluationResult) {
+                    effect = policy.getEffect();
+                    LOGGER.info("Condition Evaluation success: policy set name='{}', policy name='{}', effect='{}'",
+                            policySet.getName(), policy.getName(), policy.getEffect());
+                    break;
+                }
+            }
+            result = new PolicyEvaluationResult(effect, subjectAttributes, new ArrayList<>(resourceAttributes),
+                    resolvedResourceUris);
+        } catch (Exception e) {
+            result = handlePolicyEvaluationException(policySet, subjectIdentifier, resourceURI, e);
+        }
+        return result;
+    }
+
+    private PolicyEvaluationResult handlePolicyEvaluationException(final PolicySet policySet,
+            final String subjectIdentifier, final String resourceURI, final Throwable e) {
+        PolicyEvaluationResult result = new PolicyEvaluationResult(Effect.INDETERMINATE);
+        StringBuilder logMessage = new StringBuilder();
+        logMessage.append("Exception occured while evaluating the policy set. Policy Set ID:'")
+                .append(policySet.getName()).append("' subject:'").append(subjectIdentifier)
+                .append("', Resource URI: '").append(resourceURI).append("'");
+        if (e instanceof AttributeLimitExceededException || e instanceof AttributeRetrievalException) {
+            result.setMessage(e.getMessage());
+        }
+        LOGGER.error("{}", logMessage, e);
+        return result;
+    }
+
+    boolean evaluateConditions(final Set<Attribute> subjectAttributes, final Set<Attribute> resourceAttributes,
+            final String resourceURI, final List<Condition> conditions, final String resourceURITemplate) {
+        List<ConditionScript> validatedConditionScripts;
+        try {
+            validatedConditionScripts = this.policySetValidator.validatePolicyConditions(conditions);
+        } catch (PolicySetValidationException e) {
+            LOGGER.error("Unable to validate conditions: {}", e.getMessage());
+            throw new PolicyEvaluationException("Condition Validation failed", e);
+        }
+
+        debugAttributes(subjectAttributes, resourceAttributes);
+
+        Map<String, Object> attributeBindingsMap = this.getAttributeBindingsMap(subjectAttributes, resourceAttributes,
+                resourceURI, resourceURITemplate);
+
+        boolean result = true;
+        for (int i = 0; i < validatedConditionScripts.size(); i++) {
+            ConditionScript conditionScript = validatedConditionScripts.get(i);
+            try {
+                result = result && conditionScript.execute(attributeBindingsMap);
+            } catch (ConditionAssertionFailedException e) {
+                LOGGER.debug("Condition Assertion Failed", e);
+                result = false;
+            } catch (Exception e) {
+                LOGGER.error("Unable to evualate condition: {}", conditions.get(i), e);
+                throw new PolicyEvaluationException("Condition Evaluation failed", e);
+            }
+        }
+        return result;
+    }
+
+    private Map<String, Object> getAttributeBindingsMap(final Set<Attribute> subjectAttributes,
+            final Set<Attribute> resourceAttributes, final String resourceURI, final String resourceURITemplate) {
+        SubjectHandler subjectHandler = new SubjectHandler(subjectAttributes);
+        ResourceHandler resourceHandler = new ResourceHandler(resourceAttributes, resourceURI, resourceURITemplate);
+
+        Map<String, Object> attributeHandler = new HashMap<>();
+        attributeHandler.put("resource", resourceHandler);
+        attributeHandler.put("subject", subjectHandler);
+        attributeHandler.put("match", new AttributeMatcher());
+        return attributeHandler;
+    }
+
+    private MatchResult matchPolicies(final String subjectIdentifier, final String resourceURI, final String action,
+            final List<Policy> allPolicies, final Set<Attribute> supplementalResourceAttributes,
+            final Set<Attribute> supplementalSubjectAttributes) {
+        PolicyMatchCandidate criteria = new PolicyMatchCandidate(action, resourceURI, subjectIdentifier,
+                supplementalResourceAttributes, supplementalSubjectAttributes);
+        return this.policyMatcher.matchForResult(criteria, allPolicies);
+    }
+
+    private void debugAttributes(final Set<Attribute> subjectAttributes, final Set<Attribute> resourceAttributes) {
+        if (LOGGER.isDebugEnabled()) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("Subject Attributes :\n");
+            Iterator<Attribute> subjectAttributesItr = subjectAttributes.iterator();
+            while (subjectAttributesItr.hasNext()) {
+                sb.append(subjectAttributesItr.next().toString() + "\n");
+            }
+            sb.append("Resource Attributes :\n");
+            Iterator<Attribute> resourceAttributesIte = resourceAttributes.iterator();
+            while (resourceAttributesIte.hasNext()) {
+                sb.append(resourceAttributesIte.next().toString() + "\n");
+            }
+            LOGGER.debug(sb.toString());
+        }
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/ResourceAttributeResolver.java b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/ResourceAttributeResolver.java
new file mode 100644
index 0000000..4735d56
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/ResourceAttributeResolver.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.springframework.web.util.UriTemplate;
+
+import com.ge.predix.acs.attribute.readers.ResourceAttributeReader;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Policy;
+import com.ge.predix.acs.service.policy.matcher.UriTemplateVariableResolver;
+
+public class ResourceAttributeResolver {
+
+    private static final String ATTRIBUTE_URI_TEMPLATE_VARIABLE = "attribute_uri";
+
+    private final Map<String, Set<Attribute>> resourceAttributeMap = new HashMap<>();
+    private final ResourceAttributeReader resourceAttributeReader;
+    private final Set<Attribute> supplementalResourceAttributes;
+    private final String requestResourceUri;
+    private final UriTemplateVariableResolver uriTemplateVariableResolver = new UriTemplateVariableResolver();
+
+    /**
+     * @param requestResourceUri
+     *            URI of the resource from the policy evaluation request
+     */
+    public ResourceAttributeResolver(final ResourceAttributeReader resourceAttributeReader,
+            final String requestResourceUri, final Set<Attribute> supplementalResourceAttributes) {
+        this.resourceAttributeReader = resourceAttributeReader;
+        this.requestResourceUri = requestResourceUri;
+        if (null == supplementalResourceAttributes) {
+            this.supplementalResourceAttributes = Collections.emptySet();
+        } else {
+            this.supplementalResourceAttributes = supplementalResourceAttributes;
+        }
+    }
+
+    public ResourceAttributeResolverResult getResult(final Policy policy) {
+        String resolvedResourceUri = resolveResourceURI(policy);
+        boolean uriTemplateExists = true;
+        if (null == resolvedResourceUri) {
+            resolvedResourceUri = this.requestResourceUri;
+            uriTemplateExists = false;
+        }
+        Set<Attribute> resourceAttributes = this.resourceAttributeMap.get(resolvedResourceUri);
+        if (null == resourceAttributes) {
+            resourceAttributes = new HashSet<>(this.resourceAttributeReader.getAttributes(resolvedResourceUri));
+            resourceAttributes.addAll(this.supplementalResourceAttributes);
+            this.resourceAttributeMap.put(resolvedResourceUri, resourceAttributes);
+        }
+        return new ResourceAttributeResolverResult(resourceAttributes, resolvedResourceUri, uriTemplateExists);
+    }
+
+    String resolveResourceURI(final Policy policy) {
+        if (attributeUriTemplateExists(policy)) {
+            String attributeUriTemplate = policy.getTarget().getResource().getAttributeUriTemplate();
+            UriTemplate uriTemplate = new UriTemplate(attributeUriTemplate);
+            return this.uriTemplateVariableResolver.resolve(this.requestResourceUri, uriTemplate,
+                    ATTRIBUTE_URI_TEMPLATE_VARIABLE);
+        }
+        return null;
+    }
+
+    private boolean attributeUriTemplateExists(final Policy policy) {
+        if (policy != null && policy.getTarget() != null && policy.getTarget().getResource() != null) {
+            return StringUtils.isNotBlank(policy.getTarget().getResource().getAttributeUriTemplate());
+        }
+        return false;
+    }
+
+    public static class ResourceAttributeResolverResult {
+        private final Set<Attribute> resourceAttributes;
+        private final String resovledResourceUri;
+        private final boolean attributeUriTemplateFound;
+
+        public ResourceAttributeResolverResult(final Set<Attribute> resourceAttributes,
+                final String resovledResourceUri, final boolean attributeUriTemplateFound) {
+            this.resourceAttributes = resourceAttributes;
+            this.resovledResourceUri = resovledResourceUri;
+            this.attributeUriTemplateFound = attributeUriTemplateFound;
+        }
+
+        public Set<Attribute> getResourceAttributes() {
+            return this.resourceAttributes;
+        }
+
+        public String getResovledResourceUri() {
+            return this.resovledResourceUri;
+        }
+
+        public boolean isAttributeUriTemplateFound() {
+            return this.attributeUriTemplateFound;
+        }
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/SubjectAttributeResolver.java b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/SubjectAttributeResolver.java
new file mode 100644
index 0000000..300f999
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/evaluation/SubjectAttributeResolver.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.ge.predix.acs.attribute.readers.SubjectAttributeReader;
+import com.ge.predix.acs.model.Attribute;
+
+public class SubjectAttributeResolver {
+
+    private final Map<String, Set<Attribute>> subjectAttributeMap = new HashMap<>();
+    private final SubjectAttributeReader subjectAttributeReader;
+    private final String subjectIdentifier;
+    private final Set<Attribute> supplementalSubjectAttributes;
+
+    public SubjectAttributeResolver(final SubjectAttributeReader subjectAttributeReader, final String subjectIdentifier,
+            final Set<Attribute> supplementalSubjectAttributes) {
+        this.subjectAttributeReader = subjectAttributeReader;
+        this.subjectIdentifier = subjectIdentifier;
+        if (null == supplementalSubjectAttributes) {
+            this.supplementalSubjectAttributes = Collections.emptySet();
+        } else {
+            this.supplementalSubjectAttributes = supplementalSubjectAttributes;
+        }
+    }
+
+    public Set<Attribute> getResult(final Set<Attribute> scopes) {
+        Set<Attribute> subjectAttributes = this.subjectAttributeMap.get(this.subjectIdentifier);
+        if (null == subjectAttributes) {
+            subjectAttributes = new HashSet<>(
+                    this.subjectAttributeReader.getAttributesByScope(this.subjectIdentifier, scopes));
+            subjectAttributes.addAll(this.supplementalSubjectAttributes);
+            this.subjectAttributeMap.put(this.subjectIdentifier, subjectAttributes);
+        }
+        return subjectAttributes;
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/matcher/MatchResult.java b/service/src/main/java/com/ge/predix/acs/service/policy/matcher/MatchResult.java
new file mode 100644
index 0000000..b6dd1f9
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/matcher/MatchResult.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.matcher;
+
+import java.util.List;
+import java.util.Set;
+
+import com.ge.predix.acs.service.policy.evaluation.MatchedPolicy;
+
+public class MatchResult {
+
+    private final List<MatchedPolicy> matchedPolicies;
+    private final Set<String> resolvedResourceUris;
+
+    public MatchResult(final List<MatchedPolicy> matchedPolicies, final Set<String> resolvedResourceUris) {
+        this.matchedPolicies = matchedPolicies;
+        this.resolvedResourceUris = resolvedResourceUris;
+    }
+
+    public List<MatchedPolicy> getMatchedPolicies() {
+        return this.matchedPolicies;
+    }
+
+    public Set<String> getResolvedResourceUris() {
+        return this.resolvedResourceUris;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/matcher/PolicyMatchCandidate.java b/service/src/main/java/com/ge/predix/acs/service/policy/matcher/PolicyMatchCandidate.java
new file mode 100644
index 0000000..cab729c
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/matcher/PolicyMatchCandidate.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.matcher;
+
+import java.util.Set;
+
+import com.ge.predix.acs.model.Attribute;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+public class PolicyMatchCandidate {
+    private String action;
+    private String resourceURI;
+    private String subjectIdentifier;
+    private Set<Attribute> supplementalResourceAttributes;
+    private Set<Attribute> supplementalSubjectAttributes;
+
+    /**
+     * Creates a new criteria object.
+     *
+     * @param action
+     *            action
+     * @param resourceURI
+     *            resource
+     * @param resourceAttributes
+     *            attributes for the resource
+     * @param subjectIdentifier
+     *            identifier for the subject
+     * @param supplementalSubjectAttributes
+     *            attributes for the subject
+     */
+    public PolicyMatchCandidate(final String action, final String resourceURI, final String subjectIdentifier,
+            final Set<Attribute> supplementalResourceAttributes, final Set<Attribute> supplementalSubjectAttributes) {
+        this.action = action;
+        this.resourceURI = resourceURI;
+        this.subjectIdentifier = subjectIdentifier;
+        this.supplementalResourceAttributes = supplementalResourceAttributes;
+        this.supplementalSubjectAttributes = supplementalSubjectAttributes;
+    }
+
+    /**
+     * Creates a new criteria object.
+     *
+     */
+    public PolicyMatchCandidate() {
+        // Default constructor.
+    }
+
+    /**
+     * @return the action
+     */
+    public String getAction() {
+        return this.action;
+    }
+
+    /**
+     * @param action
+     *            the action to set
+     */
+    public void setAction(final String action) {
+        this.action = action;
+    }
+
+    /**
+     * @return the resourceURI
+     */
+    public String getResourceURI() {
+        return this.resourceURI;
+    }
+
+    /**
+     * @param resourceURI
+     *            the resourceURI to set
+     */
+    public void setResourceURI(final String resourceURI) {
+        this.resourceURI = resourceURI;
+    }
+
+    public Set<Attribute> getSupplementalResourceAttributes() {
+        return this.supplementalResourceAttributes;
+    }
+
+    public void setSupplementalResourceAttributes(final Set<Attribute> supplementalResourceAttributes) {
+        this.supplementalResourceAttributes = supplementalResourceAttributes;
+    }
+
+    public Set<Attribute> getSupplementalSubjectAttributes() {
+        return this.supplementalSubjectAttributes;
+    }
+
+    public void setSupplementalSubjectAttributes(final Set<Attribute> supplementalSubjectAttributes) {
+        this.supplementalSubjectAttributes = supplementalSubjectAttributes;
+    }
+
+    /**
+     *
+     * @return subjectIdentifier
+     */
+    public String getSubjectIdentifier() {
+        return this.subjectIdentifier;
+    }
+
+    /**
+     * Sets the subject identifier.
+     *
+     * @param subjectIdentifier
+     */
+    public void setSubjectIdentifier(final String subjectIdentifier) {
+        this.subjectIdentifier = subjectIdentifier;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/matcher/PolicyMatcher.java b/service/src/main/java/com/ge/predix/acs/service/policy/matcher/PolicyMatcher.java
new file mode 100644
index 0000000..a810f9a
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/matcher/PolicyMatcher.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.matcher;
+
+import com.ge.predix.acs.model.Policy;
+import com.ge.predix.acs.service.policy.evaluation.MatchedPolicy;
+
+import java.util.List;
+
+/**
+ * Matches an access control request to a policy.
+ *
+ * @author acs-engineers@ge.com
+ */
+public interface PolicyMatcher {
+    /**
+     * @param candidate
+     *            the criteria for a match.
+     * @param policies
+     *            the list of potential policy matches
+     * @return the policies that match the access control request.
+     */
+    List<MatchedPolicy> match(PolicyMatchCandidate candidate, List<Policy> policies);
+
+    /**
+     * @param candidate
+     *            the criteria for a match.
+     * @param policies
+     *            the list of potential policy matches
+     * @return the policies that match the access control request and the set of resolved URIs from applying all
+     *         attribute URI templates.
+     */
+    MatchResult matchForResult(PolicyMatchCandidate candidate, List<Policy> policies);
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/matcher/PolicyMatcherImpl.java b/service/src/main/java/com/ge/predix/acs/service/policy/matcher/PolicyMatcherImpl.java
new file mode 100644
index 0000000..c5bcd97
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/matcher/PolicyMatcherImpl.java
@@ -0,0 +1,168 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.matcher;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.attribute.readers.AttributeReaderFactory;
+import com.ge.predix.acs.commons.web.UriTemplateUtils;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Policy;
+import com.ge.predix.acs.service.policy.evaluation.MatchedPolicy;
+import com.ge.predix.acs.service.policy.evaluation.ResourceAttributeResolver;
+import com.ge.predix.acs.service.policy.evaluation.ResourceAttributeResolver.ResourceAttributeResolverResult;
+import com.ge.predix.acs.service.policy.evaluation.SubjectAttributeResolver;
+
+/**
+ * @author acs-engineers@ge.com
+ */
+@Component
+public class PolicyMatcherImpl implements PolicyMatcher {
+    private static final Logger LOGGER = LoggerFactory.getLogger(PolicyMatcherImpl.class);
+
+    @Autowired
+    private AttributeReaderFactory attributeReaderFactory;
+
+    @Override
+    public List<MatchedPolicy> match(final PolicyMatchCandidate candidate, final List<Policy> policies) {
+        return matchForResult(candidate, policies).getMatchedPolicies();
+    }
+
+    @Override
+    public MatchResult matchForResult(final PolicyMatchCandidate candidate, final List<Policy> policies) {
+        ResourceAttributeResolver resourceAttributeResolver = new ResourceAttributeResolver(
+                this.attributeReaderFactory.getResourceAttributeReader(), candidate.getResourceURI(),
+                candidate.getSupplementalResourceAttributes());
+        SubjectAttributeResolver subjectAttributeResolver = new SubjectAttributeResolver(
+                this.attributeReaderFactory.getSubjectAttributeReader(), candidate.getSubjectIdentifier(),
+                candidate.getSupplementalSubjectAttributes());
+
+        List<MatchedPolicy> matchedPolicies = new ArrayList<>();
+        Set<String> resolvedResourceUris = new HashSet<>();
+        for (Policy policy : policies) {
+            ResourceAttributeResolverResult resAttrResolverResult = resourceAttributeResolver.getResult(policy);
+            Set<Attribute> resourceAttributes = resAttrResolverResult.getResourceAttributes();
+            Set<Attribute> subjectAttributes = subjectAttributeResolver.getResult(resourceAttributes);
+            if (resAttrResolverResult.isAttributeUriTemplateFound()) {
+                resolvedResourceUris.add(resAttrResolverResult.getResovledResourceUri());
+            }
+            if (isPolicyMatch(candidate, policy, resourceAttributes, subjectAttributes)) {
+                matchedPolicies.add(new MatchedPolicy(policy, resourceAttributes, subjectAttributes));
+            }
+        }
+        return new MatchResult(matchedPolicies, resolvedResourceUris);
+    }
+
+    /**
+     * @param candidate policy match candidate
+     * @param policy    to match
+     * @return true if the policy meets the criteria
+     */
+    @SuppressWarnings("nls")
+    private boolean isPolicyMatch(final PolicyMatchCandidate candidate, final Policy policy,
+            final Set<Attribute> resourceAttributes, final Set<Attribute> subjectAttributes) {
+        // A policy with no target matches everything.
+        if (null == policy.getTarget()) {
+            return true;
+        }
+        boolean actionMatch = isActionMatch(candidate.getAction(), policy);
+
+        boolean subjectMatch = isSubjectMatch(subjectAttributes, policy);
+
+        boolean resourceMatch = isResourceMatch(candidate.getResourceURI(), resourceAttributes, policy);
+
+        LOGGER.debug("Checking policy [{}]: Action match ? -> {}, Subject match ? -> {}, Resource match ? -> {}, ",
+                policy.getName(), actionMatch, subjectMatch, resourceMatch);
+
+        return actionMatch && subjectMatch && resourceMatch;
+    }
+
+    private boolean isResourceMatch(final String resourceURI, final Set<Attribute> resourceAttributes,
+            final Policy policy) {
+        if (null == policy.getTarget().getResource()) {
+            return true;
+        }
+
+        boolean uriTemplateMatch = UriTemplateUtils
+                .isCanonicalMatch(policy.getTarget().getResource().getUriTemplate(), resourceURI);
+
+        if (!uriTemplateMatch) {
+            return false;
+        }
+
+        if (null == policy.getTarget().getResource().getAttributes()) {
+            return true;
+        }
+
+        for (Attribute attr : policy.getTarget().getResource().getAttributes()) {
+            if (!containsAttributeType(attr.getIssuer(), attr.getName(), resourceAttributes)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean isSubjectMatch(final Set<Attribute> subjectAttributes, final Policy policy) {
+        if ((null == policy.getTarget().getSubject()) || (null == policy.getTarget().getSubject().getAttributes())) {
+            return true;
+        }
+
+        for (Attribute attr : policy.getTarget().getSubject().getAttributes()) {
+            if (!containsAttributeType(attr.getIssuer(), attr.getName(), subjectAttributes)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @SuppressWarnings("nls")
+    private boolean isActionMatch(final String requestAction, final Policy policy) {
+        // Target's action is not null and they match or the Target's action is
+        // null and is considered a match for anything
+        List<String> policyActionList = Collections.emptyList();
+        boolean policyActionDefined = (policy.getTarget() != null) && (policy.getTarget().getAction() != null);
+        if (policyActionDefined) {
+            String policyActions = policy.getTarget().getAction();
+            policyActionList = Arrays.asList(policyActions.split("\\s*,\\s*"));
+        }
+        return (policyActionDefined && (requestAction != null) && policyActionList.contains(requestAction)) || (
+                policy.getTarget().getAction() == null);
+    }
+
+    private boolean containsAttributeType(final String issuer, final String name,
+            final Collection<Attribute> attributes) {
+        for (Attribute attr : attributes) {
+            if (issuer.equals(attr.getIssuer()) && name.equals(attr.getName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/matcher/UriTemplateVariableResolver.java b/service/src/main/java/com/ge/predix/acs/service/policy/matcher/UriTemplateVariableResolver.java
new file mode 100644
index 0000000..42df20b
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/matcher/UriTemplateVariableResolver.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.matcher;
+
+import java.util.Map;
+
+import org.springframework.web.util.UriTemplate;
+
+public class UriTemplateVariableResolver {
+
+    public String resolve(final String uri, final UriTemplate uriTemplate, final String uriTemplateVariableName) {
+
+        Map<String, String> variables = uriTemplate.match(uri);
+        return variables.get(uriTemplateVariableName);
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/validation/PolicySetValidationException.java b/service/src/main/java/com/ge/predix/acs/service/policy/validation/PolicySetValidationException.java
new file mode 100644
index 0000000..6da3412
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/validation/PolicySetValidationException.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.validation;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@SuppressWarnings("javadoc")
+public class PolicySetValidationException extends RuntimeException {
+
+    private static final long serialVersionUID = -2434141848064977230L;
+
+    public PolicySetValidationException() {
+        super();
+    }
+
+    public PolicySetValidationException(final String message, final Throwable cause, final boolean enableSuppression,
+            final boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+    public PolicySetValidationException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    public PolicySetValidationException(final String message) {
+        super(message);
+    }
+
+    public PolicySetValidationException(final Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/validation/PolicySetValidator.java b/service/src/main/java/com/ge/predix/acs/service/policy/validation/PolicySetValidator.java
new file mode 100644
index 0000000..226124e
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/validation/PolicySetValidator.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.validation;
+
+import java.util.List;
+
+import com.ge.predix.acs.commons.policy.condition.ConditionScript;
+import com.ge.predix.acs.model.Condition;
+import com.ge.predix.acs.model.PolicySet;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+public interface PolicySetValidator {
+
+    void removeCachedConditions(PolicySet policySet);
+
+    /**
+     * @param policySet
+     *            PolicySet to be validated
+     */
+    void validatePolicySet(PolicySet policySet);
+
+    /**
+     * @param conditions
+     *            Policy conditions to be validated
+     * @return A list of the corresponding ConditionScript for validated conditions
+     */
+    List<ConditionScript> validatePolicyConditions(List<Condition> conditions);
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/service/policy/validation/PolicySetValidatorImpl.java b/service/src/main/java/com/ge/predix/acs/service/policy/validation/PolicySetValidatorImpl.java
new file mode 100644
index 0000000..f23c233
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/service/policy/validation/PolicySetValidatorImpl.java
@@ -0,0 +1,190 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.validation;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.PostConstruct;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.ge.predix.acs.commons.policy.condition.ConditionScript;
+import com.ge.predix.acs.commons.policy.condition.groovy.GroovyConditionCache;
+import com.ge.predix.acs.commons.policy.condition.groovy.GroovyConditionShell;
+import com.ge.predix.acs.model.Condition;
+import com.ge.predix.acs.model.Policy;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.utils.JsonUtils;
+import com.github.fge.jsonschema.core.report.ProcessingMessage;
+import com.github.fge.jsonschema.core.report.ProcessingReport;
+import com.github.fge.jsonschema.main.JsonSchema;
+import com.github.fge.jsonschema.main.JsonSchemaFactory;
+
+/**
+ * @author acs-engineers@ge.com
+ */
+@Component
+@SuppressWarnings("nls")
+public class PolicySetValidatorImpl implements PolicySetValidator {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(PolicySetValidatorImpl.class);
+
+    private static final JsonUtils JSONUTILS = new JsonUtils();
+    private static final JsonNode JSONSCHEMA;
+
+    @Autowired
+    private GroovyConditionCache conditionCache;
+
+    @Autowired
+    private GroovyConditionShell conditionShell;
+
+    static {
+        JSONSCHEMA = JSONUTILS.readJsonNodeFromFile("acs-policy-set-schema.json");
+    }
+
+    @Value("${validAcsPolicyHttpActions:GET, POST, PUT, DELETE, PATCH, SUBSCRIBE, MESSAGE}")
+    private String validAcsPolicyHttpActions;
+
+    private Set<String> validAcsPolicyHttpActionsSet;
+
+    @Override
+    public void removeCachedConditions(final PolicySet policySet) {
+        for (Policy policy : policySet.getPolicies()) {
+            for (Condition condition : policy.getConditions()) {
+                this.conditionCache.remove(condition.getCondition());
+            }
+        }
+    }
+
+    @Override
+    public void validatePolicySet(final PolicySet policySet) {
+        validateSchema(policySet);
+        for (Policy p : policySet.getPolicies()) {
+            validatePolicyConditions(p.getConditions());
+            validatePolicyActions(p);
+        }
+    }
+
+    private void validatePolicyActions(final Policy p) {
+
+        if (p.getTarget() != null && p.getTarget().getAction() != null) {
+            String policyActions = p.getTarget().getAction();
+            // Empty actions will be treated as null actions which behave like
+            // match any
+            // during policy evaluation
+            if (policyActions.trim().length() == 0) {
+                p.getTarget().setAction(null);
+                return;
+            }
+            for (String action : policyActions.split("\\s*,\\s*")) {
+                if (!this.validAcsPolicyHttpActionsSet.contains(action)) {
+                    throw new PolicySetValidationException(String.format("Policy Action validation failed: "
+                                    + "the action: [%s] is not contained in the allowed set of actions: [%s]", action,
+                            this.validAcsPolicyHttpActions));
+                }
+            }
+        }
+    }
+
+    private void validateSchema(final PolicySet policySet) {
+        try {
+            JsonNode policySetJsonNode = JSONUTILS.readJsonNodeFromObject(policySet);
+            JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
+            JsonSchema schema = factory.getJsonSchema(JSONSCHEMA);
+            ProcessingReport report = schema.validate(policySetJsonNode);
+            Iterator<ProcessingMessage> iterator = report.iterator();
+            boolean valid = report.isSuccess();
+            StringBuilder sb = new StringBuilder();
+            if (!valid) {
+                while (iterator.hasNext()) {
+                    ProcessingMessage processingMessage = iterator.next();
+                    sb.append(" ");
+                    sb.append(processingMessage);
+                    LOGGER.debug("{}", processingMessage);
+                }
+                throw new PolicySetValidationException("JSON Schema validation " + sb.toString());
+            }
+        } catch (PolicySetValidationException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new PolicySetValidationException("Error while validating JSON schema", e);
+        }
+
+    }
+
+    @Override
+    public List<ConditionScript> validatePolicyConditions(final List<Condition> conditions) {
+        List<ConditionScript> conditionScripts = new ArrayList<>();
+        try {
+            if ((conditions == null) || conditions.isEmpty()) {
+                return conditionScripts;
+            }
+            for (Condition condition : conditions) {
+                String conditionScript = condition.getCondition();
+                ConditionScript compiledScript = this.conditionCache.get(conditionScript);
+                if (compiledScript != null) {
+                    conditionScripts.add(compiledScript);
+                    continue;
+                }
+
+                try {
+                    LOGGER.debug("Adding condition: {}", conditionScript);
+                    compiledScript = this.conditionShell.parse(conditionScript);
+                    conditionScripts.add(compiledScript);
+                    this.conditionCache.put(conditionScript, compiledScript);
+                } catch (Exception e) {
+                    throw new PolicySetValidationException(
+                            "Condition : [" + conditionScript + "] validation failed with error : " + e.getMessage(),
+                            e);
+                }
+            }
+
+            return conditionScripts;
+
+        } catch (PolicySetValidationException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new PolicySetValidationException("Unexpected exception while validating policy conditions", e);
+        }
+    }
+
+    /**
+     * Initialization method to populate the allowedPolicyHttpActions.
+     */
+    @PostConstruct
+    public void init() {
+        String[] actions = this.validAcsPolicyHttpActions.split("\\s*,\\s*");
+        LOGGER.debug("ACS Server configured with validAcsPolicyHttpActions : {}", this.validAcsPolicyHttpActions);
+        this.validAcsPolicyHttpActionsSet = new HashSet<>(Arrays.asList(actions));
+    }
+
+    public void setValidAcsPolicyHttpActions(final String validAcsPolicyHttpActions) {
+        this.validAcsPolicyHttpActions = validAcsPolicyHttpActions;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/zone/management/ZoneController.java b/service/src/main/java/com/ge/predix/acs/zone/management/ZoneController.java
new file mode 100644
index 0000000..ddbc337
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/zone/management/ZoneController.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.zone.management;
+
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.V1;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.created;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.noContent;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.notFound;
+import static com.ge.predix.acs.commons.web.ResponseEntityBuilder.ok;
+import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
+import static org.springframework.web.bind.annotation.RequestMethod.GET;
+import static org.springframework.web.bind.annotation.RequestMethod.PUT;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.ge.predix.acs.commons.web.ACSWebConstants;
+import com.ge.predix.acs.commons.web.AcsApiUriTemplates;
+import com.ge.predix.acs.commons.web.BaseRestApi;
+import com.ge.predix.acs.commons.web.RestApiException;
+import com.ge.predix.acs.rest.Zone;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@RestController
+@Api(value = ACSWebConstants.APP_ROOT_PATH, hidden = true)
+public class ZoneController extends BaseRestApi {
+
+    @Autowired
+    private ZoneService service;
+
+    @RequestMapping(method = PUT, value = V1 + AcsApiUriTemplates.ZONE_URL, consumes = MediaType.APPLICATION_JSON_VALUE)
+    @ApiOperation(value = "Creates/Updates the zone.", hidden = true)
+    public ResponseEntity<Zone> putZone(@RequestBody final Zone zone, @PathVariable("zoneName") final String zoneName) {
+
+        validateAndSanitizeInputOrFail(zone, zoneName);
+        try {
+
+            boolean zoneCreated = this.service.upsertZone(zone);
+
+            if (zoneCreated) {
+                return created(false, V1 + AcsApiUriTemplates.ZONE_URL, "zoneName:" + zoneName);
+            }
+
+            return created();
+
+        } catch (ZoneManagementException e) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY, e);
+        } catch (Exception e) {
+            String message = String.format(
+                    "Unexpected Exception while upserting Zone with name %s and subdomain %s", zone.getName(),
+                    zone.getSubdomain());
+            throw new RestApiException(HttpStatus.INTERNAL_SERVER_ERROR, message, e);
+        }
+    }
+
+    @RequestMapping(method = GET, value = V1 + AcsApiUriTemplates.ZONE_URL, produces = MediaType.APPLICATION_JSON_VALUE)
+    @ApiOperation(
+            value = "An ACS zone defines a data partition which encapsulates policy, resource and privelege data"
+                    + "for separation between ACS tenants.  Retrieves the zone by a zone name.",
+            hidden = true)
+    public ResponseEntity<Zone> getZone(@PathVariable("zoneName") final String zoneName) {
+
+        try {
+
+            Zone zone = this.service.retrieveZone(zoneName);
+            return ok(zone);
+
+        } catch (ZoneManagementException e) {
+            throw new RestApiException(HttpStatus.NOT_FOUND, e);
+        } catch (Exception e) {
+            String message = String.format("Unexpected Exception while retriving Zone with name %s", zoneName);
+            throw new RestApiException(HttpStatus.INTERNAL_SERVER_ERROR, message, e);
+        }
+    }
+
+    @RequestMapping(method = DELETE, value = V1 + AcsApiUriTemplates.ZONE_URL)
+    @ApiOperation(value = "Deletes the zone.", hidden = true)
+    public ResponseEntity<Void> deleteZone(@PathVariable("zoneName") final String zoneName) {
+
+        try {
+
+            Boolean deleted = this.service.deleteZone(zoneName);
+            if (deleted) {
+                return noContent();
+            }
+            return notFound();
+
+        } catch (Exception e) {
+            String message = String.format("Unexpected Exception while deleting Zone with name %s", zoneName);
+            throw new RestApiException(HttpStatus.INTERNAL_SERVER_ERROR, message, e);
+        }
+    }
+
+    /**
+     * @param zone
+     */
+    private void validateAndSanitizeInputOrFail(final Zone zone, final String zoneName) {
+        if (zone.getName() != null && !zoneName.equals(zone.getName())) {
+            throw new RestApiException(HttpStatus.UNPROCESSABLE_ENTITY,
+                    "Zone name in URI does not match the Zone Name in Payload");
+        }
+        if (zone.getName() == null) {
+            zone.setName(zoneName);
+        }
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/zone/management/ZoneHttpMethodsFilter.java b/service/src/main/java/com/ge/predix/acs/zone/management/ZoneHttpMethodsFilter.java
new file mode 100644
index 0000000..f9593b8
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/zone/management/ZoneHttpMethodsFilter.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.zone.management;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.security.AbstractHttpMethodsFilter;
+
+@Component
+public class ZoneHttpMethodsFilter extends AbstractHttpMethodsFilter {
+
+    private static final String ZONE_MANAGEMENT_URI_REGEX = "\\A/v1/zone/[^/]+?/??\\Z";
+
+    private static Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods() {
+        Map<String, Set<HttpMethod>> uriPatternsAndAllowedHttpMethods = new LinkedHashMap<>();
+        uriPatternsAndAllowedHttpMethods.put(ZONE_MANAGEMENT_URI_REGEX,
+                new HashSet<>(Arrays.asList(HttpMethod.PUT, HttpMethod.GET, HttpMethod.DELETE, HttpMethod.HEAD)));
+        return uriPatternsAndAllowedHttpMethods;
+    }
+
+    public ZoneHttpMethodsFilter() {
+        super(uriPatternsAndAllowedHttpMethods());
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/zone/management/ZoneManagementException.java b/service/src/main/java/com/ge/predix/acs/zone/management/ZoneManagementException.java
new file mode 100644
index 0000000..a661349
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/zone/management/ZoneManagementException.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.zone.management;
+
+public class ZoneManagementException extends RuntimeException {
+
+    private static final long serialVersionUID = -1231913762426270378L;
+
+    public ZoneManagementException() {
+        super();
+    }
+
+    public ZoneManagementException(final String message, final Throwable cause, final boolean enableSuppression,
+            final boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+    public ZoneManagementException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    public ZoneManagementException(final String message) {
+        super(message);
+    }
+
+    public ZoneManagementException(final Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/zone/management/ZoneService.java b/service/src/main/java/com/ge/predix/acs/zone/management/ZoneService.java
new file mode 100644
index 0000000..a9c844f
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/zone/management/ZoneService.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.zone.management;
+
+import com.ge.predix.acs.rest.Zone;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+public interface ZoneService {
+
+    boolean upsertZone(Zone zone);
+
+    Zone retrieveZone(String zoneName);
+
+    Boolean deleteZone(String zoneName);
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/zone/management/ZoneServiceImpl.java b/service/src/main/java/com/ge/predix/acs/zone/management/ZoneServiceImpl.java
new file mode 100644
index 0000000..6013abf
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/zone/management/ZoneServiceImpl.java
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.zone.management;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.privilege.management.dao.ResourceEntity;
+import com.ge.predix.acs.privilege.management.dao.ResourceRepository;
+import com.ge.predix.acs.privilege.management.dao.SubjectEntity;
+import com.ge.predix.acs.privilege.management.dao.SubjectRepository;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.zone.management.dao.ZoneConverter;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.ge.predix.acs.zone.management.dao.ZoneRepository;
+
+@Component
+@SuppressWarnings("nls")
+public class ZoneServiceImpl implements ZoneService {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ZoneServiceImpl.class);
+
+    @Autowired
+    private ZoneRepository zoneRepository;
+
+    @Autowired
+    @Qualifier("resourceRepository")
+    private ResourceRepository resourceRepository;
+
+    @Autowired
+    @Qualifier("subjectRepository")
+    private SubjectRepository subjectRepository;
+
+    private final ZoneConverter zoneConverter = new ZoneConverter();
+
+    private static final String SUBDOMAIN_REGEX = "(?:[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9]|[A-Za-z0-9])";
+    private static final Pattern SUBDOMAIN_PATTERN;
+
+    static {
+        SUBDOMAIN_PATTERN = Pattern.compile(SUBDOMAIN_REGEX);
+    }
+
+    @Override
+    public boolean upsertZone(final Zone zone) {
+        LOGGER.debug("upsertZone Request for: {}", zone);
+        validateAndSanitizeInputOrFail(zone);
+        boolean isEntityUpdate = false;
+        ZoneEntity zoneEntity = this.zoneRepository.getByName(zone.getName());
+        ZoneEntity zoneWithSameSubdomain = this.zoneRepository.getBySubdomain(zone.getSubdomain());
+        if (null != zoneWithSameSubdomain && null == zoneEntity) {
+            // there is already a zone with proposed subdomain and it is not an
+            // update to an existing Zone
+            String message = String.format("Subdomain %s for zoneName = %s is already being used.", zone.getSubdomain(),
+                    zone.getName());
+            throw new ZoneManagementException(message);
+        }
+        try {
+
+            if (null == zoneEntity) {
+                zoneEntity = this.zoneConverter.toZoneEntity(zone);
+            } else {
+                isEntityUpdate = true;
+                LOGGER.debug("Updating existing Zone {} {}", zoneEntity.getName(), zoneEntity.getSubdomain());
+                zoneEntity.setName(zone.getName());
+                zoneEntity.setDescription(zone.getDescription());
+                zoneEntity.setSubdomain(zone.getSubdomain());
+            }
+            this.zoneRepository.save(zoneEntity);
+            return isEntityUpdate;
+
+        } catch (Exception e) {
+            String message = String.format("Unable to persist Zone identified by zoneName = %s", zone.getName());
+            throw new ZoneManagementException(message, e);
+        }
+    }
+
+    @Override
+    public Zone retrieveZone(final String zoneName) {
+
+        ZoneEntity currentZone = this.zoneRepository.getByName(zoneName);
+        if (currentZone == null) {
+            String message = String.format("No Zone identified by zoneName = %s", zoneName);
+            throw new ZoneManagementException(message);
+        }
+        return this.zoneConverter.toZone(currentZone);
+    }
+
+    private void validateAndSanitizeInputOrFail(final Zone zone) {
+        this.validateSubdomainNames(zone.getSubdomain());
+        if (StringUtils.isEmpty(zone.getName())) {
+            throw new ZoneManagementException("Empty or Null Zone Name: The Zone Name is mandatory.");
+        }
+        if (StringUtils.isEmpty(zone.getSubdomain())) {
+            throw new ZoneManagementException("Empty or Null Zone Subdomain: The Zone Subdomain is mandatory.");
+        }
+        if (StringUtils.isEmpty(zone.getDescription())) {
+            zone.setDescription(zone.getName() + " descrption");
+        }
+    }
+
+    private void validateSubdomainNames(final String subDomain) {
+        if (!SUBDOMAIN_PATTERN.matcher(subDomain).matches()) {
+            throw new ZoneManagementException("Invalid Zone Subdomain: " + subDomain);
+        }
+    }
+
+    @Override
+    public Boolean deleteZone(final String zoneName) {
+        ZoneEntity currentZone = this.zoneRepository.getByName(zoneName);
+        if (currentZone != null) {
+            // Delete child entities in Graph repos first. This is not transactional.
+            List<ResourceEntity> resourcesInZone = this.resourceRepository.findByZone(currentZone);
+            this.resourceRepository.delete(resourcesInZone);
+            List<SubjectEntity> subjectsInZone = this.subjectRepository.findByZone(currentZone);
+            this.subjectRepository.delete(subjectsInZone);
+
+            this.zoneRepository.delete(currentZone);
+        }
+        return currentZone != null;
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/zone/management/dao/ZoneClientEntity.java b/service/src/main/java/com/ge/predix/acs/zone/management/dao/ZoneClientEntity.java
new file mode 100644
index 0000000..487501a
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/zone/management/dao/ZoneClientEntity.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.zone.management.dao;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import com.ge.predix.acs.issuer.management.dao.IssuerEntity;
+
+/**
+ *
+ *
+ * This class is no longer used except in migration logic.
+ *
+ */
+@Entity
+@Table(
+        name = "authorization_zone_client",
+        uniqueConstraints = { @UniqueConstraint(columnNames = { "issuer_id", "client_id", "authorization_zone_id" }) })
+@Deprecated
+public class ZoneClientEntity {
+
+    @Id
+    @Column(name = "id")
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private long id;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "issuer_id", referencedColumnName = "id", nullable = false, updatable = false)
+    private IssuerEntity issuer;
+
+    @Column(name = "client_id", nullable = false)
+    private String clientId;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "authorization_zone_id")
+    private ZoneEntity zone;
+
+    public long getId() {
+        return this.id;
+    }
+
+    public void setId(final long id) {
+        this.id = id;
+    }
+
+    public IssuerEntity getIssuer() {
+        return this.issuer;
+    }
+
+    public void setIssuer(final IssuerEntity issuer) {
+        this.issuer = issuer;
+    }
+
+    public String getClientId() {
+        return this.clientId;
+    }
+
+    public void setClientId(final String clientId) {
+        this.clientId = clientId;
+    }
+
+    public ZoneEntity getZone() {
+        return this.zone;
+    }
+
+    public void setZone(final ZoneEntity zone) {
+        this.zone = zone;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(this.clientId).append(this.issuer).append(this.zone).toHashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof ZoneClientEntity) {
+            final ZoneClientEntity other = (ZoneClientEntity) obj;
+            return new EqualsBuilder().append(this.clientId, other.clientId).append(this.issuer, other.issuer)
+                    .append(this.zone, other.zone).isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "ZoneClientEntity [id=" + this.id + ", issuer=" + this.issuer + ", clientId=" + this.clientId
+                + ", zoneName=" + this.zone.getName()
+                /*
+                 * Note: do not iterate the zone object, or will cause stackoverflow error
+                 */
+                + "]";
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/zone/management/dao/ZoneConverter.java b/service/src/main/java/com/ge/predix/acs/zone/management/dao/ZoneConverter.java
new file mode 100644
index 0000000..d54b567
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/zone/management/dao/ZoneConverter.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.zone.management.dao;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import com.ge.predix.acs.rest.Zone;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+public class ZoneConverter {
+
+    public ZoneEntity toZoneEntity(final Zone zone) {
+
+        if (zone == null) {
+            return null;
+        }
+
+        ZoneEntity entity = new ZoneEntity();
+        entity.setName(zone.getName());
+        entity.setSubdomain(zone.getSubdomain());
+        entity.setDescription(zone.getDescription());
+        return entity;
+    }
+
+    public Zone toZone(final ZoneEntity zoneEntity) {
+
+        if (zoneEntity == null) {
+            return null;
+        }
+
+        Zone zone = new Zone();
+        zone.setName(zoneEntity.getName());
+        zone.setSubdomain(zoneEntity.getSubdomain());
+        zone.setDescription(zoneEntity.getDescription());
+        return zone;
+    }
+
+    public Set<Zone> toZone(final Set<ZoneEntity> zoneEntities) {
+        Set<Zone> result = new HashSet<>();
+        for (ZoneEntity ze : zoneEntities) {
+            result.add(toZone(ze));
+        }
+        return result;
+    }
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/zone/management/dao/ZoneEntity.java b/service/src/main/java/com/ge/predix/acs/zone/management/dao/ZoneEntity.java
new file mode 100644
index 0000000..4a9d207
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/zone/management/dao/ZoneEntity.java
@@ -0,0 +1,210 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.zone.management.dao;
+
+import java.io.IOException;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ge.predix.acs.privilege.management.dao.ResourceEntity;
+import com.ge.predix.acs.privilege.management.dao.SubjectEntity;
+import com.ge.predix.acs.rest.AttributeConnector;
+import com.ge.predix.acs.service.policy.admin.dao.PolicySetEntity;
+
+@Entity
+@Table(
+        name = "authorization_zone",
+        uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }),
+                @UniqueConstraint(columnNames = { "subdomain" }) })
+public class ZoneEntity {
+
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+    @Id
+    @Column(name = "id")
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private long id;
+
+    @Column(name = "name", nullable = false, unique = true)
+    private String name;
+
+    @Column(name = "description", nullable = false)
+    private String description;
+
+    @Column(name = "subdomain", nullable = false, unique = true)
+    private String subdomain;
+
+    @OneToMany(
+            mappedBy = "zone",
+            cascade = { CascadeType.MERGE, CascadeType.REFRESH, CascadeType.REMOVE },
+            fetch = FetchType.LAZY)
+    private Set<SubjectEntity> subjects;
+
+    @OneToMany(
+            mappedBy = "zone",
+            cascade = { CascadeType.MERGE, CascadeType.REFRESH, CascadeType.REMOVE },
+            fetch = FetchType.LAZY)
+    private Set<ResourceEntity> resources;
+
+    @OneToMany(
+            mappedBy = "zone",
+            cascade = { CascadeType.MERGE, CascadeType.REFRESH, CascadeType.REMOVE },
+            fetch = FetchType.LAZY)
+    private Set<PolicySetEntity> policySets;
+
+    @Column(name = "resource_attribute_connector_json", nullable = true)
+    private String resourceConnectorJson;
+
+    @Column(name = "subject_attribute_connector_json", nullable = true)
+    private String subjectConnectorJson;
+
+    private AttributeConnector cachedResourceConnector;
+
+    private AttributeConnector cachedSubjectConnector;
+
+    public ZoneEntity() {
+    }
+
+    public ZoneEntity(final Long id) {
+        this.id = id;
+    }
+
+    public ZoneEntity(final Long id, final String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    public long getId() {
+        return this.id;
+    }
+
+    public void setId(final long id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    public String getSubdomain() {
+        return this.subdomain;
+    }
+
+    public void setSubdomain(final String subdomain) {
+        this.subdomain = subdomain;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(this.name).toHashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof ZoneEntity) {
+            ZoneEntity other = (ZoneEntity) obj;
+            return new EqualsBuilder().append(this.getName(), other.getName()).isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "ZoneEntity [id=" + this.id + ", name=" + this.name + ", description=" + this.description
+                + ", subdomain=" + this.subdomain + "]";
+    }
+
+    public AttributeConnector getResourceAttributeConnector() {
+        if (null == this.cachedResourceConnector) {
+            this.cachedResourceConnector = connectorFromJson(this.resourceConnectorJson);
+        }
+        return this.cachedResourceConnector;
+    }
+
+    public void setResourceAttributeConnector(final AttributeConnector connector) {
+        this.resourceConnectorJson = jsonFromConnector(connector);
+        this.cachedResourceConnector = connector;
+    }
+
+    public AttributeConnector getSubjectAttributeConnector() {
+        if (null == this.cachedSubjectConnector) {
+            this.cachedSubjectConnector = connectorFromJson(this.subjectConnectorJson);
+        }
+        return this.cachedSubjectConnector;
+    }
+
+    public void setSubjectAttributeConnector(final AttributeConnector connector) {
+        this.subjectConnectorJson = jsonFromConnector(connector);
+        this.cachedSubjectConnector = connector;
+    }
+    
+    private String jsonFromConnector(final AttributeConnector connector) {
+        if (null == connector) {
+            return null;
+        }
+
+        String connectorJson;
+        try {
+            connectorJson = OBJECT_MAPPER.writeValueAsString(connector);
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+        return connectorJson;
+    }
+
+    private AttributeConnector connectorFromJson(final String connectorJson) {
+        if (null == connectorJson) {
+            return null;
+        }
+
+        AttributeConnector connector;
+        try {
+            connector = OBJECT_MAPPER.readValue(connectorJson, AttributeConnector.class);
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+        return connector;
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/zone/management/dao/ZoneRepository.java b/service/src/main/java/com/ge/predix/acs/zone/management/dao/ZoneRepository.java
new file mode 100644
index 0000000..5efe0ff
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/zone/management/dao/ZoneRepository.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.zone.management.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface ZoneRepository extends JpaRepository<ZoneEntity, Long> {
+
+    ZoneEntity getBySubdomain(String subdomain);
+
+    ZoneEntity getByName(String name);
+
+}
diff --git a/service/src/main/java/com/ge/predix/acs/zone/resolver/SpringSecurityZoneResolver.java b/service/src/main/java/com/ge/predix/acs/zone/resolver/SpringSecurityZoneResolver.java
new file mode 100644
index 0000000..8852215
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/zone/resolver/SpringSecurityZoneResolver.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.zone.resolver;
+
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+
+import com.ge.predix.acs.commons.web.ZoneDoesNotExistException;
+import com.ge.predix.acs.request.context.AcsRequestContext;
+import com.ge.predix.acs.request.context.AcsRequestContext.ACSRequestContextAttribute;
+import com.ge.predix.acs.request.context.AcsRequestContextHolder;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.ge.predix.uaa.token.lib.ZoneOAuth2Authentication;
+
+@Component
+public class SpringSecurityZoneResolver implements ZoneResolver {
+
+    public static ZoneEntity getZoneEntity() {
+        AcsRequestContext acsRequestContext = AcsRequestContextHolder.getAcsRequestContext();
+        ZoneEntity result = (ZoneEntity) acsRequestContext.get(ACSRequestContextAttribute.ZONE_ENTITY);
+
+        // This could happen if a zone was removed from ACS but a registration still exists in ZAC
+        if (null == result) {
+            ZoneOAuth2Authentication zoneAuth = (ZoneOAuth2Authentication) SecurityContextHolder.getContext()
+                    .getAuthentication();
+            throw new ZoneDoesNotExistException("The zone '" + zoneAuth.getZoneId() + "' does not exist.");
+        }
+
+        return result;
+    }
+    
+    @Override
+    public ZoneEntity getZoneEntityOrFail() {
+        return getZoneEntity();
+    }
+    
+    public static String getZoneName() {
+        return getZoneEntity().getName();
+    }
+}
diff --git a/service/src/main/java/com/ge/predix/acs/zone/resolver/ZoneResolver.java b/service/src/main/java/com/ge/predix/acs/zone/resolver/ZoneResolver.java
new file mode 100644
index 0000000..82ab3cf
--- /dev/null
+++ b/service/src/main/java/com/ge/predix/acs/zone/resolver/ZoneResolver.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.zone.resolver;
+
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+@FunctionalInterface
+public interface ZoneResolver {
+    ZoneEntity getZoneEntityOrFail();
+}
diff --git a/service/src/main/java/db/postgres/PolicySetRowMapper.java b/service/src/main/java/db/postgres/PolicySetRowMapper.java
new file mode 100644
index 0000000..348fecf
--- /dev/null
+++ b/service/src/main/java/db/postgres/PolicySetRowMapper.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package db.postgres;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.ge.predix.acs.service.policy.admin.dao.PolicySetEntity;
+
+public class PolicySetRowMapper implements RowMapper<PolicySetEntity> {
+
+    @Override
+    public PolicySetEntity mapRow(final ResultSet rs, final int rowNum) throws SQLException {
+        return new PolicySetEntity(null, rs.getString("policy_set_id"), rs.getString("policy_set_json"));
+    }
+
+}
diff --git a/service/src/main/java/db/postgres/ResourceRowMapper.java b/service/src/main/java/db/postgres/ResourceRowMapper.java
new file mode 100644
index 0000000..a9f3a92
--- /dev/null
+++ b/service/src/main/java/db/postgres/ResourceRowMapper.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package db.postgres;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.ge.predix.acs.privilege.management.dao.ResourceEntity;
+
+public class ResourceRowMapper implements RowMapper<ResourceEntity> {
+
+    @Override
+    public ResourceEntity mapRow(final ResultSet rs, final int rowNum) throws SQLException {
+        ResourceEntity resource = new ResourceEntity();
+        resource.setResourceIdentifier(rs.getString("resource_identifier"));
+        resource.setAttributesAsJson(rs.getString("attributes"));
+        return resource;
+    }
+}
diff --git a/service/src/main/java/db/postgres/SubjectRowMapper.java b/service/src/main/java/db/postgres/SubjectRowMapper.java
new file mode 100644
index 0000000..7004594
--- /dev/null
+++ b/service/src/main/java/db/postgres/SubjectRowMapper.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package db.postgres;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.ge.predix.acs.privilege.management.dao.SubjectEntity;
+
+public class SubjectRowMapper implements RowMapper<SubjectEntity> {
+
+    @Override
+    public SubjectEntity mapRow(final ResultSet rs, final int rowNum) throws SQLException {
+        SubjectEntity subject = new SubjectEntity();
+        subject.setSubjectIdentifier(rs.getString("subject_identifier"));
+        subject.setAttributesAsJson(rs.getString("attributes"));
+        return subject;
+    }
+
+}
diff --git a/service/src/main/java/db/postgres/V2_0_1__InitializeIdentityZones.java b/service/src/main/java/db/postgres/V2_0_1__InitializeIdentityZones.java
new file mode 100644
index 0000000..408df1b
--- /dev/null
+++ b/service/src/main/java/db/postgres/V2_0_1__InitializeIdentityZones.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package db.postgres;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.flywaydb.core.api.migration.spring.SpringJdbcMigration;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.PreparedStatementCreator;
+import org.springframework.jdbc.support.GeneratedKeyHolder;
+import org.springframework.jdbc.support.KeyHolder;
+import org.springframework.jdbc.support.rowset.SqlRowSet;
+
+import com.ge.predix.acs.privilege.management.dao.ResourceEntity;
+import com.ge.predix.acs.privilege.management.dao.SubjectEntity;
+import com.ge.predix.acs.service.policy.admin.dao.PolicySetEntity;
+import com.ge.predix.acs.zone.management.dao.ZoneClientEntity;
+
+//CHECKSTYLE:OFF
+//Naming convention for the class name is being enforced by spring
+@SuppressWarnings("deprecation")
+public class V2_0_1__InitializeIdentityZones implements SpringJdbcMigration {
+    // CHECKSTYLE:ON
+
+    @Override
+    public void migrate(final JdbcTemplate jdbcTemplate) throws Exception {
+        Long acsAuthorizationZoneId = createDefaultAuthzZone(jdbcTemplate);
+        /*
+         * Let's find all the possible OAuth issuer_id and client_id combinations that exist in the old database
+         * schema.
+         */
+        Set<ZoneClientEntity> existingOAuthClients = findExistingOAuthClients(jdbcTemplate);
+        addOAuthClientsToDefaultAuthzZone(jdbcTemplate, acsAuthorizationZoneId, existingOAuthClients);
+        dropConstraintsAndColumns(jdbcTemplate);
+        removeDuplicateRows(jdbcTemplate, acsAuthorizationZoneId);
+    }
+
+    private Long createDefaultAuthzZone(final JdbcTemplate jdbcTemplate) {
+        final String insertZoneSql =
+                "INSERT INTO authorization_zone (name, description, subdomain) " + "VALUES (?,?,?)";
+        KeyHolder holder = new GeneratedKeyHolder();
+
+        jdbcTemplate.update(new PreparedStatementCreator() {
+
+            @Override
+            public PreparedStatement createPreparedStatement(final Connection connection) throws SQLException {
+                PreparedStatement ps = connection.prepareStatement(insertZoneSql, new String[] { "id" });
+                ps.setString(1, "apm-migrated");
+                ps.setString(2, "APM Migrated Zone from mvp1");
+                ps.setString(3, "apm-migrated");
+                return ps;
+            }
+        }, holder);
+
+        return holder.getKey().longValue();
+    }
+
+    private Set<ZoneClientEntity> findExistingOAuthClients(final JdbcTemplate jdbcTemplate) {
+        Set<ZoneClientEntity> oauthClients = new HashSet<>();
+        List<ZoneClientEntity> subjectOAuthClients = jdbcTemplate
+                .query("SELECT DISTINCT issuer_id, client_id FROM subject", new ZoneClientRowMapper());
+        oauthClients.addAll(subjectOAuthClients);
+
+        List<ZoneClientEntity> resourceOAuthClients = jdbcTemplate
+                .query("SELECT DISTINCT issuer_id, client_id FROM resource", new ZoneClientRowMapper());
+        oauthClients.addAll(resourceOAuthClients);
+
+        List<ZoneClientEntity> policySetOAuthClients = jdbcTemplate
+                .query("SELECT DISTINCT issuer_id, client_id FROM policy_set", new ZoneClientRowMapper());
+        oauthClients.addAll(policySetOAuthClients);
+        return oauthClients;
+    }
+
+    private void addOAuthClientsToDefaultAuthzZone(final JdbcTemplate jdbcTemplate, final Long acsAuthorizationZoneId,
+            final Set<ZoneClientEntity> existingOAuthClients) {
+
+        for (ZoneClientEntity oauthClient : existingOAuthClients) {
+            jdbcTemplate.update("INSERT INTO authorization_zone_client (issuer_id, client_id,"
+                            + " authorization_zone_id) VALUES (?,?,?)", Long.valueOf(oauthClient.getIssuer()
+                            .getIssuerId()),
+                    oauthClient.getClientId(), acsAuthorizationZoneId);
+        }
+    }
+
+    private void dropConstraintsAndColumns(final JdbcTemplate jdbcTemplate) {
+        jdbcTemplate.execute("ALTER TABLE resource DROP CONSTRAINT unique_issuer_client_resource_identifier;");
+        jdbcTemplate.execute("ALTER TABLE resource DROP COLUMN issuer_id;");
+        jdbcTemplate.execute("ALTER TABLE resource DROP COLUMN client_id;");
+        jdbcTemplate.execute("ALTER TABLE subject DROP CONSTRAINT unique_issuer_client_subject_identifier;");
+        jdbcTemplate.execute("ALTER TABLE subject DROP COLUMN issuer_id;");
+        jdbcTemplate.execute("ALTER TABLE subject DROP COLUMN client_id;");
+        jdbcTemplate.execute("ALTER TABLE policy_set DROP CONSTRAINT unique_issuer_client_pset;");
+        jdbcTemplate.execute("ALTER TABLE policy_set DROP COLUMN issuer_id;");
+        jdbcTemplate.execute("ALTER TABLE policy_set DROP COLUMN client_id;");
+    }
+
+    private void removeDuplicateRows(final JdbcTemplate jdbcTemplate, final Long zone) {
+        final List<SubjectEntity> subjects = jdbcTemplate
+                .query("SELECT DISTINCT subject_identifier, attributes FROM subject", new SubjectRowMapper());
+        jdbcTemplate.update("DELETE FROM subject *");
+        for (SubjectEntity s : subjects) {
+            jdbcTemplate.update("INSERT INTO subject (subject_identifier, attributes, "
+                            + " authorization_zone_id) VALUES (?,?,?)", s.getSubjectIdentifier(), s
+                            .getAttributesAsJson(),
+                    zone);
+        }
+        final List<ResourceEntity> resources = jdbcTemplate
+                .query("SELECT DISTINCT resource_identifier, attributes FROM resource", new ResourceRowMapper());
+        jdbcTemplate.update("DELETE FROM resource *");
+        for (ResourceEntity r : resources) {
+            jdbcTemplate.update("INSERT INTO resource (resource_identifier, attributes, "
+                            + " authorization_zone_id) VALUES (?,?,?)", r.getResourceIdentifier(), r
+                            .getAttributesAsJson(),
+                    zone);
+        }
+
+        final List<PolicySetEntity> policysets = jdbcTemplate
+                .query("SELECT DISTINCT policy_set_id, policy_set_json FROM policy_set", new PolicySetRowMapper());
+        jdbcTemplate.update("DELETE FROM policy_set *");
+        for (PolicySetEntity ps : policysets) {
+            SqlRowSet row = jdbcTemplate
+                    .queryForRowSet("SELECT * FROM policy_set WHERE policy_set_id =?", ps.getPolicySetID());
+            if (row.next()) {
+                jdbcTemplate.update("UPDATE policy_set SET policy_set_json = ? WHERE policy_set_id = ?",
+                        ps.getPolicySetJson(), ps.getPolicySetID());
+            } else {
+                jdbcTemplate.update("INSERT INTO policy_set (policy_set_id, policy_set_json, "
+                        + " authorization_zone_id) VALUES (?,?,?)", ps.getPolicySetID(), ps.getPolicySetJson(), zone);
+            }
+        }
+    }
+}
diff --git a/service/src/main/java/db/postgres/ZoneClientRowMapper.java b/service/src/main/java/db/postgres/ZoneClientRowMapper.java
new file mode 100644
index 0000000..ac4e617
--- /dev/null
+++ b/service/src/main/java/db/postgres/ZoneClientRowMapper.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package db.postgres;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import com.ge.predix.acs.issuer.management.dao.IssuerEntity;
+import com.ge.predix.acs.zone.management.dao.ZoneClientEntity;
+
+@SuppressWarnings("deprecation")
+public class ZoneClientRowMapper implements RowMapper<ZoneClientEntity> {
+
+    @Override
+    public ZoneClientEntity mapRow(final ResultSet rs, final int rowNum) throws SQLException {
+        ZoneClientEntity zoneClient = new ZoneClientEntity();
+        zoneClient.setClientId(rs.getString("client_id"));
+        IssuerEntity issuer = new IssuerEntity();
+        issuer.setIssuerId(rs.getString("issuer_id"));
+        zoneClient.setIssuer(issuer);
+        return zoneClient;
+    }
+
+}
diff --git a/service/src/main/resources/META-INF/persistence.xml b/service/src/main/resources/META-INF/persistence.xml
new file mode 100644
index 0000000..99afa5d
--- /dev/null
+++ b/service/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<persistence xmlns="http://java.sun.com/xml/ns/persistence"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
+    version="1.0">
+
+    <persistence-unit name="default">
+        <description>My Persistence Unit</description>
+        <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
+        <properties>
+<!--        <property name="openjpa.Log" value="DefaultLevel=WARN, Runtime=INFO, Tool=INFO, SQL=TRACE"/>
+            <property name="openjpa.ConnectionFactoryProperties" value="PrintParameters=true" /> -->
+        </properties>
+    </persistence-unit>
+
+</persistence>
diff --git a/service/src/main/resources/acs-policy-set-schema.json b/service/src/main/resources/acs-policy-set-schema.json
new file mode 100644
index 0000000..86a8b1d
--- /dev/null
+++ b/service/src/main/resources/acs-policy-set-schema.json
@@ -0,0 +1,114 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "id": "http://jsonschema.net",
+    "type": "object",	
+    "properties": {
+        "name": {
+            "id": "http://jsonschema.net/name",
+            "type": "string"
+        },
+        "policies": {
+            "id": "http://jsonschema.net/policies",
+            "type": "array",
+            "minItems": 1,
+            "items": {
+                "id": "http://jsonschema.net/policies/0",
+                "type": "object",
+                "required": ["effect"],
+                "properties": {
+                    "target": {
+                        "id": "http://jsonschema.net/policies/0/target",
+                        "type": "object",
+                        "required": ["resource"],
+                        "properties": {
+                            "name": {
+                                "id": "http://jsonschema.net/policies/0/target/name",
+                                "type": "string"
+                            },
+                            "resource": {
+                                "id": "http://jsonschema.net/policies/0/target/resource",
+                                "type": "object",
+                                "required": ["uriTemplate"],
+                                "properties": {
+                                    "name": {
+                                        "id": "http://jsonschema.net/policies/0/target/resource/name",
+                                        "type": "string"
+                                    },
+                                    "uriTemplate": {
+                                        "id": "http://jsonschema.net/policies/0/target/resource/uriTemplate",
+                                        "type": "string"
+                                    },
+                                    "attributeUriTemplate": {
+                                        "id": "http://jsonschema.net/policies/0/target/resource/attributeUriTemplate",
+                                        "type": "string"
+                                    }
+                                }
+                            },
+                            "action": {
+                                "id": "http://jsonschema.net/policies/0/target/action",
+                                "type": "string"
+                            },
+                            "subject": {
+                                "id": "http://jsonschema.net/policies/0/target/subject",
+                                "type": "object",
+                                "required": ["attributes"],
+                                "properties": {
+                                    "name": {
+                                        "id": "http://jsonschema.net/policies/0/target/subject/name",
+                                        "type": "string"
+                                    },
+                                    "attributes": {
+                                        "id": "http://jsonschema.net/policies/0/target/subject/attributes",
+                                        "type": "array",
+                                        "items": {
+                                            "id": "http://jsonschema.net/policies/0/target/subject/attributes/0",
+                                            "type": "object",
+                                            "properties": {
+                                                "issuer": {
+                                                    "id": "http://jsonschema.net/policies/0/target/subject/attributes/0/issuer",
+                                                    "type": "string"
+                                                },
+                                                "name": {
+                                                    "id": "http://jsonschema.net/policies/0/target/subject/attributes/0/name",
+                                                    "type": "string"
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    },
+                    "name": {
+                        "id": "http://jsonschema.net/policies/0/name",
+                        "type": "string"
+                    },
+                    "conditions": {
+                        "id": "http://jsonschema.net/policies/0/conditions",
+                        "type": "array",
+                        "items": {
+                            "id": "http://jsonschema.net/policies/0/conditions/0",
+                            "type": "object",
+                            "properties": {
+                                "name": {
+                                    "id": "http://jsonschema.net/policies/0/conditions/0/name",
+                                    "type": "string"
+                                },
+                                "condition": {
+                                    "id": "http://jsonschema.net/policies/0/conditions/0/condition",
+                                    "type": "string"
+                                }
+                            },
+                            "required": ["condition"]
+                        }
+                    },
+                    "effect": {
+                        "id": "http://jsonschema.net/policies/0/effect",
+                        "type": "string"
+                    }
+                }
+            }
+        }
+    },
+    "required": ["policies", "name"]    
+}
diff --git a/service/src/main/resources/application-h2.properties b/service/src/main/resources/application-h2.properties
new file mode 100644
index 0000000..bb228a8
--- /dev/null
+++ b/service/src/main/resources/application-h2.properties
@@ -0,0 +1,20 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+#flyway config
+flyway.locations=db/h2
diff --git a/service/src/main/resources/application-performance.properties b/service/src/main/resources/application-performance.properties
new file mode 100644
index 0000000..42c537f
--- /dev/null
+++ b/service/src/main/resources/application-performance.properties
@@ -0,0 +1,25 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+#Sets the profile for Performance tests
+spring.jmx.enabled=${JMX_ENABLED}
+endpoints.jmx.enabled=${JMX_ENABLED}
+endpoints.jolokia.enabled=${JOLOKIA_ENABLED}
+server.session-timeout=${APP_SERVER_SESSION_TIMEOUT}
+
+#logging.level.com.ge.predix.acs=DEBUG
diff --git a/service/src/main/resources/application-production.properties b/service/src/main/resources/application-production.properties
new file mode 100644
index 0000000..d17d31f
--- /dev/null
+++ b/service/src/main/resources/application-production.properties
@@ -0,0 +1,20 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+#FIXME:  This file should not be in the acs repo ?
+logging.level.com.ge.predix.acs=INFO
\ No newline at end of file
diff --git a/service/src/main/resources/application.properties b/service/src/main/resources/application.properties
new file mode 100644
index 0000000..c3f247a
--- /dev/null
+++ b/service/src/main/resources/application.properties
@@ -0,0 +1,58 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+# Listen port for ACS service
+server.port = ${ACS_LOCAL_PORT}
+
+#nurego metering config
+NUREGO_BATCH_INTERVAL_SECONDS=3600
+NUREGO_BATCH_MAX_MAP_SIZE=1024
+
+#flyway config
+flyway.locations=db/postgres
+
+uaaCheckHealthUrl=${UAA_CHECK_HEALTH_URL:${ACS_UAA_URL}/healthz}
+zacCheckHealthUrl=${ZAC_CHECK_HEALTH_URL:${ZAC_URL}/health}
+
+# JMX and Health endpoints disabled by default
+endpoints.enabled=false
+management.security.roles=acs.monitoring
+management.health.redis.enabled=false
+management.health.diskspace.enabled=false
+management.health.db.enabled=false
+endpoints.health.enabled=true
+endpoints.info.enabled=true
+health.hystrix.enabled=false
+
+# Enable these for JMX and Jolokia endpoints
+endpoints.jmx.enabled=false
+endpoints.jolokia.enabled=false
+
+info.app.name=ACS Service
+info.app.description=Predix Access Control Service
+info.app.version=@jenkins.build.number@
+METER_BASE_DOMAIN=${ACS_BASE_DOMAIN}
+
+cors.xhr.allowed.headers = Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method, Access-Control-Request-Headers
+cors.xhr.allowed.origins = ${CORS_XHR_ALLOWED_ORIGINS:}
+cors.xhr.allowed.uris = ^/v2/api-docs$
+cors.xhr.controlmaxage = 1728000
+cors.xhr.allowed.methods = GET
+
+spring.zipkin.enabled=false
+spring.sleuth.traceId128=true
\ No newline at end of file
diff --git a/service/src/main/resources/banner.txt b/service/src/main/resources/banner.txt
new file mode 100644
index 0000000..1b6c3c8
--- /dev/null
+++ b/service/src/main/resources/banner.txt
@@ -0,0 +1,7 @@
+     ___       ______     _______.
+    /   \     /      |   /       |
+   /  ^  \   |  ,----'  |   (----`
+  /  /_\  \  |  |        \   \    
+ /  _____  \ |  `----.----)   |   
+/__/     \__\ \______|_______/    
+                                  
\ No newline at end of file
diff --git a/service/src/main/resources/db/h2/V0__service_instance.sql b/service/src/main/resources/db/h2/V0__service_instance.sql
new file mode 100644
index 0000000..4d0888f
--- /dev/null
+++ b/service/src/main/resources/db/h2/V0__service_instance.sql
@@ -0,0 +1,45 @@
+CREATE TABLE `policy_set` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `client_id` varchar(128) NOT NULL,
+  `issuer_id` varchar(128) NOT NULL,
+  `policy_set_id` varchar(128) NOT NULL,
+  `policy_set_json` MEDIUMTEXT NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+CREATE TABLE `resource` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `issuer_id` int(18) NOT NULL,
+  `client_id` varchar(128) NOT NULL,
+  `resource_identifier` varchar(128) NOT NULL,
+  `resource_id` varchar(128) NOT NULL,
+  `attributes` MEDIUMTEXT NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+CREATE TABLE `subject` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `issuer_id` int(18) NOT NULL,
+  `client_id` varchar(128) NOT NULL,
+  `subject_identifier` varchar(128) NOT NULL,
+  `subject_id` varchar(128) NOT NULL,
+  `attributes` MEDIUMTEXT NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+CREATE TABLE `issuer` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `issuer_id` varchar(128) NOT NULL,
+  `issuer_check_token_url` varchar(128) NOT NULL,
+  PRIMARY KEY (`id`)  
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+ALTER TABLE policy_set ADD CONSTRAINT unique_issuer_client_pset UNIQUE (issuer_id, client_id, policy_set_id);
+
+ALTER TABLE issuer ADD CONSTRAINT unique_issuer_id UNIQUE (issuer_id);
+
+ALTER TABLE resource ADD CONSTRAINT unique_issuer_client_resource_identifier UNIQUE (issuer_id, client_id, resource_identifier);
+ALTER TABLE resource ADD CONSTRAINT unique_issuer_client_resource_id UNIQUE (issuer_id, client_id, resource_id);
+
+ALTER TABLE subject ADD CONSTRAINT unique_issuer_client_subject_id UNIQUE (issuer_id, client_id, subject_id);
+ALTER TABLE subject ADD CONSTRAINT unique_issuer_client_subject_identifier UNIQUE (issuer_id, client_id, subject_identifier);
\ No newline at end of file
diff --git a/service/src/main/resources/db/h2/V1__drop_subject_resource_id_constraints_columns.sql b/service/src/main/resources/db/h2/V1__drop_subject_resource_id_constraints_columns.sql
new file mode 100644
index 0000000..5662680
--- /dev/null
+++ b/service/src/main/resources/db/h2/V1__drop_subject_resource_id_constraints_columns.sql
@@ -0,0 +1,4 @@
+ALTER TABLE resource DROP CONSTRAINT unique_issuer_client_resource_id;
+ALTER TABLE resource DROP COLUMN resource_id;
+ALTER TABLE subject DROP CONSTRAINT unique_issuer_client_subject_id;
+ALTER TABLE subject DROP COLUMN subject_id;
\ No newline at end of file
diff --git a/service/src/main/resources/db/h2/V2_0_0__create_authz_zones.sql b/service/src/main/resources/db/h2/V2_0_0__create_authz_zones.sql
new file mode 100644
index 0000000..5d3612b
--- /dev/null
+++ b/service/src/main/resources/db/h2/V2_0_0__create_authz_zones.sql
@@ -0,0 +1,22 @@
+CREATE TABLE `authorization_zone` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) NOT NULL,
+  `description` varchar(1024) NOT NULL,
+  `subdomain` varchar(255) NOT NULL,
+  PRIMARY KEY(`id`),
+  UNIQUE KEY `name` (`name`),
+  UNIQUE KEY `subdomain` (`subdomain`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+CREATE TABLE `authorization_zone_client` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `issuer_id` int(18) NOT NULL,
+  `client_id` varchar(255) NOT NULL,
+  `authorization_zone_id` int(18) NOT NULL,
+  PRIMARY KEY(`id`),
+  UNIQUE KEY `client_in_zone` (`issuer_id`,`client_id`,`authorization_zone_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+ALTER TABLE subject ADD COLUMN authorization_zone_id int(18) DEFAULT 0;
+ALTER TABLE resource ADD COLUMN authorization_zone_id int(18) DEFAULT 0;
+ALTER TABLE policy_set ADD COLUMN authorization_zone_id int(18) DEFAULT 0;
\ No newline at end of file
diff --git a/service/src/main/resources/db/h2/V2_0_2__PostInitializeIdentityZones.sql b/service/src/main/resources/db/h2/V2_0_2__PostInitializeIdentityZones.sql
new file mode 100644
index 0000000..3cac453
--- /dev/null
+++ b/service/src/main/resources/db/h2/V2_0_2__PostInitializeIdentityZones.sql
@@ -0,0 +1,21 @@
+
+ALTER TABLE resource DROP CONSTRAINT unique_issuer_client_resource_identifier;
+ALTER TABLE resource DROP COLUMN issuer_id;
+ALTER TABLE resource DROP COLUMN client_id;
+
+ALTER TABLE subject DROP CONSTRAINT unique_issuer_client_subject_identifier;
+ALTER TABLE subject DROP COLUMN issuer_id;
+ALTER TABLE subject DROP COLUMN client_id;
+
+ALTER TABLE policy_set DROP CONSTRAINT unique_issuer_client_pset;
+ALTER TABLE policy_set DROP COLUMN issuer_id;
+ALTER TABLE policy_set DROP COLUMN client_id;
+
+ALTER TABLE resource ADD CONSTRAINT unique_zid_resource_identifier UNIQUE (authorization_zone_id, resource_identifier);
+ALTER TABLE subject ADD CONSTRAINT unique_zid_subject_identifier UNIQUE (authorization_zone_id, subject_identifier);
+ALTER TABLE policy_set ADD CONSTRAINT unique_zid_pset UNIQUE (authorization_zone_id, policy_set_id);
+
+ALTER TABLE resource ADD FOREIGN KEY (authorization_zone_id) REFERENCES authorization_zone(id) ON DELETE CASCADE;
+ALTER TABLE subject ADD FOREIGN KEY (authorization_zone_id) REFERENCES authorization_zone(id) ON DELETE CASCADE;
+ALTER TABLE policy_set ADD FOREIGN KEY (authorization_zone_id) REFERENCES authorization_zone(id) ON DELETE CASCADE;
+ALTER TABLE authorization_zone_client ADD FOREIGN KEY (authorization_zone_id) REFERENCES authorization_zone(id) ON DELETE CASCADE;
\ No newline at end of file
diff --git a/service/src/main/resources/db/h2/V2_0_3__add_zone_issuer_table.sql b/service/src/main/resources/db/h2/V2_0_3__add_zone_issuer_table.sql
new file mode 100644
index 0000000..7b5b806
--- /dev/null
+++ b/service/src/main/resources/db/h2/V2_0_3__add_zone_issuer_table.sql
@@ -0,0 +1,13 @@
+CREATE TABLE `zone_issuer` (
+  `issuer_id` int(18) NOT NULL,
+  `zone_id` int(18) NOT NULL,
+  PRIMARY KEY(`issuer_id`, `zone_id`),
+  FOREIGN KEY (zone_id) REFERENCES authorization_zone(id) ON DELETE CASCADE,
+  FOREIGN KEY (issuer_id) REFERENCES issuer(id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+
+ALTER TABLE authorization_zone_client ADD FOREIGN KEY (issuer_id) REFERENCES issuer(id) ON DELETE CASCADE;
+ALTER TABLE issuer ADD CONSTRAINT unique_issuer_check_token_url UNIQUE (issuer_check_token_url);
+
+ALTER TABLE issuer ALTER column issuer_check_token_url varchar(1024) NOT NULL;
diff --git a/service/src/main/resources/db/h2/V2_0_5__remove_zone_client_and_issuer_tables.sql b/service/src/main/resources/db/h2/V2_0_5__remove_zone_client_and_issuer_tables.sql
new file mode 100644
index 0000000..064670f
--- /dev/null
+++ b/service/src/main/resources/db/h2/V2_0_5__remove_zone_client_and_issuer_tables.sql
@@ -0,0 +1,3 @@
+DROP TABLE zone_issuer;
+DROP TABLE authorization_zone_client;
+DROP TABLE issuer;
diff --git a/service/src/main/resources/db/h2/V3_0_1__alter_attribute_connectors.sql b/service/src/main/resources/db/h2/V3_0_1__alter_attribute_connectors.sql
new file mode 100644
index 0000000..06af8c7
--- /dev/null
+++ b/service/src/main/resources/db/h2/V3_0_1__alter_attribute_connectors.sql
@@ -0,0 +1,2 @@
+ALTER TABLE authorization_zone ADD resource_attribute_connector_json MEDIUMTEXT NULL;
+ALTER TABLE authorization_zone ADD subject_attribute_connector_json MEDIUMTEXT NULL;
diff --git a/service/src/main/resources/db/mariadb/V1__service_instance.sql b/service/src/main/resources/db/mariadb/V1__service_instance.sql
new file mode 100644
index 0000000..1facce1
--- /dev/null
+++ b/service/src/main/resources/db/mariadb/V1__service_instance.sql
@@ -0,0 +1,46 @@
+CREATE TABLE `policy_set` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `client_id` varchar(128) NOT NULL,
+  `issuer_id` varchar(128) NOT NULL,
+  `policy_set_id` varchar(128) NOT NULL,
+  `policy_set_json` MEDIUMTEXT NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+CREATE TABLE `resource` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `issuer_id` int(18) NOT NULL,
+  `client_id` varchar(128) NOT NULL,
+  `resource_identifier` varchar(128) NOT NULL,
+  `resource_id` varchar(128) NOT NULL,
+  `attributes` MEDIUMTEXT NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+CREATE TABLE `subject` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `issuer_id` int(18) NOT NULL,
+  `client_id` varchar(128) NOT NULL,
+  `subject_identifier` varchar(128) NOT NULL,
+  `subject_id` varchar(128) NOT NULL,
+  `attributes` MEDIUMTEXT NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+CREATE TABLE `issuer` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `issuer_id` varchar(128) NOT NULL,
+  `issuer_check_token_url` varchar(128) NOT NULL,
+  PRIMARY KEY (`id`)  
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+ALTER TABLE policy_set ADD CONSTRAINT unique_issuer_client_pset UNIQUE (issuer_id, client_id, policy_set_id);
+
+ALTER TABLE issuer ADD CONSTRAINT unique_issuer_id UNIQUE (issuer_id);
+
+ALTER TABLE resource ADD CONSTRAINT unique_issuer_client_resource_identifier UNIQUE (issuer_id, client_id, resource_identifier);
+ALTER TABLE resource ADD CONSTRAINT unique_issuer_client_resource_id UNIQUE (issuer_id, client_id, resource_id);
+
+ALTER TABLE subject ADD CONSTRAINT unique_issuer_client_subject_id UNIQUE (issuer_id, client_id, subject_id);
+ALTER TABLE subject ADD CONSTRAINT unique_issuer_client_subject_identifier UNIQUE (issuer_id, client_id, subject_identifier);
+
diff --git a/service/src/main/resources/db/mariadb/V2_0_0__create_authz_zones.sql b/service/src/main/resources/db/mariadb/V2_0_0__create_authz_zones.sql
new file mode 100644
index 0000000..5d3612b
--- /dev/null
+++ b/service/src/main/resources/db/mariadb/V2_0_0__create_authz_zones.sql
@@ -0,0 +1,22 @@
+CREATE TABLE `authorization_zone` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) NOT NULL,
+  `description` varchar(1024) NOT NULL,
+  `subdomain` varchar(255) NOT NULL,
+  PRIMARY KEY(`id`),
+  UNIQUE KEY `name` (`name`),
+  UNIQUE KEY `subdomain` (`subdomain`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+CREATE TABLE `authorization_zone_client` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `issuer_id` int(18) NOT NULL,
+  `client_id` varchar(255) NOT NULL,
+  `authorization_zone_id` int(18) NOT NULL,
+  PRIMARY KEY(`id`),
+  UNIQUE KEY `client_in_zone` (`issuer_id`,`client_id`,`authorization_zone_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+ALTER TABLE subject ADD COLUMN authorization_zone_id int(18) DEFAULT 0;
+ALTER TABLE resource ADD COLUMN authorization_zone_id int(18) DEFAULT 0;
+ALTER TABLE policy_set ADD COLUMN authorization_zone_id int(18) DEFAULT 0;
\ No newline at end of file
diff --git a/service/src/main/resources/db/mariadb/V2_0_2__PostInitializeIdentityZones.sql b/service/src/main/resources/db/mariadb/V2_0_2__PostInitializeIdentityZones.sql
new file mode 100644
index 0000000..73a10a0
--- /dev/null
+++ b/service/src/main/resources/db/mariadb/V2_0_2__PostInitializeIdentityZones.sql
@@ -0,0 +1,18 @@
+
+ALTER TABLE resource DROP CONSTRAINT unique_issuer_client_resource_identifier;
+ALTER TABLE resource DROP COLUMN issuer_id;
+ALTER TABLE resource DROP COLUMN client_id;
+
+
+ALTER TABLE subject DROP CONSTRAINT unique_issuer_client_subject_identifier;
+ALTER TABLE subject DROP COLUMN issuer_id;
+ALTER TABLE subject DROP COLUMN client_id;
+
+ALTER TABLE policy_set DROP CONSTRAINT unique_issuer_client_pset;
+ALTER TABLE policy_set DROP COLUMN issuer_id;
+ALTER TABLE policy_set DROP COLUMN client_id;
+
+
+ALTER TABLE resource ADD CONSTRAINT unique_zid_resource_identifier UNIQUE (authorization_zone_id, resource_identifier);
+ALTER TABLE subject ADD CONSTRAINT unique_zid_subject_identifier UNIQUE (authorization_zone_id, subject_identifier);
+ALTER TABLE policy_set ADD CONSTRAINT unique_zid_pset UNIQUE (authorization_zone_id, policy_set_id);
\ No newline at end of file
diff --git a/service/src/main/resources/db/mariadb/V2__drop_subject_resource_id_indexes_columns.sql b/service/src/main/resources/db/mariadb/V2__drop_subject_resource_id_indexes_columns.sql
new file mode 100644
index 0000000..3c46262
--- /dev/null
+++ b/service/src/main/resources/db/mariadb/V2__drop_subject_resource_id_indexes_columns.sql
@@ -0,0 +1,4 @@
+ALTER TABLE resource DROP INDEX unique_issuer_client_resource_id;
+ALTER TABLE resource DROP COLUMN resource_id;
+ALTER TABLE subject DROP INDEX unique_issuer_client_subject_id;
+ALTER TABLE subject DROP COLUMN subject_id;
diff --git a/service/src/main/resources/db/postgres/V0__service_instance.sql b/service/src/main/resources/db/postgres/V0__service_instance.sql
new file mode 100644
index 0000000..feeb095
--- /dev/null
+++ b/service/src/main/resources/db/postgres/V0__service_instance.sql
@@ -0,0 +1,37 @@
+CREATE TABLE policy_set (
+  id bigserial NOT NULL primary key,
+  client_id varchar(128) NOT NULL,
+  issuer_id integer NOT NULL,
+  policy_set_id varchar(128) NOT NULL,
+  policy_set_json text NOT NULL
+);
+
+CREATE TABLE resource (
+  id bigserial NOT NULL primary key,
+  issuer_id integer NOT NULL,
+  client_id varchar(128) NOT NULL,
+  resource_identifier varchar(128) NOT NULL,
+  attributes text NOT NULL
+);
+
+CREATE TABLE subject (
+  id bigserial NOT NULL primary key,
+  issuer_id integer NOT NULL,
+  client_id varchar(128) NOT NULL,
+  subject_identifier varchar(128) NOT NULL,
+  attributes text NOT NULL
+);
+
+CREATE TABLE issuer (
+  id bigserial NOT NULL primary key,
+  issuer_id varchar(128) NOT NULL,
+  issuer_check_token_url varchar(128) NOT NULL
+);
+
+ALTER TABLE policy_set ADD CONSTRAINT unique_issuer_client_pset UNIQUE (issuer_id, client_id, policy_set_id);
+
+ALTER TABLE issuer ADD CONSTRAINT unique_issuer_id UNIQUE (issuer_id);
+
+ALTER TABLE resource ADD CONSTRAINT unique_issuer_client_resource_identifier UNIQUE (issuer_id, client_id, resource_identifier);
+
+ALTER TABLE subject ADD CONSTRAINT unique_issuer_client_subject_identifier UNIQUE (issuer_id, client_id, subject_identifier);
\ No newline at end of file
diff --git a/service/src/main/resources/db/postgres/V2_0_0__create_authz_zones.sql b/service/src/main/resources/db/postgres/V2_0_0__create_authz_zones.sql
new file mode 100644
index 0000000..5ab0bed
--- /dev/null
+++ b/service/src/main/resources/db/postgres/V2_0_0__create_authz_zones.sql
@@ -0,0 +1,21 @@
+CREATE TABLE authorization_zone (
+  id bigserial NOT NULL primary key,
+  name varchar(255) NOT NULL,
+  description varchar(1024) NOT NULL,
+  subdomain varchar(255) NOT NULL
+);
+
+CREATE TABLE authorization_zone_client (
+  id bigserial NOT NULL primary key,
+  issuer_id integer NOT NULL,
+  client_id varchar(255) NOT NULL,
+  authorization_zone_id integer NOT NULL
+);
+
+ALTER TABLE authorization_zone ADD CONSTRAINT name UNIQUE (name);
+ALTER TABLE authorization_zone ADD CONSTRAINT subdomain UNIQUE (subdomain);
+ALTER TABLE authorization_zone_client ADD CONSTRAINT client_in_zone UNIQUE (issuer_id,client_id,authorization_zone_id);
+
+ALTER TABLE subject ADD COLUMN authorization_zone_id integer DEFAULT 0;
+ALTER TABLE resource ADD COLUMN authorization_zone_id integer DEFAULT 0;
+ALTER TABLE policy_set ADD COLUMN authorization_zone_id integer DEFAULT 0;
\ No newline at end of file
diff --git a/service/src/main/resources/db/postgres/V2_0_3__PostInitializeIdentityZones.sql b/service/src/main/resources/db/postgres/V2_0_3__PostInitializeIdentityZones.sql
new file mode 100644
index 0000000..e048be6
--- /dev/null
+++ b/service/src/main/resources/db/postgres/V2_0_3__PostInitializeIdentityZones.sql
@@ -0,0 +1,8 @@
+ALTER TABLE resource ADD CONSTRAINT unique_zid_resource_identifier UNIQUE (authorization_zone_id, resource_identifier);
+ALTER TABLE subject ADD CONSTRAINT unique_zid_subject_identifier UNIQUE (authorization_zone_id, subject_identifier);
+ALTER TABLE policy_set ADD CONSTRAINT unique_zid_pset UNIQUE (authorization_zone_id, policy_set_id);
+
+ALTER TABLE resource ADD FOREIGN KEY (authorization_zone_id) REFERENCES authorization_zone(id);
+ALTER TABLE subject ADD FOREIGN KEY (authorization_zone_id) REFERENCES authorization_zone(id);
+ALTER TABLE policy_set ADD FOREIGN KEY (authorization_zone_id) REFERENCES authorization_zone(id);
+ALTER TABLE authorization_zone_client ADD FOREIGN KEY (authorization_zone_id) REFERENCES authorization_zone(id);
\ No newline at end of file
diff --git a/service/src/main/resources/db/postgres/V2_0_4__add_zone_issuer_table.sql b/service/src/main/resources/db/postgres/V2_0_4__add_zone_issuer_table.sql
new file mode 100644
index 0000000..9902bb9
--- /dev/null
+++ b/service/src/main/resources/db/postgres/V2_0_4__add_zone_issuer_table.sql
@@ -0,0 +1,12 @@
+CREATE TABLE zone_issuer (
+  issuer_id integer  NOT NULL,
+  zone_id integer NOT NULL,
+  PRIMARY KEY(issuer_id, zone_id),
+  FOREIGN KEY (zone_id) REFERENCES authorization_zone(id),
+  FOREIGN KEY (issuer_id) REFERENCES issuer(id)
+);
+
+ALTER TABLE authorization_zone_client ADD FOREIGN KEY (issuer_id) REFERENCES issuer(id);
+ALTER TABLE issuer ADD CONSTRAINT unique_issuer_check_token_url UNIQUE (issuer_check_token_url);
+
+ALTER TABLE issuer ALTER column issuer_check_token_url TYPE varchar(1024);
\ No newline at end of file
diff --git a/service/src/main/resources/db/postgres/V2_0_5__remove_zone_client_and_issuer_tables.sql b/service/src/main/resources/db/postgres/V2_0_5__remove_zone_client_and_issuer_tables.sql
new file mode 100644
index 0000000..064670f
--- /dev/null
+++ b/service/src/main/resources/db/postgres/V2_0_5__remove_zone_client_and_issuer_tables.sql
@@ -0,0 +1,3 @@
+DROP TABLE zone_issuer;
+DROP TABLE authorization_zone_client;
+DROP TABLE issuer;
diff --git a/service/src/main/resources/db/postgres/V3_0_0__add_attribute_connectors.sql b/service/src/main/resources/db/postgres/V3_0_0__add_attribute_connectors.sql
new file mode 100644
index 0000000..8284a88
--- /dev/null
+++ b/service/src/main/resources/db/postgres/V3_0_0__add_attribute_connectors.sql
@@ -0,0 +1,17 @@
+CREATE TABLE attribute_connector (
+  id bigserial NOT NULL primary key,
+  cached_interval_minutes integer DEFAULT 0
+);
+
+CREATE TABLE attribute_adapter_connection (
+  id bigserial NOT NULL primary key,
+  connector_id integer NOT NULL,
+  adapter_endpoint varchar(256) NOT NULL,
+  adapter_token_url varchar(256) NOT NULL,
+  adapter_client_id varchar(128) NOT NULL
+);
+
+ALTER TABLE authorization_zone ADD COLUMN resource_attribute_connector bigint NULL;
+ALTER TABLE authorization_zone ADD FOREIGN KEY (resource_attribute_connector) REFERENCES attribute_connector(id) ON DELETE CASCADE;
+ALTER TABLE authorization_zone ADD COLUMN subject_attribute_connector bigint NULL;
+ALTER TABLE authorization_zone ADD FOREIGN KEY (subject_attribute_connector) REFERENCES attribute_connector(id) ON DELETE CASCADE;
\ No newline at end of file
diff --git a/service/src/main/resources/db/postgres/V3_0_1__alter_attribute_connectors.sql b/service/src/main/resources/db/postgres/V3_0_1__alter_attribute_connectors.sql
new file mode 100644
index 0000000..0814df5
--- /dev/null
+++ b/service/src/main/resources/db/postgres/V3_0_1__alter_attribute_connectors.sql
@@ -0,0 +1,7 @@
+ALTER TABLE authorization_zone DROP COLUMN resource_attribute_connector;
+ALTER TABLE authorization_zone DROP COLUMN subject_attribute_connector;
+DROP TABLE attribute_connector;
+DROP TABLE attribute_adapter_connection;
+
+ALTER TABLE authorization_zone ADD resource_attribute_connector_json text NULL;
+ALTER TABLE authorization_zone ADD subject_attribute_connector_json text NULL;
diff --git a/service/src/main/resources/log4j-dev.xml b/service/src/main/resources/log4j-dev.xml
new file mode 100644
index 0000000..88bd148
--- /dev/null
+++ b/service/src/main/resources/log4j-dev.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration debug="false" xmlns:log4j='http://jakarta.apache.org/log4j/'>
+
+    <appender name="console" class="org.apache.log4j.ConsoleAppender">
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="[Z:%X{Zone-Id} T:%X{X-B3-TraceId} S:%X{X-B3-SpanId}] %d{YYYY-MM-dd HH:mm:ss} %p [%t] %c [%F:%L] %m%n"/>
+        </layout>
+    </appender>
+
+    <logger name="org.springframework.security">
+        <level value="info"/>
+    </logger>
+
+    <logger name="org.flywaydb.core.internal.dbsupport">
+        <level value="warn"/>
+    </logger>
+
+    <logger name="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
+        <level value="error"/>
+    </logger>
+
+    <logger name="org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping">
+        <level value="error"/>
+    </logger>
+
+    <logger name="org.springframework.boot.actuate.endpoint.mvc.FilterRegistrationBean">
+        <level value="error"/>
+    </logger>
+
+    <logger name="org.springframework.security.config.http.FilterInvocationSecurityMetadataSourceParser">
+        <level value="error"/>
+    </logger>
+
+    <logger name="springfox.documentation">
+        <level value="error"/>
+    </logger>
+
+    <logger name="com.ge.predix.acs">
+        <level value="info"/>
+    </logger>
+
+    <root>
+        <level value="INFO"/>
+        <appender-ref ref="console"/>
+    </root>
+
+</log4j:configuration>
diff --git a/service/src/main/resources/log4j.xml b/service/src/main/resources/log4j.xml
new file mode 100644
index 0000000..405b53c
--- /dev/null
+++ b/service/src/main/resources/log4j.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration debug="false" xmlns:log4j='http://jakarta.apache.org/log4j/'>
+
+    <appender name="console" class="org.apache.log4j.ConsoleAppender">
+        <layout class="com.ge.predix.log4j1.PredixLayout"/>
+    </appender>
+
+    <logger name="org.springframework.security">
+        <level value="info"/>
+    </logger>
+
+    <logger name="org.flywaydb.core.internal.dbsupport">
+        <level value="warn"/>
+    </logger>
+
+    <logger name="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
+        <level value="error"/>
+    </logger>
+
+    <logger name="org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping">
+        <level value="error"/>
+    </logger>
+
+    <logger name="org.springframework.boot.actuate.endpoint.mvc.FilterRegistrationBean">
+        <level value="error"/>
+    </logger>
+
+    <logger name="org.springframework.security.config.http.FilterInvocationSecurityMetadataSourceParser">
+        <level value="error"/>
+    </logger>
+
+    <logger name="springfox.documentation">
+        <level value="error"/>
+    </logger>
+
+    <logger name="com.ge.predix.acs">
+        <level value="info"/>
+    </logger>
+
+    <root>
+        <level value="INFO"/>
+        <appender-ref ref="console"/>
+    </root>
+
+</log4j:configuration>
diff --git a/service/src/main/resources/partial-sample-policy-set.json b/service/src/main/resources/partial-sample-policy-set.json
new file mode 100644
index 0000000..72b5875
--- /dev/null
+++ b/service/src/main/resources/partial-sample-policy-set.json
@@ -0,0 +1,98 @@
+{
+    "name" : "partial-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/sanramon"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), 'sanramon')" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/newyork"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), 'sanramon')" }
+            ],
+            "effect" : "DENY"
+        },
+		{
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "secured-by-expression/sites/sanramon"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), 'sanramon')" }
+            ],
+            "effect" : "PERMIT"
+        },
+		{
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "secured-by-expression/sites/newyork"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), 'sanramon')" }
+            ],
+            "effect" : "DENY"
+        }        
+                
+    ]
+}
\ No newline at end of file
diff --git a/service/src/main/resources/security-config.xml b/service/src/main/resources/security-config.xml
new file mode 100644
index 0000000..7cf1085
--- /dev/null
+++ b/service/src/main/resources/security-config.xml
@@ -0,0 +1,277 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:mvc="http://www.springframework.org/schema/mvc"
+       xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
+       xmlns:p="http://www.springframework.org/schema/p"
+       xmlns:redis="http://www.springframework.org/schema/redis"
+       xmlns:sec="http://www.springframework.org/schema/security"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
+        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
+        http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2.xsd">
+
+    <!-- Register Oauth AuthN Manager -->
+    <sec:authentication-manager />
+
+    <bean
+        class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
+        <property name="order" value="-1"></property>
+        <property name="urlPathHelper">
+            <bean class="com.ge.predix.acs.config.UrlPathHelperNonDecoding"></bean>
+        </property>
+    </bean>
+
+    <context:component-scan base-package="com.ge.predix.web.cors"/>
+
+    <!-- Correlation Log Filter -->
+    <bean id="logFilter" class="com.ge.predix.log.filter.LogFilter">
+       <constructor-arg>
+            <set value-type="java.lang.String">
+                <value>${ACS_BASE_DOMAIN:localhost}</value>
+            </set>
+        </constructor-arg>
+        <constructor-arg>
+            <set value-type="java.lang.String">
+                <value>Predix-Zone-Id</value>
+                <value>ACS-Zone-Subdomain</value>
+            </set>
+        </constructor-arg>
+        <constructor-arg value="acs" />
+    </bean>
+
+    <!-- Authentication Filter -->
+    <bean id="preAuthenticationEntryPoint"
+        class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint" />
+
+    <!-- Filter for Oauth Token Validation -->
+    <oauth:resource-server id="oauth2remoteTokenFilter"
+        token-services-ref="tokenService" />
+
+    <!-- Authorization Configuration For V1 policy-set APIS -->
+    <http pattern="/v1/policy-set/**" request-matcher="ant" create-session="stateless"
+        xmlns="http://www.springframework.org/schema/security"
+        disable-url-rewriting="true" use-expressions="true"
+        entry-point-ref="preAuthenticationEntryPoint">
+        <csrf disabled="true"/>
+
+        <intercept-url pattern="/v1/policy-set/**" method="GET"
+            access="hasAnyAuthority('acs.policies.read') " />
+        <intercept-url pattern="/v1/policy-set/**" method="HEAD"
+            access="hasAnyAuthority('acs.policies.read') " />
+        <intercept-url pattern="/v1/policy-set/**" method="OPTIONS"
+            access="hasAnyAuthority('acs.policies.read') " />
+
+        <intercept-url pattern="/v1/policy-set/**" method="PUT"
+            access="hasAnyAuthority('acs.policies.write') " />
+        <intercept-url pattern="/v1/policy-set/**" method="POST"
+            access="hasAnyAuthority('acs.policies.write') " />
+        <intercept-url pattern="/v1/policy-set/**" method="DELETE"
+            access="hasAnyAuthority('acs.policies.write') " />
+        <intercept-url pattern="/v1/policy-set/**" method="PATCH"
+            access="hasAnyAuthority('acs.policies.write') " />
+
+        <!-- This filter must always be first in the Spring Security filter chain for this URI pattern -->
+        <custom-filter ref="policyHttpMethodsFilter" before="FIRST"/>
+        <custom-filter ref="oauth2remoteTokenFilter"
+            position="PRE_AUTH_FILTER" />
+        <custom-filter ref="acsRequestEnrichingFilter"
+                after="BASIC_AUTH_FILTER" />
+    </http>
+
+    <!-- Authorization Configuration For V1 policy-evaluation APIS -->
+    <http pattern="/v1/policy-evaluation/**" request-matcher="ant" create-session="stateless"
+        xmlns="http://www.springframework.org/schema/security"
+        disable-url-rewriting="true" use-expressions="true"
+        entry-point-ref="preAuthenticationEntryPoint">
+        <csrf disabled="true"/>
+
+        <intercept-url pattern="/v1/policy-evaluation"
+            access="isFullyAuthenticated() " />
+
+        <!-- This filter must always be first in the Spring Security filter chain for this URI pattern -->
+        <custom-filter ref="evaluationHttpMethodsFilter" before="FIRST"/>
+        <custom-filter ref="oauth2remoteTokenFilter"
+            position="PRE_AUTH_FILTER" />
+        <custom-filter ref="acsRequestEnrichingFilter"
+            after="BASIC_AUTH_FILTER" />
+    </http>
+
+    <!-- Authorization Configuration For V1 resource APIS -->
+    <http pattern="/v1/resource/**" request-matcher="ant" create-session="stateless"
+        xmlns="http://www.springframework.org/schema/security"
+        disable-url-rewriting="true" use-expressions="true"
+        entry-point-ref="preAuthenticationEntryPoint">
+        <csrf disabled="true"/>
+
+        <intercept-url pattern="/v1/resource/**" method="GET"
+            access="hasAnyAuthority('acs.attributes.read') " />
+        <intercept-url pattern="/v1/resource/**" method="HEAD"
+            access="hasAnyAuthority('acs.attributes.read') " />
+        <intercept-url pattern="/v1/resource/**" method="OPTIONS"
+            access="hasAnyAuthority('acs.attributes.read') " />
+
+        <intercept-url pattern="/v1/resource/**" method="PUT"
+            access="hasAnyAuthority('acs.attributes.write') " />
+        <intercept-url pattern="/v1/resource/**" method="POST"
+            access="hasAnyAuthority('acs.attributes.write') " />
+        <intercept-url pattern="/v1/resource/**" method="DELETE"
+            access="hasAnyAuthority('acs.attributes.write') " />
+        <intercept-url pattern="/v1/resource/**" method="PATCH"
+            access="hasAnyAuthority('acs.attributes.write') "/>
+
+        <!-- This filter must always be first in the Spring Security filter chain for this URI pattern -->
+        <custom-filter ref="resourceHttpMethodsFilter" before="FIRST"/>
+        <custom-filter ref="oauth2remoteTokenFilter"
+            position="PRE_AUTH_FILTER" />
+        <custom-filter ref="acsRequestEnrichingFilter"
+                after="BASIC_AUTH_FILTER" />
+    </http>
+
+    <!-- Authorization Configuration For V1 subject APIS -->
+    <http pattern="/v1/subject/**" request-matcher="ant" create-session="stateless"
+        xmlns="http://www.springframework.org/schema/security"
+        disable-url-rewriting="true" use-expressions="true"
+        entry-point-ref="preAuthenticationEntryPoint">
+        <csrf disabled="true"/>
+
+        <intercept-url pattern="/v1/subject/**" method="GET"
+            access="hasAnyAuthority('acs.attributes.read') " />
+        <intercept-url pattern="/v1/subject/**" method="HEAD"
+            access="hasAnyAuthority('acs.attributes.read') " />
+        <intercept-url pattern="/v1/subject/**" method="OPTIONS"
+            access="hasAnyAuthority('acs.attributes.read') " />
+
+        <intercept-url pattern="/v1/subject/**" method="PUT"
+            access="hasAnyAuthority('acs.attributes.write') " />
+        <intercept-url pattern="/v1/subject/**" method="POST"
+            access="hasAnyAuthority('acs.attributes.write') " />
+        <intercept-url pattern="/v1/subject/**" method="DELETE"
+            access="hasAnyAuthority('acs.attributes.write') " />
+        <intercept-url pattern="/v1/subject/**" method="PATCH"
+            access="hasAnyAuthority('acs.attributes.write') "/>
+
+        <!-- This filter must always be first in the Spring Security filter chain for this URI pattern -->
+        <custom-filter ref="subjectHttpMethodsFilter" before="FIRST"/>
+        <custom-filter ref="oauth2remoteTokenFilter"
+            position="PRE_AUTH_FILTER" />
+        <custom-filter ref="acsRequestEnrichingFilter"
+                after="BASIC_AUTH_FILTER" />
+    </http>
+
+    <!-- Authorization Configuration For V1 connector APIS -->
+    <http pattern="/v1/connector/*" request-matcher="ant" create-session="stateless"
+        xmlns="http://www.springframework.org/schema/security"
+        disable-url-rewriting="true" use-expressions="true"
+        entry-point-ref="preAuthenticationEntryPoint">
+        <csrf disabled="true"/>
+
+        <intercept-url pattern="/v1/connector/*" method="GET"
+            access="hasAnyAuthority('acs.connectors.read') " />
+        <intercept-url pattern="/v1/connector/*" method="HEAD"
+            access="hasAnyAuthority('acs.connectors.read') " />
+        <intercept-url pattern="/v1/connector/*" method="OPTIONS"
+            access="hasAnyAuthority('acs.connectors.read') " />
+
+        <intercept-url pattern="/v1/connector/*" method="PUT"
+            access="hasAnyAuthority('acs.connectors.write') " />
+        <intercept-url pattern="/v1/connector/*" method="POST"
+            access="hasAnyAuthority('acs.connectors.write') " />
+        <intercept-url pattern="/v1/connector/*" method="DELETE"
+            access="hasAnyAuthority('acs.connectors.write') " />
+        <intercept-url pattern="/v1/connector/*" method="PATCH"
+            access="hasAnyAuthority('acs.connectors.write') "/>
+
+        <!-- This filter must always be first in the Spring Security filter chain for this URI pattern -->
+        <custom-filter ref="connectorHttpMethodsFilter" before="FIRST"/>
+        <custom-filter ref="oauth2remoteTokenFilter"
+            position="PRE_AUTH_FILTER" />
+        <custom-filter ref="acsRequestEnrichingFilter"
+                after="BASIC_AUTH_FILTER" />
+    </http>
+
+    <!-- Authorization Configuration For V1 APIS -->
+    <http pattern="/v1/zone/**" request-matcher="ant" create-session="stateless"
+        xmlns="http://www.springframework.org/schema/security"
+        disable-url-rewriting="true" use-expressions="true"
+        entry-point-ref="preAuthenticationEntryPoint">
+        <csrf disabled="true"/>
+
+        <!-- TokenService will only verify the token against defaultTrustedIssuerId
+            for requests with no zone. Additional scope acs.zones.admin needs to be asserted
+            here. -->
+        <intercept-url pattern="/v1/zone/**"
+            access="isFullyAuthenticated() and hasAnyAuthority('acs.zones.admin')" />
+
+        <!-- This filter must always be first in the Spring Security filter chain for this URI pattern -->
+        <custom-filter ref="zoneHttpMethodsFilter" before="FIRST"/>
+        <custom-filter ref="oauth2remoteTokenFilter"
+            position="PRE_AUTH_FILTER" />
+    </http>
+
+    <!-- Authorization Configuration For Monitoring APIs -->
+    <bean id="noAuthenticationEntryPoint" class="com.ge.predix.acs.security.NoAuthenticationEntryPoint" />
+    <http pattern="/monitoring/heartbeat*" request-matcher="ant" create-session="stateless"
+          xmlns="http://www.springframework.org/schema/security"
+          disable-url-rewriting="true" use-expressions="true"
+          entry-point-ref="noAuthenticationEntryPoint">
+        <csrf disabled="true"/>
+
+        <intercept-url pattern="/monitoring/heartbeat*" access="permitAll()"/>
+
+        <!-- This filter must always be first in the Spring Security filter chain for this URI pattern -->
+        <custom-filter ref="monitoringHttpMethodsFilter" before="FIRST"/>
+    </http>
+
+    <http pattern="/health*" request-matcher="ant" create-session="stateless"
+          xmlns="http://www.springframework.org/schema/security"
+          disable-url-rewriting="true" use-expressions="true"
+          entry-point-ref="preAuthenticationEntryPoint">
+        <csrf disabled="true"/>
+
+        <intercept-url pattern="/health*" access="isAnonymous() or hasAnyAuthority('acs.monitoring')"/>
+        <custom-filter ref="oauth2remoteTokenFilter" position="PRE_AUTH_FILTER"/>
+        <custom-filter ref="managementSecurityRoleFilter" after="BASIC_AUTH_FILTER"/>
+    </http>
+
+    <beans profile="httpValidation">
+        <bean id="policyHttpMethodsFilter" class="com.ge.predix.acs.service.policy.admin.PolicyHttpMethodsFilter"/>
+        <bean id="evaluationHttpMethodsFilter" class="com.ge.predix.acs.service.policy.evaluation.EvaluationHttpMethodsFilter"/>
+        <bean id="resourceHttpMethodsFilter" class="com.ge.predix.acs.privilege.management.ResourceHttpMethodsFilter"/>
+        <bean id="subjectHttpMethodsFilter" class="com.ge.predix.acs.privilege.management.SubjectHttpMethodsFilter"/>
+        <bean id="connectorHttpMethodsFilter" class="com.ge.predix.acs.attribute.connector.ConnectorHttpMethodsFilter"/>
+        <bean id="zoneHttpMethodsFilter" class="com.ge.predix.acs.zone.management.ZoneHttpMethodsFilter"/>
+        <bean id="monitoringHttpMethodsFilter" class="com.ge.predix.acs.monitoring.MonitoringHttpMethodsFilter"/>
+    </beans>
+
+    <beans profile="!httpValidation">
+        <bean id="policyHttpMethodsFilter" class="com.ge.predix.acs.security.EmptyHttpMethodsFilter"/>
+        <bean id="evaluationHttpMethodsFilter" class="com.ge.predix.acs.security.EmptyHttpMethodsFilter"/>
+        <bean id="resourceHttpMethodsFilter" class="com.ge.predix.acs.security.EmptyHttpMethodsFilter"/>
+        <bean id="subjectHttpMethodsFilter" class="com.ge.predix.acs.security.EmptyHttpMethodsFilter"/>
+        <bean id="connectorHttpMethodsFilter" class="com.ge.predix.acs.security.EmptyHttpMethodsFilter"/>
+        <bean id="zoneHttpMethodsFilter" class="com.ge.predix.acs.security.EmptyHttpMethodsFilter"/>
+        <bean id="monitoringHttpMethodsFilter" class="com.ge.predix.acs.security.EmptyHttpMethodsFilter"/>
+    </beans>
+</beans>
diff --git a/service/src/test/java/com/ge/predix/acs/attribute/cache/AttributeCacheFactoryTest.java b/service/src/test/java/com/ge/predix/acs/attribute/cache/AttributeCacheFactoryTest.java
new file mode 100644
index 0000000..5207738
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/attribute/cache/AttributeCacheFactoryTest.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.cache;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.springframework.core.env.Environment;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class AttributeCacheFactoryTest {
+    @Mock
+    private Environment mockEnvironment;
+
+    @InjectMocks
+    private AttributeCacheFactory cacheFactory;
+
+    @BeforeClass
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testAttributeCacheDisabled() {
+        setupEnvironment(false, null);
+        assertThat(cacheFactory.createResourceAttributeCache(180, "myzone", null),
+                instanceOf(NonCachingAttributeCache.class));
+        assertThat(cacheFactory.createSubjectAttributeCache(180, "myzone", null),
+                instanceOf(NonCachingAttributeCache.class));
+    }
+
+    @Test
+    public void testAttributeCacheRedis() {
+        setupEnvironment(true, "redis");
+        assertThat(cacheFactory.createResourceAttributeCache(180, "myzone", null),
+                instanceOf(RedisAttributeCache.class));
+        assertThat(cacheFactory.createSubjectAttributeCache(180, "myzone", null),
+                instanceOf(RedisAttributeCache.class));
+    }
+
+    @Test
+    public void testAttributeCacheCloudRedis() {
+        setupEnvironment(true, "cloud-redis");
+        assertThat(cacheFactory.createResourceAttributeCache(180, "myzone", null),
+                instanceOf(RedisAttributeCache.class));
+        assertThat(cacheFactory.createSubjectAttributeCache(180, "myzone", null),
+                instanceOf(RedisAttributeCache.class));
+    }
+
+    @Test
+    public void testAttributeCacheInMemoty() {
+        setupEnvironment(true, null);
+        assertThat(cacheFactory.createResourceAttributeCache(180, "myzone", null),
+                instanceOf(InMemoryAttributeCache.class));
+        assertThat(cacheFactory.createSubjectAttributeCache(180, "myzone", null),
+                instanceOf(InMemoryAttributeCache.class));
+    }
+
+    private void setupEnvironment(final boolean cachingEnabled, final String springProfileActive) {
+        ReflectionTestUtils.setField(cacheFactory, "resourceCachingEnabled", cachingEnabled);
+        ReflectionTestUtils.setField(cacheFactory, "subjectCachingEnabled", cachingEnabled);
+        String[] redisEnvironment = {springProfileActive};
+        Mockito.doReturn(redisEnvironment).when(mockEnvironment).getActiveProfiles();
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/attribute/cache/InMemoryAttributeCacheTest.java b/service/src/test/java/com/ge/predix/acs/attribute/cache/InMemoryAttributeCacheTest.java
new file mode 100644
index 0000000..d6e74ae
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/attribute/cache/InMemoryAttributeCacheTest.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.cache;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.mockito.internal.util.reflection.Whitebox;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.attribute.readers.CachedAttributes;
+import com.ge.predix.acs.model.Attribute;
+
+public class InMemoryAttributeCacheTest {
+
+    private static final String TEST_ZONE = "test-zone";
+
+    private static final String TEST_KEY = "test-key";
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSetCachedAttributes() {
+        InMemoryAttributeCache inMemoryAttributeCache = new InMemoryAttributeCache(5, TEST_ZONE,
+                AbstractAttributeCache::resourceKey);
+        Set<Attribute> attributes = Collections.singleton(new Attribute("https://test.com", "attribute1", "value1"));
+        CachedAttributes cachedAttributes = new CachedAttributes(attributes);
+        inMemoryAttributeCache.set(TEST_KEY, cachedAttributes);
+
+        Map<String, CachedAttributes> resourceCache = (Map<String, CachedAttributes>) Whitebox
+                .getInternalState(inMemoryAttributeCache, "attributeCache");
+        String cacheKey = AbstractAttributeCache.resourceKey(TEST_ZONE, TEST_KEY);
+        Assert.assertEquals(resourceCache.get(cacheKey), cachedAttributes);
+    }
+
+    @Test
+    public void testGetCachedAttributes() {
+        InMemoryAttributeCache inMemoryAttributeCache = new InMemoryAttributeCache(5, TEST_ZONE,
+                AbstractAttributeCache::resourceKey);
+        Set<Attribute> attributes = Collections.singleton(new Attribute("https://test.com", "attribute1", "value1"));
+        CachedAttributes cachedAttributes = new CachedAttributes(attributes);
+
+        Map<String, CachedAttributes> cachedAttributesMap = new HashMap<>();
+        String cacheKey = AbstractAttributeCache.resourceKey(TEST_ZONE, TEST_KEY);
+        cachedAttributesMap.put(cacheKey, cachedAttributes);
+        ReflectionTestUtils.setField(inMemoryAttributeCache, "attributeCache", cachedAttributesMap);
+
+        Assert.assertEquals(inMemoryAttributeCache.get(TEST_KEY), cachedAttributes);
+
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testFlushAll() {
+        InMemoryAttributeCache inMemoryAttributeCache = new InMemoryAttributeCache(5, TEST_ZONE,
+                AbstractAttributeCache::subjectKey);
+        Set<Attribute> attributes = Collections.singleton(new Attribute("https://test.com", "attribute1", "value1"));
+        CachedAttributes cachedAttributes = new CachedAttributes(attributes);
+
+        Map<String, CachedAttributes> cachedAttributesMap = new HashMap<>();
+        String cacheKey = AbstractAttributeCache.subjectKey(TEST_ZONE, TEST_KEY);
+        cachedAttributesMap.put(cacheKey, cachedAttributes);
+        ReflectionTestUtils.setField(inMemoryAttributeCache, "attributeCache", cachedAttributesMap);
+        Assert.assertEquals(inMemoryAttributeCache.get(TEST_KEY), cachedAttributes);
+        inMemoryAttributeCache.flushAll();
+        Assert.assertTrue(
+                ((Map<String, CachedAttributes>) ReflectionTestUtils.getField(inMemoryAttributeCache, "attributeCache"))
+                        .isEmpty());
+
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/attribute/connector/ConnectorHttpMethodsFilterTest.java b/service/src/test/java/com/ge/predix/acs/attribute/connector/ConnectorHttpMethodsFilterTest.java
new file mode 100644
index 0000000..c84945e
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/attribute/connector/ConnectorHttpMethodsFilterTest.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.connector;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mockito.InjectMocks;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpMethod;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.attribute.connector.management.AttributeConnectorController;
+
+public final class ConnectorHttpMethodsFilterTest {
+
+    @InjectMocks
+    private AttributeConnectorController attributeConnectorController;
+
+    private static final Set<HttpMethod> ALL_HTTP_METHODS = new HashSet<>(Arrays.asList(HttpMethod.values()));
+
+    private MockMvc mockMvc;
+
+    @BeforeClass
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        this.mockMvc = MockMvcBuilders.standaloneSetup(this.attributeConnectorController)
+                .addFilters(new ConnectorHttpMethodsFilter()).build();
+    }
+
+    @Test(dataProvider = "urisAndTheirAllowedHttpMethods")
+    public void testUriPatternsAndTheirAllowedHttpMethods(final String uri, final Set<HttpMethod> allowedHttpMethods)
+            throws Exception {
+        Set<HttpMethod> disallowedHttpMethods = new HashSet<>(ALL_HTTP_METHODS);
+        disallowedHttpMethods.removeAll(allowedHttpMethods);
+        for (HttpMethod disallowedHttpMethod : disallowedHttpMethods) {
+            this.mockMvc.perform(MockMvcRequestBuilders.request(disallowedHttpMethod, URI.create(uri)))
+                    .andExpect(MockMvcResultMatchers.status().isMethodNotAllowed());
+        }
+    }
+
+    @DataProvider
+    public Object[][] urisAndTheirAllowedHttpMethods() {
+        return new Object[][] {
+                new Object[] { "/v1/connector/resource",
+                        new HashSet<>(Arrays.asList(HttpMethod.PUT, HttpMethod.GET, HttpMethod.DELETE, HttpMethod.HEAD,
+                                HttpMethod.OPTIONS)) },
+                { "/v1/connector/subject", new HashSet<>(Arrays.asList(HttpMethod.PUT, HttpMethod.GET,
+                        HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.OPTIONS)) } };
+    }
+}
\ No newline at end of file
diff --git a/service/src/test/java/com/ge/predix/acs/attribute/connector/management/AttributeConnectorServiceTest.java b/service/src/test/java/com/ge/predix/acs/attribute/connector/management/AttributeConnectorServiceTest.java
new file mode 100644
index 0000000..6ebf121
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/attribute/connector/management/AttributeConnectorServiceTest.java
@@ -0,0 +1,383 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.connector.management;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.attribute.readers.AttributeReaderFactory;
+import com.ge.predix.acs.rest.AttributeAdapterConnection;
+import com.ge.predix.acs.rest.AttributeConnector;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.ge.predix.acs.zone.management.dao.ZoneRepository;
+import com.ge.predix.acs.zone.resolver.ZoneResolver;
+
+@Test
+public class AttributeConnectorServiceTest {
+    @InjectMocks
+    private AttributeConnectorServiceImpl connectorService;
+    @Mock
+    private ZoneResolver zoneResolver;
+    @Mock
+    private ZoneRepository zoneRepository;
+    @Mock
+    private AttributeReaderFactory attributeReaderFactory;
+
+    @BeforeMethod
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        this.connectorService.setEncryptionKey("1234567890123456");
+    }
+
+    @Test(dataProvider = "validConnectorProvider")
+    public void testCreateResourceConnector(final AttributeConnector expectedConnector) {
+        ZoneEntity zoneEntity = new ZoneEntity();
+        Mockito.doReturn(zoneEntity).when(this.zoneResolver).getZoneEntityOrFail();
+        Assert.assertTrue(this.connectorService.upsertResourceConnector(expectedConnector));
+        Assert.assertEquals(this.connectorService.retrieveResourceConnector(), expectedConnector);
+    }
+
+    @Test(dataProvider = "validConnectorProvider")
+    public void testUpdateResourceConnector(final AttributeConnector expectedConnector) throws Exception {
+        ZoneEntity zoneEntity = new ZoneEntity();
+        zoneEntity.setResourceAttributeConnector(new AttributeConnector());
+        Mockito.doReturn(zoneEntity).when(this.zoneResolver).getZoneEntityOrFail();
+        Assert.assertFalse(this.connectorService.upsertResourceConnector(expectedConnector));
+        Assert.assertEquals(this.connectorService.retrieveResourceConnector(), expectedConnector);
+    }
+
+    @Test(dataProvider = "validConnectorProvider", expectedExceptions = { AttributeConnectorException.class })
+    public void testUpsertResourceConnectorWhenSaveFails(final AttributeConnector connector) {
+        ZoneEntity zoneEntity = new ZoneEntity();
+        Mockito.doReturn(zoneEntity).when(this.zoneResolver).getZoneEntityOrFail();
+        Mockito.doThrow(Exception.class).when(this.zoneRepository).save(Mockito.any(ZoneEntity.class));
+        this.connectorService.upsertResourceConnector(connector);
+    }
+
+    @Test(dataProvider = "badConnectorProvider", expectedExceptions = { AttributeConnectorException.class })
+    public void testUpsertResourceConnectorWhenValidationFails(final AttributeConnector connector) {
+        Mockito.doReturn(new ZoneEntity()).when(this.zoneResolver).getZoneEntityOrFail();
+        this.connectorService.upsertResourceConnector(connector);
+    }
+
+    @Test(dataProvider = "validConnectorProvider")
+    public void testGetResourceConnector(final AttributeConnector expectedConnector) throws Exception {
+        ZoneEntity zoneEntity = new ZoneEntity();
+        zoneEntity.setResourceAttributeConnector(expectedConnector);
+        Mockito.doReturn(zoneEntity).when(this.zoneResolver).getZoneEntityOrFail();
+        this.connectorService.upsertResourceConnector(expectedConnector);
+        Assert.assertEquals(this.connectorService.retrieveResourceConnector(), expectedConnector);
+    }
+
+    @Test
+    public void testConnectorsWhichDoNotExist() {
+        Mockito.doReturn(new ZoneEntity()).when(this.zoneResolver).getZoneEntityOrFail();
+        Assert.assertNull(this.connectorService.retrieveResourceConnector());
+        Assert.assertNull(this.connectorService.retrieveSubjectConnector());
+        Assert.assertFalse(this.connectorService.deleteResourceConnector());
+        Assert.assertFalse(this.connectorService.deleteSubjectConnector());
+    }
+
+    @Test(dataProvider = "validConnectorProvider")
+    public void testDeleteResourceConnector(final AttributeConnector connector) throws Exception {
+        ZoneEntity zoneEntity = new ZoneEntity();
+        zoneEntity.setResourceAttributeConnector(connector);
+        Mockito.doReturn(zoneEntity).when(this.zoneResolver).getZoneEntityOrFail();
+        Assert.assertTrue(this.connectorService.deleteResourceConnector());
+        Assert.assertNull(this.connectorService.retrieveResourceConnector());
+    }
+
+    @Test(expectedExceptions = { AttributeConnectorException.class })
+    public void testDeleteResourceConnectorWhenSaveFails() throws Exception {
+        ZoneEntity zoneEntity = new ZoneEntity();
+        zoneEntity.setResourceAttributeConnector(new AttributeConnector());
+        Mockito.doReturn(zoneEntity).when(this.zoneResolver).getZoneEntityOrFail();
+        Mockito.doThrow(Exception.class).when(this.zoneRepository).save(Mockito.any(ZoneEntity.class));
+        this.connectorService.deleteResourceConnector();
+    }
+
+    @Test(dataProvider = "validConnectorProvider")
+    public void testCreateSubjectConnector(final AttributeConnector expectedConnector) {
+        ZoneEntity zoneEntity = new ZoneEntity();
+        Mockito.doReturn(zoneEntity).when(this.zoneResolver).getZoneEntityOrFail();
+        Assert.assertTrue(this.connectorService.upsertSubjectConnector(expectedConnector));
+        Assert.assertEquals(this.connectorService.retrieveSubjectConnector(), expectedConnector);
+    }
+
+    @Test(dataProvider = "validConnectorProvider")
+    public void testUpdateSubjectConnector(final AttributeConnector expectedConnector) throws Exception {
+        ZoneEntity zoneEntity = new ZoneEntity();
+        zoneEntity.setSubjectAttributeConnector(new AttributeConnector());
+        Mockito.doReturn(zoneEntity).when(this.zoneResolver).getZoneEntityOrFail();
+        Assert.assertFalse(this.connectorService.upsertSubjectConnector(expectedConnector));
+        Assert.assertEquals(this.connectorService.retrieveSubjectConnector(), expectedConnector);
+    }
+
+    @Test(dataProvider = "validConnectorProvider", expectedExceptions = { AttributeConnectorException.class })
+    public void testUpsertSubjectConnectorWhenSaveFails(final AttributeConnector connector) {
+        ZoneEntity zoneEntity = new ZoneEntity();
+        Mockito.doReturn(zoneEntity).when(this.zoneResolver).getZoneEntityOrFail();
+        Mockito.doThrow(Exception.class).when(this.zoneRepository).save(Mockito.any(ZoneEntity.class));
+        this.connectorService.upsertSubjectConnector(connector);
+    }
+
+    @Test(dataProvider = "badConnectorProvider", expectedExceptions = { AttributeConnectorException.class })
+    public void testUpsertSubjectConnectorWhenValidationFails(final AttributeConnector connector) {
+        Mockito.doReturn(new ZoneEntity()).when(this.zoneResolver).getZoneEntityOrFail();
+        this.connectorService.upsertSubjectConnector(connector);
+    }
+
+    @Test(dataProvider = "validConnectorProvider")
+    public void testGetSubjectConnector(final AttributeConnector expectedConnector) throws Exception {
+        ZoneEntity zoneEntity = new ZoneEntity();
+        zoneEntity.setSubjectAttributeConnector(expectedConnector);
+        Mockito.doReturn(zoneEntity).when(this.zoneResolver).getZoneEntityOrFail();
+        this.connectorService.upsertSubjectConnector(expectedConnector);
+        Assert.assertEquals(this.connectorService.retrieveSubjectConnector(), expectedConnector);
+    }
+
+    @Test(dataProvider = "validConnectorProvider")
+    public void testDeleteSubjectConnector(final AttributeConnector connector) throws Exception {
+        ZoneEntity zoneEntity = new ZoneEntity();
+        zoneEntity.setSubjectAttributeConnector(connector);
+        Mockito.doReturn(zoneEntity).when(this.zoneResolver).getZoneEntityOrFail();
+        Assert.assertTrue(this.connectorService.deleteSubjectConnector());
+        Assert.assertNull(this.connectorService.retrieveSubjectConnector());
+    }
+
+    @Test(expectedExceptions = { AttributeConnectorException.class })
+    public void testDeleteSubjectConnectorWhenSaveFails() throws Exception {
+        ZoneEntity zoneEntity = new ZoneEntity();
+        zoneEntity.setSubjectAttributeConnector(new AttributeConnector());
+        Mockito.doReturn(zoneEntity).when(this.zoneResolver).getZoneEntityOrFail();
+        Mockito.doThrow(Exception.class).when(this.zoneRepository).save(Mockito.any(ZoneEntity.class));
+        this.connectorService.deleteSubjectConnector();
+    }
+
+    @DataProvider
+    private Object[][] validConnectorProvider() {
+        return new Object[][] { getValidConnector(), getConnectorWithAdapterEndpointHavingAMixedCaseScheme(),
+                getConnectorWithAdapterUaaTokenUrlHavingAMixedCaseScheme() };
+    }
+
+    private Object[] getValidConnector() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(100);
+        connector.setAdapters(getValidAdapter());
+        return new Object[] { connector };
+    }
+
+    private Set<AttributeAdapterConnection> getValidAdapter() {
+        return Collections.singleton(new AttributeAdapterConnection("https://my-endpoint.com", "https://my-uaa.com",
+                "my-client", "my-secret"));
+    }
+
+    @DataProvider
+    private Object[][] badConnectorProvider() {
+        return new Object[][] { getNullConnector(), getConnectorWithoutAdapter(),
+                getConnectorWithCachedIntervalBelowThreshold(), getConnectorWithTwoAdapters(),
+                getConnectorWithEmptyAdapterEndpointUrl(), getConnectorWithEmptyAdapterUaaTokenUrl(),
+                getConnectorWithNullAdapterUaaTokenUrl(), getConnectorWithNullAdapterEndpointUrl(),
+                getConnectorWithoutAdapterClientId(), getConnectorWithoutAdapterClientSecret(),
+                getConnectorWithAdapterEndpointInsecure(), getConnectorWithAdapterUaaTokenUrlInsecure(),
+                getConnectorWithAdapterEndpointHavingNoScheme(), getConnectorWithAdapterUaaTokenUrlHavingNoScheme() };
+    }
+
+    private Set<AttributeAdapterConnection> getAdapterWithOnlyEndpointInsecure() {
+        return Collections.singleton(new AttributeAdapterConnection("http://my-endpoint.com", "https://my-uaa.com",
+                "my-client", "my-secret"));
+    }
+
+    private Set<AttributeAdapterConnection> getAdapterWithOnlyUaaTokenUrlInsecure() {
+        return Collections.singleton(new AttributeAdapterConnection("https://my-endpoint.com", "http://my-uaa.com",
+                "my-client", "my-secret"));
+    }
+
+    private Set<AttributeAdapterConnection> getAdapterWithOnlyEndpointHavingAMixedCaseScheme() {
+        return Collections.singleton(new AttributeAdapterConnection("hTtPs://my-endpoint.com", "https://my-uaa.com",
+                "my-client", "my-secret"));
+    }
+
+    private Set<AttributeAdapterConnection> getAdapterWithOnlyUaaTokenUrlHavingAMixedCaseScheme() {
+        return Collections.singleton(new AttributeAdapterConnection("https://my-endpoint.com", "hTtPs://my-uaa.com",
+                "my-client", "my-secret"));
+    }
+
+    private Set<AttributeAdapterConnection> getAdapterWithOnlyEndpointHavingNoScheme() {
+        return Collections.singleton(new AttributeAdapterConnection("www.my-endpoint.com", "https://my-uaa.com",
+                "my-client", "my-secret"));
+    }
+
+    private Set<AttributeAdapterConnection> getAdapterWithOnlyUaaTokenUrlHavingNoScheme() {
+        return Collections.singleton(new AttributeAdapterConnection("https://my-endpoint.com", "www.my-uaa.com",
+                "my-client", "my-secret"));
+    }
+
+    private Set<AttributeAdapterConnection> getAdapterWithoutClientSecret() {
+        return Collections.singleton(
+                new AttributeAdapterConnection("https://my-endpoint.com", "https://my-uaa.com", "my-client", ""));
+    }
+
+    private Set<AttributeAdapterConnection> getAdapterWithoutClientId() {
+        return Collections.singleton(
+                new AttributeAdapterConnection("https://my-endpoint.com", "https://my-uaa.com", "", "my-secret"));
+    }
+
+    private Set<AttributeAdapterConnection> getAdapterWithEmptyUaaTokenUrl() {
+        return Collections
+                .singleton(new AttributeAdapterConnection("https://my-endpoint.com", "", "my-client", "my-secret"));
+    }
+
+    private Set<AttributeAdapterConnection> getAdapterWithEmptyEndpointUrl() {
+        return Collections
+                .singleton(new AttributeAdapterConnection("", "https://my-uaa.com", "my-client", "my-secret"));
+    }
+
+    private Set<AttributeAdapterConnection> getAdapterWithNullUaaTokenUrl() {
+        return Collections
+                .singleton(new AttributeAdapterConnection("https://my-endpoint.com", null, "my-client", "my-secret"));
+    }
+
+    private Set<AttributeAdapterConnection> getAdapterWithNullEndpointUrl() {
+        return Collections
+                .singleton(new AttributeAdapterConnection(null, "https://my-uaa.com", "my-client", "my-secret"));
+    }
+
+    private Object[] getConnectorWithAdapterEndpointInsecure() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(100);
+        connector.setAdapters(getAdapterWithOnlyEndpointInsecure());
+        return new Object[] { connector };
+    }
+
+    private Object[] getConnectorWithAdapterUaaTokenUrlInsecure() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(100);
+        connector.setAdapters(getAdapterWithOnlyUaaTokenUrlInsecure());
+        return new Object[] { connector };
+    }
+
+    private Object[] getConnectorWithAdapterEndpointHavingAMixedCaseScheme() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(100);
+        connector.setAdapters(getAdapterWithOnlyEndpointHavingAMixedCaseScheme());
+        return new Object[] { connector };
+    }
+
+    private Object[] getConnectorWithAdapterUaaTokenUrlHavingAMixedCaseScheme() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(100);
+        connector.setAdapters(getAdapterWithOnlyUaaTokenUrlHavingAMixedCaseScheme());
+        return new Object[] { connector };
+    }
+
+    private Object[] getConnectorWithAdapterEndpointHavingNoScheme() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(100);
+        connector.setAdapters(getAdapterWithOnlyEndpointHavingNoScheme());
+        return new Object[] { connector };
+    }
+
+    private Object[] getConnectorWithAdapterUaaTokenUrlHavingNoScheme() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(100);
+        connector.setAdapters(getAdapterWithOnlyUaaTokenUrlHavingNoScheme());
+        return new Object[] { connector };
+    }
+
+    private Object[] getConnectorWithoutAdapterClientSecret() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(100);
+        connector.setAdapters(getAdapterWithoutClientSecret());
+        return new Object[] { connector };
+    }
+
+    private Object[] getConnectorWithoutAdapterClientId() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(100);
+        connector.setAdapters(getAdapterWithoutClientId());
+        return new Object[] { connector };
+    }
+
+    private Object[] getConnectorWithEmptyAdapterUaaTokenUrl() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(100);
+        connector.setAdapters(getAdapterWithEmptyUaaTokenUrl());
+        return new Object[] { connector };
+    }
+
+    private Object[] getConnectorWithEmptyAdapterEndpointUrl() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(100);
+        connector.setAdapters(getAdapterWithEmptyEndpointUrl());
+        return new Object[] { connector };
+    }
+
+    private Object[] getConnectorWithNullAdapterUaaTokenUrl() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(100);
+        connector.setAdapters(getAdapterWithNullUaaTokenUrl());
+        return new Object[] { connector };
+    }
+
+    private Object[] getConnectorWithNullAdapterEndpointUrl() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(100);
+        connector.setAdapters(getAdapterWithNullEndpointUrl());
+        return new Object[] { connector };
+    }
+
+    private Object[] getConnectorWithTwoAdapters() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(100);
+        Set<AttributeAdapterConnection> adapters = new HashSet<>();
+        adapters.add(new AttributeAdapterConnection("https://my-endpoint", "https://my-uaa", "my-client", "my-secret"));
+        adapters.add(
+                new AttributeAdapterConnection("https://my-endpoint2", "https://my-uaa2", "my-client2", "my-secret2"));
+        connector.setAdapters(adapters);
+        return new Object[] { connector };
+    }
+
+    private Object[] getConnectorWithCachedIntervalBelowThreshold() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(10);
+        connector.setAdapters(getValidAdapter());
+        return new Object[] { connector };
+    }
+
+    private Object[] getConnectorWithoutAdapter() {
+        AttributeConnector connector = new AttributeConnector();
+        connector.setMaxCachedIntervalMinutes(100);
+        return new Object[] { connector };
+    }
+
+    private Object[] getNullConnector() {
+        return new Object[] { null };
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/attribute/readers/DefaultResourceAttributeReaderTest.java b/service/src/test/java/com/ge/predix/acs/attribute/readers/DefaultResourceAttributeReaderTest.java
new file mode 100644
index 0000000..3845039
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/attribute/readers/DefaultResourceAttributeReaderTest.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.when;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.privilege.management.PrivilegeManagementService;
+import com.ge.predix.acs.rest.BaseResource;
+
+@Test
+public class DefaultResourceAttributeReaderTest {
+    @Mock
+    private PrivilegeManagementService privilegeManagementService;
+
+    @Autowired
+    @InjectMocks
+    private PrivilegeServiceResourceAttributeReader defaultResourceAttributeReader;
+
+    @BeforeMethod
+    public void beforeMethod() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testGetAttributes() throws Exception {
+        Set<Attribute> resourceAttributes = new HashSet<>();
+        resourceAttributes.add(new Attribute("https://acs.attributes.int", "site", "sanramon"));
+        BaseResource testResource = new BaseResource("/test/resource", resourceAttributes);
+
+        when(this.privilegeManagementService.getByResourceIdentifierWithInheritedAttributes(any()))
+                .thenReturn(testResource);
+        Assert.assertTrue(this.defaultResourceAttributeReader.getAttributes(testResource.getResourceIdentifier())
+                .containsAll(resourceAttributes));
+    }
+
+    @Test
+    public void testGetAttributesForNonExistentResource() throws Exception {
+        when(this.privilegeManagementService.getByResourceIdentifierWithInheritedAttributes(any())).thenReturn(null);
+        Assert.assertTrue(this.defaultResourceAttributeReader.getAttributes("nonexistentResource").isEmpty());
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/attribute/readers/DefaultSubjectAttributeReaderTest.java b/service/src/test/java/com/ge/predix/acs/attribute/readers/DefaultSubjectAttributeReaderTest.java
new file mode 100644
index 0000000..bd63966
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/attribute/readers/DefaultSubjectAttributeReaderTest.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.privilege.management.PrivilegeManagementService;
+import com.ge.predix.acs.rest.BaseSubject;
+
+@Test
+public class DefaultSubjectAttributeReaderTest {
+    @Mock
+    private PrivilegeManagementService privilegeManagementService;
+
+    @Autowired
+    @InjectMocks
+    private PrivilegeServiceSubjectAttributeReader defaultSubjectAttributeReader;
+
+    @BeforeMethod
+    public void beforeMethod() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testGetAttributes() throws Exception {
+        Set<Attribute> subjectAttributes = new HashSet<>();
+        subjectAttributes.add(new Attribute("https://acs.attributes.int", "role", "administrator"));
+        BaseSubject testSubject = new BaseSubject("/test/subject", subjectAttributes);
+
+        when(this.privilegeManagementService.getBySubjectIdentifierAndScopes(any(), eq(Collections.emptySet())))
+                .thenReturn(testSubject);
+        Assert.assertTrue(this.defaultSubjectAttributeReader.getAttributes(testSubject.getSubjectIdentifier())
+                .containsAll(subjectAttributes));
+    }
+
+    @Test
+    public void testGetAttributesForNonExistentSubject() throws Exception {
+        when(this.privilegeManagementService.getBySubjectIdentifierAndScopes(any(), eq(Collections.emptySet())))
+                .thenReturn(null);
+        Assert.assertTrue(this.defaultSubjectAttributeReader.getAttributes("nonexistentSubject").isEmpty());
+    }
+
+    @Test
+    public void testGetAttributesByScope() throws Exception {
+        Set<Attribute> subjectAttributes = new HashSet<>();
+        subjectAttributes.add(new Attribute("https://acs.attributes.int", "role", "administrator"));
+        BaseSubject testSubject = new BaseSubject("/test/subject", subjectAttributes);
+
+        when(this.privilegeManagementService.getBySubjectIdentifierAndScopes(any(), any())).thenReturn(testSubject);
+        Assert.assertTrue(this.defaultSubjectAttributeReader
+                .getAttributesByScope(testSubject.getSubjectIdentifier(), Collections.emptySet())
+                .containsAll(subjectAttributes));
+    }
+
+    @Test
+    public void testGetAttributesByScopeForNonExistentSubject() throws Exception {
+        when(this.privilegeManagementService.getBySubjectIdentifierAndScopes(any(), any())).thenReturn(null);
+        Assert.assertTrue(this.defaultSubjectAttributeReader
+                .getAttributesByScope("nonexistentSubject", Collections.emptySet()).isEmpty());
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/attribute/readers/ExternalAttributeReaderHelper.java b/service/src/test/java/com/ge/predix/acs/attribute/readers/ExternalAttributeReaderHelper.java
new file mode 100644
index 0000000..f1fe0c5
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/attribute/readers/ExternalAttributeReaderHelper.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+import java.util.Collections;
+
+import org.mockito.Mockito;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.testng.annotations.DataProvider;
+
+import com.ge.predix.acs.attribute.cache.AttributeCache;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.AttributeAdapterConnection;
+import com.ge.predix.acs.rest.attribute.adapter.AttributesResponse;
+
+public final class ExternalAttributeReaderHelper {
+
+    private ExternalAttributeReaderHelper() {
+        // Hiding constructor because this a test utility class
+    }
+
+    static void setupMockedAdapterResponse(final ExternalAttributeReader externalAttributeReader,
+            final AttributeCache attributeCache, final String identifier) {
+        ResponseEntity<AttributesResponse> attributeResponseResponseEntity = new ResponseEntity<>(
+                new AttributesResponse(Collections.singleton(new Attribute("issuer", "name", "value")),
+                        "attributesResponse"), HttpStatus.OK);
+        OAuth2RestTemplate mockRestTemplate = Mockito.mock(OAuth2RestTemplate.class);
+        Mockito.doReturn(attributeResponseResponseEntity).when(mockRestTemplate)
+                .getForEntity(Mockito.anyString(), Mockito.eq(AttributesResponse.class));
+
+        Mockito.doReturn(Collections.singleton(
+                new AttributeAdapterConnection("https://my-url", "https://my-uaa", "my-client", "my-secret")))
+                .when(externalAttributeReader).getAttributeAdapterConnections();
+
+        Mockito.doReturn(mockRestTemplate).when(externalAttributeReader)
+                .getAdapterOauth2RestTemplate(Mockito.any(AttributeAdapterConnection.class));
+        Mockito.when(attributeCache.getAttributes(identifier)).thenReturn(null);
+    }
+
+    @DataProvider
+    static Object[][] attributeSizeConstraintDataProvider() {
+        return new Integer[][] { { 0, 100 }, { 100, 0 } };
+    }
+
+}
diff --git a/service/src/test/java/com/ge/predix/acs/attribute/readers/ExternalResourceAttributeReaderTest.java b/service/src/test/java/com/ge/predix/acs/attribute/readers/ExternalResourceAttributeReaderTest.java
new file mode 100644
index 0000000..2c65bfc
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/attribute/readers/ExternalResourceAttributeReaderTest.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.mockito.InjectMocks;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.attribute.cache.AttributeCache;
+import com.ge.predix.acs.model.Attribute;
+
+public class ExternalResourceAttributeReaderTest {
+
+    @Spy
+    private AttributeCache attributeCache;
+
+    @InjectMocks
+    @Spy
+    private ExternalResourceAttributeReader externalResourceAttributeReader;
+
+    private static final String IDENTIFIER = "part/03f95db1-4255-4265-a509-f7bca3e1fee4";
+
+    private CachedAttributes expectedAdapterAttributes;
+
+    private static String generateRandomString() {
+        return RandomStringUtils.randomAlphanumeric(20);
+    }
+
+    @BeforeClass
+    void beforeClass() throws Exception {
+        this.expectedAdapterAttributes = new CachedAttributes(Collections
+                .singleton(new Attribute(generateRandomString(), generateRandomString(), generateRandomString())));
+    }
+
+    @BeforeMethod
+    void beforeMethod() {
+        this.attributeCache = Mockito.mock(AttributeCache.class);
+        this.externalResourceAttributeReader = new ExternalResourceAttributeReader(null, this.attributeCache, 3000);
+
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testGetAttributesWithCacheMiss() throws Exception {
+        Mockito.when(this.attributeCache.getAttributes(IDENTIFIER)).thenReturn(null);
+
+        Mockito.doReturn(this.expectedAdapterAttributes).when(this.externalResourceAttributeReader)
+                .getAttributesFromAdapters(IDENTIFIER);
+
+        Set<Attribute> actualAdapterAttributes = this.externalResourceAttributeReader.getAttributes(IDENTIFIER);
+
+        Mockito.verify(this.attributeCache).setAttributes(IDENTIFIER, this.expectedAdapterAttributes);
+
+        Assert.assertEquals(this.expectedAdapterAttributes.getAttributes(), actualAdapterAttributes);
+    }
+
+    @Test
+    public void testGetAttributesWithCacheHit() throws Exception {
+        Mockito.when(this.attributeCache.getAttributes(IDENTIFIER)).thenReturn(this.expectedAdapterAttributes);
+
+        Set<Attribute> actualAdapterAttributes = this.externalResourceAttributeReader.getAttributes(IDENTIFIER);
+
+        Mockito.verify(this.externalResourceAttributeReader, Mockito.times(0)).getAttributesFromAdapters(IDENTIFIER);
+        Mockito.verify(this.attributeCache, Mockito.times(0)).setAttributes(IDENTIFIER, this.expectedAdapterAttributes);
+
+        Assert.assertEquals(this.expectedAdapterAttributes.getAttributes(), actualAdapterAttributes);
+    }
+
+    @Test(dataProviderClass = ExternalAttributeReaderHelper.class,
+            dataProvider = "attributeSizeConstraintDataProvider",
+            expectedExceptions = { AttributeRetrievalException.class },
+            expectedExceptionsMessageRegExp = "Total size of attributes or "
+                    + "number of attributes too large for id: '" + IDENTIFIER + "'.*")
+    public void testGetAttributesThatAreToLarge(final int maxNumberOfAttributes, final int maxSizeOfAttributesInBytes)
+            throws Exception {
+        ExternalAttributeReaderHelper
+                .setupMockedAdapterResponse(this.externalResourceAttributeReader, this.attributeCache, IDENTIFIER);
+        ReflectionTestUtils
+                .setField(this.externalResourceAttributeReader, "maxNumberOfAttributes", maxNumberOfAttributes);
+        ReflectionTestUtils.setField(this.externalResourceAttributeReader, "maxSizeOfAttributesInBytes",
+                maxSizeOfAttributesInBytes);
+        this.externalResourceAttributeReader.getAttributes(IDENTIFIER);
+    }
+
+}
diff --git a/service/src/test/java/com/ge/predix/acs/attribute/readers/ExternalSubjectAttributeReaderTest.java b/service/src/test/java/com/ge/predix/acs/attribute/readers/ExternalSubjectAttributeReaderTest.java
new file mode 100644
index 0000000..721d125
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/attribute/readers/ExternalSubjectAttributeReaderTest.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.attribute.readers;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.mockito.InjectMocks;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.attribute.cache.AttributeCache;
+import com.ge.predix.acs.model.Attribute;
+
+public class ExternalSubjectAttributeReaderTest {
+
+    @Spy
+    private AttributeCache attributeCache;
+
+    @InjectMocks
+    @Spy
+    private ExternalSubjectAttributeReader externalSubjectAttributeReader;
+
+    private static final String IDENTIFIER = "part/03f95db1-4255-4265-a509-f7bca3e1fee4";
+
+    private CachedAttributes expectedAdapterAttributes;
+
+    private static String generateRandomString() {
+        return RandomStringUtils.randomAlphanumeric(20);
+    }
+
+    @BeforeClass
+    void beforeClass() throws Exception {
+        this.expectedAdapterAttributes = new CachedAttributes(Collections
+                .singleton(new Attribute(generateRandomString(), generateRandomString(), generateRandomString())));
+    }
+
+    @BeforeMethod
+    void beforeMethod() {
+        this.attributeCache = Mockito.mock(AttributeCache.class);
+        this.externalSubjectAttributeReader = new ExternalSubjectAttributeReader(null, this.attributeCache, 3000);
+
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testGetAttributesWithCacheMiss() throws Exception {
+        Mockito.when(this.attributeCache.getAttributes(IDENTIFIER)).thenReturn(null);
+
+        Mockito.doReturn(this.expectedAdapterAttributes).when(this.externalSubjectAttributeReader)
+                .getAttributesFromAdapters(IDENTIFIER);
+
+        Set<Attribute> actualAdapterAttributes = this.externalSubjectAttributeReader.getAttributes(IDENTIFIER);
+
+        Mockito.verify(this.attributeCache).setAttributes(IDENTIFIER, this.expectedAdapterAttributes);
+
+        Assert.assertEquals(this.expectedAdapterAttributes.getAttributes(), actualAdapterAttributes);
+    }
+
+
+    @Test
+    public void testGetAttributesWithCacheHit() throws Exception {
+        Mockito.when(this.attributeCache.getAttributes(IDENTIFIER)).thenReturn(this.expectedAdapterAttributes);
+
+        Set<Attribute> actualAdapterAttributes = this.externalSubjectAttributeReader.getAttributes(IDENTIFIER);
+
+        Mockito.verify(this.externalSubjectAttributeReader, Mockito.times(0)).getAttributesFromAdapters(IDENTIFIER);
+        Mockito.verify(this.attributeCache, Mockito.times(0)).setAttributes(IDENTIFIER, this.expectedAdapterAttributes);
+
+        Assert.assertEquals(this.expectedAdapterAttributes.getAttributes(), actualAdapterAttributes);
+    }
+
+    @Test
+    public void testGetAttributesByScope() throws Exception {
+        Mockito.doReturn(this.expectedAdapterAttributes).when(this.externalSubjectAttributeReader)
+                .getAttributesFromAdapters(IDENTIFIER);
+
+        Set<Attribute> actualAttributes = this.externalSubjectAttributeReader.getAttributes(IDENTIFIER);
+
+        Assert.assertEquals(this.expectedAdapterAttributes.getAttributes(), actualAttributes);
+
+        actualAttributes = this.externalSubjectAttributeReader.getAttributesByScope(IDENTIFIER,
+                Collections.singleton(new Attribute("test-issuer", "test-scope", "test-value")));
+
+        Assert.assertEquals(this.expectedAdapterAttributes.getAttributes(), actualAttributes);
+    }
+
+    @Test(dataProviderClass = ExternalAttributeReaderHelper.class, dataProvider =
+            "attributeSizeConstraintDataProvider", expectedExceptions = {
+            AttributeRetrievalException.class }, expectedExceptionsMessageRegExp = "Total size of attributes or "
+            + "number of attributes too large for id: '" + IDENTIFIER + "'.*")
+    public void testGetAttributesThatAreToLarge(final int maxNumberOfAttributes, final int maxSizeOfAttributesInBytes)
+            throws Exception {
+        ExternalAttributeReaderHelper.setupMockedAdapterResponse(this.externalSubjectAttributeReader, this
+                .attributeCache, IDENTIFIER);
+        ReflectionTestUtils
+                .setField(this.externalSubjectAttributeReader, "maxNumberOfAttributes", maxNumberOfAttributes);
+        ReflectionTestUtils.setField(this.externalSubjectAttributeReader, "maxSizeOfAttributesInBytes",
+                maxSizeOfAttributesInBytes);
+        this.externalSubjectAttributeReader.getAttributes(IDENTIFIER);
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/config/AcsConfigLocalProfileTest.java b/service/src/test/java/com/ge/predix/acs/config/AcsConfigLocalProfileTest.java
new file mode 100644
index 0000000..b1b8ce9
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/config/AcsConfigLocalProfileTest.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.config;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+
+@ContextConfiguration(classes = { InMemoryDataSourceConfig.class })
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+public class AcsConfigLocalProfileTest extends AbstractTestNGSpringContextTests {
+    @Autowired
+    private DataSource dataSource;
+
+    @Test
+    public void testLocalProfile() {
+        Assert.assertTrue(EmbeddedDatabase.class.isAssignableFrom(this.dataSource.getClass()));
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/config/PolicyEvaluationCacheConfigTest.java b/service/src/test/java/com/ge/predix/acs/config/PolicyEvaluationCacheConfigTest.java
new file mode 100644
index 0000000..a045f39
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/config/PolicyEvaluationCacheConfigTest.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.config;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.springframework.core.env.Environment;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.policy.evaluation.cache.NonCachingPolicyEvaluationCache;
+import com.ge.predix.acs.policy.evaluation.cache.RedisPolicyEvaluationCache;
+
+public class PolicyEvaluationCacheConfigTest {
+
+    @Mock
+    private Environment mockEnvironment;
+
+    @InjectMocks
+    private PolicyEvaluationCacheConfig policyEvaluationCacheConfig;
+
+    @BeforeClass
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testPolicyEvaluationCacheConfigDisabled() {
+        setupEnvironment(null);
+        assertThat(policyEvaluationCacheConfig.cache(false), instanceOf(NonCachingPolicyEvaluationCache.class));
+    }
+
+    @Test
+    public void testPolicyEvaluationCacheConfigRedis() {
+        setupEnvironment("redis");
+        assertThat(policyEvaluationCacheConfig.cache(true), instanceOf(RedisPolicyEvaluationCache.class));
+    }
+
+    @Test
+    public void testPolicyEvaluationCacheConfigCloudRedis() {
+        setupEnvironment("cloud-redis");
+        assertThat(policyEvaluationCacheConfig.cache(true), instanceOf(RedisPolicyEvaluationCache.class));
+    }
+
+    private void setupEnvironment(final String springProfileActive) {
+        String[] redisEnvironment = {springProfileActive};
+        Mockito.doReturn(redisEnvironment).when(mockEnvironment).getActiveProfiles();
+    }
+
+}
diff --git a/service/src/test/java/com/ge/predix/acs/encryption/EncryptorTest.java b/service/src/test/java/com/ge/predix/acs/encryption/EncryptorTest.java
new file mode 100644
index 0000000..80e57f3
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/encryption/EncryptorTest.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.encryption;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class EncryptorTest {
+
+    private static final String VALUE_TO_ENCRYPT = "testValue";
+
+    @Test
+    public void testEncryptCompleteFlow() {
+        Encryptor encryption = new Encryptor();
+        encryption.setEncryptionKey("FooBarFooBarFooB");
+        Assert.assertNotEquals(encryption.encrypt(VALUE_TO_ENCRYPT), VALUE_TO_ENCRYPT);
+        Assert.assertEquals(encryption.decrypt(encryption.encrypt(VALUE_TO_ENCRYPT)), VALUE_TO_ENCRYPT);
+    }
+
+    @Test(expectedExceptions = { SymmetricKeyValidationException.class })
+    public void testCreateEncryptionWithTooShortOfAKey() {
+        Encryptor encryption = new Encryptor();
+        encryption.setEncryptionKey("Too_short");
+    }
+
+    @Test
+    public void testCreateEncryptionWithTooLongOfAKey() {
+        try {
+            Encryptor encryption = new Encryptor();
+            encryption.setEncryptionKey("Toooooooooo_loooooooooong");
+        } catch (Throwable e) {
+            Assert.fail();
+        }
+    }
+
+}
diff --git a/service/src/test/java/com/ge/predix/acs/monitoring/AcsDbHealthIndicatorTest.java b/service/src/test/java/com/ge/predix/acs/monitoring/AcsDbHealthIndicatorTest.java
new file mode 100644
index 0000000..32d457e
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/monitoring/AcsDbHealthIndicatorTest.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import com.ge.predix.acs.privilege.management.dao.TitanMigrationManager;
+import org.mockito.Mockito;
+import org.mockito.internal.util.reflection.Whitebox;
+import org.springframework.boot.actuate.health.Status;
+import org.springframework.dao.ConcurrencyFailureException;
+import org.springframework.dao.PermissionDeniedDataAccessException;
+import org.springframework.dao.QueryTimeoutException;
+import org.springframework.dao.TransientDataAccessResourceException;
+import org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class AcsDbHealthIndicatorTest {
+
+    private static final String IS_MIGRATION_COMPLETE_FIELD_NAME = "isMigrationComplete";
+
+    @Test(dataProvider = "statuses")
+    public void testHealth(final AcsMonitoringRepository acsMonitoringRepository, final Status status,
+            final AcsMonitoringUtilities.HealthCode healthCode, final TitanMigrationManager titanMigrationManager)
+            throws Exception {
+        AcsDbHealthIndicator acsDbHealthIndicator = new AcsDbHealthIndicator(acsMonitoringRepository);
+        acsDbHealthIndicator.setMigrationManager(titanMigrationManager);
+        Assert.assertEquals(status, acsDbHealthIndicator.health().getStatus());
+        Assert.assertEquals(AcsDbHealthIndicator.DESCRIPTION,
+                acsDbHealthIndicator.health().getDetails().get(AcsMonitoringUtilities.DESCRIPTION_KEY));
+        if (healthCode == AcsMonitoringUtilities.HealthCode.AVAILABLE) {
+            Assert.assertFalse(acsDbHealthIndicator.health().getDetails().containsKey(AcsMonitoringUtilities.CODE_KEY));
+        } else {
+            Assert.assertEquals(healthCode,
+                    acsDbHealthIndicator.health().getDetails().get(AcsMonitoringUtilities.CODE_KEY));
+        }
+    }
+
+    @DataProvider
+    public Object[][] statuses() {
+        TitanMigrationManager happyMigrationManager = new TitanMigrationManager();
+        TitanMigrationManager sadMigrationManager = new TitanMigrationManager();
+        Whitebox.setInternalState(happyMigrationManager, IS_MIGRATION_COMPLETE_FIELD_NAME, true);
+        Whitebox.setInternalState(sadMigrationManager, IS_MIGRATION_COMPLETE_FIELD_NAME, false);
+
+        return new Object[][] { new Object[] { mockDbWithUp(), Status.UP, AcsMonitoringUtilities.HealthCode.AVAILABLE,
+                happyMigrationManager },
+
+                { mockDbWithException(new TransientDataAccessResourceException("")), Status.DOWN,
+                        AcsMonitoringUtilities.HealthCode.UNAVAILABLE, happyMigrationManager },
+
+                { mockDbWithException(new QueryTimeoutException("")), Status.DOWN,
+                        AcsMonitoringUtilities.HealthCode.UNAVAILABLE, happyMigrationManager },
+
+                { mockDbWithException(new DataSourceLookupFailureException("")), Status.DOWN,
+                        AcsMonitoringUtilities.HealthCode.UNREACHABLE, happyMigrationManager },
+
+                { mockDbWithException(new PermissionDeniedDataAccessException("", null)), Status.DOWN,
+                        AcsMonitoringUtilities.HealthCode.MISCONFIGURATION, happyMigrationManager },
+
+                { mockDbWithException(new ConcurrencyFailureException("")), Status.DOWN,
+                        AcsMonitoringUtilities.HealthCode.ERROR, happyMigrationManager },
+
+                { mockDbWithUp(), Status.DOWN, AcsMonitoringUtilities.HealthCode.MIGRATION_INCOMPLETE,
+                        sadMigrationManager }, };
+    }
+
+    private AcsMonitoringRepository mockDbWithUp() {
+        AcsMonitoringRepository acsMonitoringRepository = Mockito.mock(AcsMonitoringRepository.class);
+        Mockito.doNothing().when(acsMonitoringRepository).queryPolicySetTable();
+        return acsMonitoringRepository;
+    }
+
+    private AcsMonitoringRepository mockDbWithException(final Exception e) {
+        AcsMonitoringRepository acsMonitoringRepository = Mockito.mock(AcsMonitoringRepository.class);
+        Mockito.doThrow(e).when(acsMonitoringRepository).queryPolicySetTable();
+        return acsMonitoringRepository;
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/monitoring/AcsHealthAggregatorTest.java b/service/src/test/java/com/ge/predix/acs/monitoring/AcsHealthAggregatorTest.java
new file mode 100644
index 0000000..40e036f
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/monitoring/AcsHealthAggregatorTest.java
@@ -0,0 +1,128 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.Status;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+// Modified version of org.springframework.boot.actuate.health.OrderedHealthAggregatorTests
+public class AcsHealthAggregatorTest {
+
+    private AcsHealthAggregator healthAggregator;
+
+    @BeforeMethod
+    public void beforeMethod() {
+        this.healthAggregator = new AcsHealthAggregator();
+    }
+
+    @Test
+    public void defaultOrder() {
+        Map<String, Health> healths = new HashMap<>();
+        healths.put("h1", new Health.Builder().status(Status.DOWN).build());
+        healths.put("h2", new Health.Builder().status(Status.UP).build());
+        healths.put("h3", new Health.Builder().status(Status.UNKNOWN).build());
+        healths.put("h4", new Health.Builder().status(Status.OUT_OF_SERVICE).build());
+        Assert.assertEquals(this.healthAggregator.aggregate(healths).getStatus(), Status.DOWN);
+    }
+
+    @Test
+    public void customOrder() {
+        this.healthAggregator.setStatusOrder(Status.UNKNOWN, Status.UP, Status.OUT_OF_SERVICE, Status.DOWN);
+        Map<String, Health> healths = new HashMap<>();
+        healths.put("h1", new Health.Builder().status(Status.DOWN).build());
+        healths.put("h2", new Health.Builder().status(Status.UP).build());
+        healths.put("h3", new Health.Builder().status(Status.UNKNOWN).build());
+        healths.put("h4", new Health.Builder().status(Status.OUT_OF_SERVICE).build());
+        Assert.assertEquals(this.healthAggregator.aggregate(healths).getStatus(), Status.UNKNOWN);
+    }
+
+    @Test
+    public void defaultOrderWithCustomStatus() {
+        Map<String, Health> healths = new HashMap<>();
+        healths.put("h1", new Health.Builder().status(Status.DOWN).build());
+        healths.put("h2", new Health.Builder().status(Status.UP).build());
+        healths.put("h3", new Health.Builder().status(Status.UNKNOWN).build());
+        healths.put("h4", new Health.Builder().status(Status.OUT_OF_SERVICE).build());
+        healths.put("h5", new Health.Builder().status(new Status("CUSTOM")).build());
+        Assert.assertEquals(this.healthAggregator.aggregate(healths).getStatus(), Status.DOWN);
+    }
+
+    @Test(dataProvider = "statuses")
+    public void defaultOrderWithDegradedStatus(final Status expectedStatus, final List<Status> actualStatuses) {
+        Map<String, Health> healths = new HashMap<>();
+        healths.put("h1", new Health.Builder().status(actualStatuses.get(0)).build());
+        healths.put("h2", new Health.Builder().status(actualStatuses.get(1)).build());
+        healths.put("h3", new Health.Builder().status(actualStatuses.get(2)).build());
+        Assert.assertEquals(this.healthAggregator.aggregate(healths).getStatus(), expectedStatus);
+    }
+
+    @DataProvider
+    public Object[][] statuses() {
+        return new Object[][] { new Object[] { AcsHealthAggregator.DEGRADED_STATUS,
+                Arrays.asList(Status.UP, AcsHealthAggregator.DEGRADED_STATUS, Status.UP) },
+
+                { Status.UP, Arrays.asList(Status.UP, Status.UNKNOWN, Status.UP) },
+
+                { Status.DOWN, Arrays.asList(Status.UP, AcsHealthAggregator.DEGRADED_STATUS, Status.DOWN) }, };
+    }
+
+    @Test
+    public void customOrderWithCustomStatus() {
+        this.healthAggregator.setStatusOrder(Arrays.asList("DOWN", "OUT_OF_SERVICE", "UP", "UNKNOWN", "CUSTOM"));
+        Map<String, Health> healths = new HashMap<>();
+        healths.put("h1", new Health.Builder().status(Status.DOWN).build());
+        healths.put("h2", new Health.Builder().status(Status.UP).build());
+        healths.put("h3", new Health.Builder().status(Status.UNKNOWN).build());
+        healths.put("h4", new Health.Builder().status(Status.OUT_OF_SERVICE).build());
+        healths.put("h5", new Health.Builder().status(new Status("CUSTOM")).build());
+        Assert.assertEquals(this.healthAggregator.aggregate(healths).getStatus(), Status.DOWN);
+    }
+
+    @Test(dataProvider = "singleStatuses")
+    public void customOrderWithSingleStatus(final String key, final Health health, final Status expectedStatus) {
+        Map<String, Health> healths = new HashMap<>();
+        healths.put(key, health);
+        Assert.assertEquals(this.healthAggregator.aggregate(healths).getStatus(), expectedStatus);
+    }
+
+    @DataProvider
+    public Object[][] singleStatuses() {
+        return new Object[][] {
+                new Object[] { "h1", new Health.Builder().status(new Status("CUSTOM")).build(), Status.UNKNOWN },
+
+                { "cache", new Health.Builder().status(Status.DOWN).build(), AcsHealthAggregator.DEGRADED_STATUS },
+
+                { "cache", new Health.Builder().status(Status.UNKNOWN).build(), Status.UP }, };
+    }
+
+    @Test
+    public void noStatuses() throws Exception {
+        Map<String, Health> healths = new HashMap<>();
+        Assert.assertEquals(this.healthAggregator.aggregate(healths).getStatus(), Status.UNKNOWN);
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/monitoring/CacheHealthIndicatorTest.java b/service/src/test/java/com/ge/predix/acs/monitoring/CacheHealthIndicatorTest.java
new file mode 100644
index 0000000..4843f06
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/monitoring/CacheHealthIndicatorTest.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import org.mockito.Mockito;
+import org.springframework.boot.actuate.health.Status;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class CacheHealthIndicatorTest {
+
+    @Test(dataProvider = "statuses")
+    public void testHealth(final CacheHealthIndicator cacheHealthIndicator, final Status status,
+            final AcsMonitoringUtilities.HealthCode healthCode) throws Exception {
+        Assert.assertEquals(cacheHealthIndicator.health().getStatus(), status);
+        Assert.assertEquals(cacheHealthIndicator.health().getDetails().get(AcsMonitoringUtilities.DESCRIPTION_KEY),
+                AbstractCacheHealthIndicator.DESCRIPTION);
+        if (healthCode == AcsMonitoringUtilities.HealthCode.AVAILABLE) {
+            Assert.assertFalse(cacheHealthIndicator.health().getDetails().containsKey(AcsMonitoringUtilities.CODE_KEY));
+        } else {
+            Assert.assertEquals(cacheHealthIndicator.health().getDetails().get(AcsMonitoringUtilities.CODE_KEY),
+                    healthCode);
+        }
+    }
+
+    @DataProvider
+    public Object[][] statuses() throws Exception {
+        return new Object[][] {
+                new Object[] { mockCacheWithUp(true, true, DecisionCacheHealthIndicator.class), Status.UP,
+                        AcsMonitoringUtilities.HealthCode.AVAILABLE },
+                { mockCacheWithUp(true, true, ResourceCacheHealthIndicator.class), Status.UP,
+                        AcsMonitoringUtilities.HealthCode.AVAILABLE },
+                { mockCacheWithUp(true, true, SubjectCacheHealthIndicator.class), Status.UP,
+                        AcsMonitoringUtilities.HealthCode.AVAILABLE },
+                { mockCacheWithUp(false, true, DecisionCacheHealthIndicator.class), Status.UNKNOWN,
+                        AcsMonitoringUtilities.HealthCode.DISABLED },
+                { mockCacheWithUp(false, true, ResourceCacheHealthIndicator.class), Status.UNKNOWN,
+                        AcsMonitoringUtilities.HealthCode.DISABLED },
+                { mockCacheWithUp(false, true, SubjectCacheHealthIndicator.class), Status.UNKNOWN,
+                        AcsMonitoringUtilities.HealthCode.DISABLED },
+                { mockCacheWithUp(true, false, DecisionCacheHealthIndicator.class), Status.UNKNOWN,
+                        AcsMonitoringUtilities.HealthCode.HEALTH_CHECK_DISABLED },
+                { mockCacheWithUp(true, false, ResourceCacheHealthIndicator.class), Status.UNKNOWN,
+                        AcsMonitoringUtilities.HealthCode.HEALTH_CHECK_DISABLED },
+                { mockCacheWithUp(true, false, SubjectCacheHealthIndicator.class), Status.UNKNOWN,
+                        AcsMonitoringUtilities.HealthCode.HEALTH_CHECK_DISABLED },
+                { mockCacheWithExceptionWhileGettingConnection(new RuntimeException(),
+                        DecisionCacheHealthIndicator.class), Status.DOWN,
+                        AcsMonitoringUtilities.HealthCode.ERROR },
+                { mockCacheWithExceptionWhileGettingConnection(new RuntimeException(),
+                        ResourceCacheHealthIndicator.class), Status.DOWN, AcsMonitoringUtilities.HealthCode.ERROR },
+                { mockCacheWithExceptionWhileGettingConnection(new RuntimeException(),
+                        SubjectCacheHealthIndicator.class), Status.DOWN, AcsMonitoringUtilities.HealthCode.ERROR },
+                { mockCacheWithExceptionWhileGettingInfo(new RuntimeException(), DecisionCacheHealthIndicator.class),
+                        Status.DOWN, AcsMonitoringUtilities.HealthCode.ERROR },
+                { mockCacheWithExceptionWhileGettingInfo(new RuntimeException(), ResourceCacheHealthIndicator.class),
+                        Status.DOWN, AcsMonitoringUtilities.HealthCode.ERROR },
+                { mockCacheWithExceptionWhileGettingInfo(new RuntimeException(), SubjectCacheHealthIndicator.class),
+                        Status.DOWN, AcsMonitoringUtilities.HealthCode.ERROR } };
+    }
+
+    @SuppressWarnings("unchecked")
+    private static CacheHealthIndicator mockCache(final boolean cachingEnabled, final boolean healthCheckEnabled,
+            final Class clazz) throws Exception {
+        RedisConnectionFactory redisConnectionFactory = Mockito.mock(RedisConnectionFactory.class);
+        CacheHealthIndicator cacheHealthIndicator = (CacheHealthIndicator) clazz
+                .getConstructor(RedisConnectionFactory.class, boolean.class)
+                .newInstance(redisConnectionFactory, cachingEnabled);
+        ReflectionTestUtils.setField(cacheHealthIndicator, "healthCheckEnabled", healthCheckEnabled);
+        return Mockito.spy(cacheHealthIndicator);
+    }
+
+    private static CacheHealthIndicator mockCache(final Class clazz) throws Exception {
+            return mockCache(true, true, clazz);
+    }
+
+    private CacheHealthIndicator mockCacheWithUp(final boolean cachingEnabled, final boolean healthCheckEnabled,
+            final Class clazz) throws Exception {
+        CacheHealthIndicator cacheHealthIndicator = mockCache(cachingEnabled, healthCheckEnabled, clazz);
+        Mockito.doReturn(Mockito.mock(RedisConnection.class)).when(cacheHealthIndicator).getRedisConnection();
+        return cacheHealthIndicator;
+    }
+
+
+    private CacheHealthIndicator mockCacheWithExceptionWhileGettingConnection(final Exception e, final Class clazz)
+            throws Exception {
+        CacheHealthIndicator cacheHealthIndicator = mockCache(clazz);
+        Mockito.doThrow(e).when(cacheHealthIndicator).getRedisConnection();
+        return cacheHealthIndicator;
+    }
+
+    private CacheHealthIndicator mockCacheWithExceptionWhileGettingInfo(final Exception e, final Class clazz)
+            throws Exception {
+        CacheHealthIndicator decisionCacheHealthIndicator = mockCache(clazz);
+        RedisConnection redisConnection = Mockito.mock(RedisConnection.class);
+        Mockito.doReturn(redisConnection).when(decisionCacheHealthIndicator).getRedisConnection();
+        Mockito.doThrow(e).when(redisConnection).info();
+        return decisionCacheHealthIndicator;
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/monitoring/GraphDbHealthIndicatorTest.java b/service/src/test/java/com/ge/predix/acs/monitoring/GraphDbHealthIndicatorTest.java
new file mode 100644
index 0000000..0f88357
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/monitoring/GraphDbHealthIndicatorTest.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import com.ge.predix.acs.privilege.management.dao.GraphResourceRepository;
+import com.thinkaurelius.titan.core.QueryException;
+import com.thinkaurelius.titan.core.TitanConfigurationException;
+import com.thinkaurelius.titan.diskstorage.ResourceUnavailableException;
+import com.thinkaurelius.titan.graphdb.database.idassigner.IDPoolExhaustedException;
+import org.mockito.Mockito;
+import org.springframework.boot.actuate.health.Status;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class GraphDbHealthIndicatorTest {
+
+    @Test(dataProvider = "statuses")
+    public void testHealth(final GraphResourceRepository graphResourceRepository, final Status status,
+            final AcsMonitoringUtilities.HealthCode healthCode, final boolean cassandraEnabled) throws Exception {
+        GraphDbHealthIndicator graphDbHealthIndicator = new GraphDbHealthIndicator(graphResourceRepository);
+        ReflectionTestUtils.setField(graphDbHealthIndicator, "cassandraEnabled", cassandraEnabled);
+        Assert.assertEquals(status, graphDbHealthIndicator.health().getStatus());
+        Assert.assertEquals(GraphDbHealthIndicator.DESCRIPTION,
+                graphDbHealthIndicator.health().getDetails().get(AcsMonitoringUtilities.DESCRIPTION_KEY));
+        if (healthCode == AcsMonitoringUtilities.HealthCode.AVAILABLE) {
+            Assert.assertFalse(
+                    graphDbHealthIndicator.health().getDetails().containsKey(AcsMonitoringUtilities.CODE_KEY));
+        } else {
+            Assert.assertEquals(healthCode,
+                    graphDbHealthIndicator.health().getDetails().get(AcsMonitoringUtilities.CODE_KEY));
+        }
+    }
+
+    @DataProvider
+    public Object[][] statuses() {
+        return new Object[][] {
+                new Object[] { mockGraphDbWithUp(), Status.UP, AcsMonitoringUtilities.HealthCode.IN_MEMORY, false },
+
+                { mockGraphDbWithUp(), Status.UP, AcsMonitoringUtilities.HealthCode.AVAILABLE, true },
+
+                { mockGraphDbWithExceptionWhileCheckingVersion(new QueryException("")), Status.DOWN,
+                        AcsMonitoringUtilities.HealthCode.INVALID_QUERY, true },
+
+                { mockGraphDbWithExceptionWhileCheckingVersion(new ResourceUnavailableException("")), Status.DOWN,
+                        AcsMonitoringUtilities.HealthCode.UNAVAILABLE, true },
+
+                { mockGraphDbWithExceptionWhileCheckingVersion(new TitanConfigurationException("")), Status.DOWN,
+                        AcsMonitoringUtilities.HealthCode.MISCONFIGURATION, true },
+
+                { mockGraphDbWithExceptionWhileCheckingVersion(new IDPoolExhaustedException("")), Status.DOWN,
+                        AcsMonitoringUtilities.HealthCode.ERROR, true }, };
+
+    }
+
+    private GraphResourceRepository mockGraphDbWithUp() {
+        GraphResourceRepository graphResourceRepository = Mockito.mock(GraphResourceRepository.class);
+        Mockito.doReturn(true).when(graphResourceRepository).checkVersionVertexExists(Mockito.anyInt());
+        return graphResourceRepository;
+    }
+
+    private GraphResourceRepository mockGraphDbWithExceptionWhileCheckingVersion(final Exception e) {
+        GraphResourceRepository graphResourceRepository = Mockito.mock(GraphResourceRepository.class);
+        Mockito.doThrow(e).when(graphResourceRepository).checkVersionVertexExists(Mockito.anyInt());
+        return graphResourceRepository;
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/monitoring/MonitoringHttpMethodsFilterTest.java b/service/src/test/java/com/ge/predix/acs/monitoring/MonitoringHttpMethodsFilterTest.java
new file mode 100644
index 0000000..5f32119
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/monitoring/MonitoringHttpMethodsFilterTest.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mockito.InjectMocks;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpMethod;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public final class MonitoringHttpMethodsFilterTest {
+
+    @InjectMocks
+    private AcsMonitoringController acsMonitoringController;
+
+    private static final Set<HttpMethod> ALL_HTTP_METHODS = new HashSet<>(Arrays.asList(HttpMethod.values()));
+
+    private MockMvc mockMvc;
+
+    @BeforeClass
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        this.mockMvc = MockMvcBuilders.standaloneSetup(this.acsMonitoringController)
+                .addFilters(new MonitoringHttpMethodsFilter()).build();
+    }
+
+    @Test(dataProvider = "urisAndTheirAllowedHttpMethods")
+    public void testUriPatternsAndTheirAllowedHttpMethods(final String uri, final Set<HttpMethod> allowedHttpMethods)
+            throws Exception {
+        Set<HttpMethod> disallowedHttpMethods = new HashSet<>(ALL_HTTP_METHODS);
+        disallowedHttpMethods.removeAll(allowedHttpMethods);
+        for (HttpMethod disallowedHttpMethod : disallowedHttpMethods) {
+            this.mockMvc.perform(MockMvcRequestBuilders.request(disallowedHttpMethod, URI.create(uri)))
+                    .andExpect(MockMvcResultMatchers.status().isMethodNotAllowed());
+        }
+    }
+
+    @DataProvider
+    public Object[][] urisAndTheirAllowedHttpMethods() {
+        return new Object[][] { new Object[] { "/monitoring/heartbeat",
+                new HashSet<>(Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS)) } };
+    }
+}
\ No newline at end of file
diff --git a/service/src/test/java/com/ge/predix/acs/monitoring/UaaHealthIndicatorTest.java b/service/src/test/java/com/ge/predix/acs/monitoring/UaaHealthIndicatorTest.java
new file mode 100644
index 0000000..96d0a5c
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/monitoring/UaaHealthIndicatorTest.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.monitoring;
+
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.health.Status;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class UaaHealthIndicatorTest {
+
+    @Value("${uaaCheckHealthUrl}")
+    private String uaaCheckHealthUrl;
+
+    @Test(dataProvider = "statuses")
+    public void testHealth(final RestTemplate restTemplate, final Status status,
+            final AcsMonitoringUtilities.HealthCode healthCode) throws Exception {
+        UaaHealthIndicator uaaHealthIndicator = new UaaHealthIndicator(restTemplate);
+        Assert.assertEquals(status, uaaHealthIndicator.health().getStatus());
+        Assert.assertEquals(uaaHealthIndicator.getDescription(),
+                uaaHealthIndicator.health().getDetails().get(AcsMonitoringUtilities.DESCRIPTION_KEY));
+        if (healthCode == AcsMonitoringUtilities.HealthCode.AVAILABLE) {
+            Assert.assertFalse(uaaHealthIndicator.health().getDetails().containsKey(AcsMonitoringUtilities.CODE_KEY));
+        } else {
+            Assert.assertEquals(healthCode,
+                    uaaHealthIndicator.health().getDetails().get(AcsMonitoringUtilities.CODE_KEY));
+        }
+    }
+
+    @DataProvider
+    public Object[][] statuses() {
+        return new Object[][] {
+                new Object[] { mockRestWithUp(), Status.UP, AcsMonitoringUtilities.HealthCode.AVAILABLE },
+
+                { mockRestWithException(new RestClientException("")), Status.DOWN,
+                        AcsMonitoringUtilities.HealthCode.UNREACHABLE },
+
+                { mockRestWithException(new RuntimeException()), Status.DOWN,
+                        AcsMonitoringUtilities.HealthCode.ERROR }, };
+    }
+
+    private RestTemplate mockRestWithUp() {
+        RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
+        Mockito.when(restTemplate.getForObject(this.uaaCheckHealthUrl, String.class)).thenReturn("OK");
+        return restTemplate;
+    }
+
+    private RestTemplate mockRestWithException(final Exception e) {
+        RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
+        Mockito.when(restTemplate.getForObject(this.uaaCheckHealthUrl, String.class)).thenThrow(e);
+        return restTemplate;
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/policy/evaluation/cache/AbstractPolicyEvaluationCacheTest.java b/service/src/test/java/com/ge/predix/acs/policy/evaluation/cache/AbstractPolicyEvaluationCacheTest.java
new file mode 100644
index 0000000..0db6911
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/policy/evaluation/cache/AbstractPolicyEvaluationCacheTest.java
@@ -0,0 +1,522 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.policy.evaluation.cache;
+
+import static com.ge.predix.acs.testutils.XFiles.AGENT_MULDER;
+import static com.ge.predix.acs.testutils.XFiles.XFILES_ID;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.joda.time.DateTime;
+import org.mockito.Mockito;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.attribute.connector.management.AttributeConnectorService;
+import com.ge.predix.acs.attribute.connector.management.AttributeConnectorServiceImpl;
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.privilege.management.dao.ResourceEntity;
+import com.ge.predix.acs.privilege.management.dao.SubjectEntity;
+import com.ge.predix.acs.rest.AttributeConnector;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+public class AbstractPolicyEvaluationCacheTest {
+
+    private static final String ZONE_NAME = "testzone1";
+    private static final ZoneEntity ZONE_ENTITY = new ZoneEntity(1L, ZONE_NAME);
+    private static final String ACTION_GET = "GET";
+    private static final PolicySet POLICY_ONE = new PolicySet("policyOne");
+    private static final PolicySet POLICY_TWO = new PolicySet("policyTwo");
+    private static final LinkedHashSet<String> EVALUATION_ORDER_POLICYONE_POLICYTWO = Stream
+            .of("policyOne", "policyTwo").collect(Collectors.toCollection(LinkedHashSet::new));
+    private static final LinkedHashSet<String> EVALUATION_ORDER_POLICYTWO_POLICYONE = Stream
+            .of("policyTwo", "policyOne").collect(Collectors.toCollection(LinkedHashSet::new));
+    private static final LinkedHashSet<String> EVALUATION_ORDER_POLICYONE = Stream.of("policyOne")
+            .collect(Collectors.toCollection(LinkedHashSet::new));
+
+    private final InMemoryPolicyEvaluationCache cache = new InMemoryPolicyEvaluationCache();
+
+    @BeforeClass
+    void beforeClass() {
+        AttributeConnectorService connectorService = Mockito.mock(AttributeConnectorService.class);
+        ReflectionTestUtils.setField(this.cache, "connectorService", connectorService);
+    }
+
+    @AfterMethod
+    public void cleanupTest() {
+        this.cache.reset();
+    }
+
+    @Test
+    public void testGetWithCacheMissForPolicyEvaluation() {
+
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+        assertNull(this.cache.get(key));
+    }
+
+    @Test
+    public void testGetWithCacheMissForResource() {
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        this.cache.resetForSubject(ZONE_NAME, AGENT_MULDER);
+        this.cache.resetForPolicySet(ZONE_NAME, POLICY_ONE.getName());
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+        PolicyEvaluationResult result = mockPermitResult();
+        this.cache.set(key, result);
+        this.cache.delete(AbstractPolicyEvaluationCache.resourceKey(ZONE_NAME, XFILES_ID));
+        assertNull(this.cache.get(key));
+    }
+
+    @Test
+    public void testGetWithCacheHit() {
+
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        this.cache.resetForSubject(ZONE_NAME, AGENT_MULDER);
+        this.cache.resetForResource(ZONE_NAME, XFILES_ID);
+        this.cache.resetForPolicySet(ZONE_NAME, POLICY_ONE.getName());
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        request.setPolicySetsEvaluationOrder(EVALUATION_ORDER_POLICYONE);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationResult result = mockPermitResult();
+        this.cache.set(key, result);
+
+        PolicyEvaluationResult cachedResult = this.cache.get(key);
+        assertEquals(cachedResult.getEffect(), result.getEffect());
+    }
+
+    @Test
+    public void testGetWithPolicyInvalidation() throws Exception {
+
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        this.cache.resetForSubject(ZONE_NAME, AGENT_MULDER);
+        this.cache.resetForResource(ZONE_NAME, XFILES_ID);
+        this.cache.resetForPolicySet(ZONE_NAME, POLICY_ONE.getName());
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        request.setPolicySetsEvaluationOrder(EVALUATION_ORDER_POLICYONE);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationResult result = mockPermitResult();
+        this.cache.set(key, result);
+
+        PolicyEvaluationResult cachedResult = this.cache.get(key);
+        assertEquals(cachedResult.getEffect(), result.getEffect());
+
+        Thread.sleep(1);
+        this.cache.resetForPolicySet(ZONE_NAME, POLICY_ONE.getName());
+        assertNull(this.cache.get(key));
+    }
+
+    @Test
+    public void testGetWithResetForMultiplePolicySets() throws Exception {
+
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        this.cache.resetForSubject(ZONE_NAME, AGENT_MULDER);
+        this.cache.resetForResource(ZONE_NAME, XFILES_ID);
+        this.cache.resetForPolicySet(ZONE_NAME, POLICY_ONE.getName());
+        this.cache.resetForPolicySet(ZONE_NAME, POLICY_TWO.getName());
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        request.setPolicySetsEvaluationOrder(EVALUATION_ORDER_POLICYONE_POLICYTWO);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationResult result = mockPermitResult();
+        this.cache.set(key, result);
+
+        PolicyEvaluationResult cachedResult = this.cache.get(key);
+        assertNotNull(cachedResult);
+        assertEquals(cachedResult.getEffect(), result.getEffect());
+
+        Thread.sleep(1);
+        this.cache.resetForPolicySet(ZONE_NAME, POLICY_TWO.getName());
+        assertNull(this.cache.get(key));
+    }
+
+    @Test
+    public void testGetWithPolicyEvaluationOrderChange() throws Exception {
+
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        this.cache.resetForSubject(ZONE_NAME, AGENT_MULDER);
+        this.cache.resetForResource(ZONE_NAME, XFILES_ID);
+        this.cache.resetForPolicySet(ZONE_NAME, POLICY_ONE.getName());
+        this.cache.resetForPolicySet(ZONE_NAME, POLICY_TWO.getName());
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        request.setPolicySetsEvaluationOrder(EVALUATION_ORDER_POLICYONE_POLICYTWO);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationResult result = mockPermitResult();
+        this.cache.set(key, result);
+
+        PolicyEvaluationResult cachedResult = this.cache.get(key);
+        assertNotNull(cachedResult);
+        assertEquals(cachedResult.getEffect(), result.getEffect());
+
+        Thread.sleep(1);
+
+        request.setPolicySetsEvaluationOrder(EVALUATION_ORDER_POLICYTWO_POLICYONE);
+        key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME).request(request).build();
+
+        assertNull(this.cache.get(key));
+    }
+
+    @Test
+    public void testGetWithResetForResource() throws Exception {
+
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        this.cache.resetForSubject(ZONE_NAME, AGENT_MULDER);
+        this.cache.resetForResource(ZONE_NAME, XFILES_ID);
+        this.cache.resetForPolicySet(ZONE_NAME, POLICY_ONE.getName());
+        this.cache.resetForPolicySet(ZONE_NAME, POLICY_TWO.getName());
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationResult result = mockPermitResult();
+        this.cache.set(key, result);
+
+        PolicyEvaluationResult cachedResult = this.cache.get(key);
+        assertEquals(cachedResult.getEffect(), result.getEffect());
+
+        Thread.sleep(1);
+        this.cache.resetForResource(ZONE_NAME, XFILES_ID);
+        assertNull(this.cache.get(key));
+    }
+
+    @Test
+    public void testGetWithResetForLongResource() throws Exception {
+
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        this.cache.resetForSubject(ZONE_NAME, AGENT_MULDER);
+        this.cache.resetForResource(ZONE_NAME, XFILES_ID);
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier("/v1/x-files");
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationResult result = mockPermitResult();
+        this.cache.set(key, result);
+
+        PolicyEvaluationResult cachedResult = this.cache.get(key);
+        assertEquals(cachedResult.getEffect(), result.getEffect());
+
+        Thread.sleep(1);
+        this.cache.resetForResource(ZONE_NAME, XFILES_ID);
+        assertNull(this.cache.get(key));
+    }
+
+    @Test
+    public void testGetWithResetForResources() throws Exception {
+
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        this.cache.resetForSubject(ZONE_NAME, AGENT_MULDER);
+        this.cache.resetForResource(ZONE_NAME, XFILES_ID);
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationResult result = mockPermitResult();
+        this.cache.set(key, result);
+
+        PolicyEvaluationResult cachedResult = this.cache.get(key);
+        assertEquals(cachedResult.getEffect(), result.getEffect());
+
+        Thread.sleep(1);
+        this.cache.resetForResources(ZONE_NAME, Collections.singletonList(new ResourceEntity(ZONE_ENTITY, XFILES_ID)));
+        assertNull(this.cache.get(key));
+    }
+
+    @Test
+    public void testGetWithResetForResolvedResources() throws Exception {
+
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        String resolvedResourceUri = "/resolved-resource";
+        this.cache.resetForSubject(ZONE_NAME, AGENT_MULDER);
+        this.cache.resetForResource(ZONE_NAME, XFILES_ID);
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationResult result = mockPermitResult();
+        result.setResolvedResourceUris(Collections.singleton(resolvedResourceUri));
+        this.cache.set(key, result);
+
+        PolicyEvaluationResult cachedResult = this.cache.get(key);
+        assertEquals(cachedResult.getEffect(), result.getEffect());
+
+        Thread.sleep(1);
+        this.cache.resetForResources(ZONE_NAME,
+                Collections.singletonList(new ResourceEntity(ZONE_ENTITY, resolvedResourceUri)));
+        assertNull(this.cache.get(key));
+    }
+
+    @Test
+    public void testGetWithResetForResourcesByIds() throws Exception {
+
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        this.cache.resetForSubject(ZONE_NAME, AGENT_MULDER);
+        this.cache.resetForResource(ZONE_NAME, XFILES_ID);
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationResult result = mockPermitResult();
+        this.cache.set(key, result);
+
+        PolicyEvaluationResult cachedResult = this.cache.get(key);
+        assertEquals(cachedResult.getEffect(), result.getEffect());
+
+        Thread.sleep(1);
+        this.cache.resetForResourcesByIds(ZONE_NAME, Collections.singleton(XFILES_ID));
+        assertNull(this.cache.get(key));
+    }
+
+    @Test
+    public void testGetWithResetForSubject() throws Exception {
+
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        this.cache.resetForSubject(ZONE_NAME, AGENT_MULDER);
+        this.cache.resetForResource(ZONE_NAME, XFILES_ID);
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationResult result = mockPermitResult();
+        this.cache.set(key, result);
+
+        PolicyEvaluationResult cachedResult = this.cache.get(key);
+        assertEquals(cachedResult.getEffect(), result.getEffect());
+
+        Thread.sleep(1);
+        this.cache.resetForSubject(ZONE_NAME, AGENT_MULDER);
+        assertNull(this.cache.get(key));
+    }
+
+    @Test
+    public void testGetWithResetForSubjectsByIds() throws Exception {
+
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        this.cache.resetForSubject(ZONE_NAME, AGENT_MULDER);
+        this.cache.resetForResource(ZONE_NAME, XFILES_ID);
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationResult result = mockPermitResult();
+        this.cache.set(key, result);
+
+        PolicyEvaluationResult cachedResult = this.cache.get(key);
+        assertEquals(cachedResult.getEffect(), result.getEffect());
+
+        Thread.sleep(1);
+        this.cache.resetForSubjectsByIds(ZONE_NAME, Collections.singleton(AGENT_MULDER));
+        assertNull(this.cache.get(key));
+    }
+
+    @Test
+    public void testGetWithResetForSubjects() throws Exception {
+
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        this.cache.resetForSubject(ZONE_NAME, AGENT_MULDER);
+        this.cache.resetForResource(ZONE_NAME, XFILES_ID);
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationResult result = mockPermitResult();
+        this.cache.set(key, result);
+
+        PolicyEvaluationResult cachedResult = this.cache.get(key);
+        assertEquals(cachedResult.getEffect(), result.getEffect());
+
+        Thread.sleep(1);
+        this.cache.resetForSubjects(ZONE_NAME, Collections.singletonList(new SubjectEntity(ZONE_ENTITY, AGENT_MULDER)));
+        assertNull(this.cache.get(key));
+    }
+
+    @Test
+    public void testGetWithReset() {
+
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        this.cache.resetForSubject(ZONE_NAME, AGENT_MULDER);
+        this.cache.resetForResource(ZONE_NAME, XFILES_ID);
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationResult result = mockPermitResult();
+        this.cache.set(key, result);
+
+        PolicyEvaluationResult cachedResult = this.cache.get(key);
+        assertEquals(cachedResult.getEffect(), result.getEffect());
+
+        this.cache.reset();
+        assertNull(this.cache.get(key));
+    }
+
+    @Test(dataProvider = "intervalProvider")
+    public void testHaveConnectorIntervalsLapsed(final AttributeConnector resourceConnector,
+            final AttributeConnector subjectConnector, final DateTime currentTime,
+            final boolean haveConnectorCacheIntervalsLapsed) {
+        AttributeConnectorService connectorService = Mockito.mock(AttributeConnectorServiceImpl.class);
+
+        Mockito.doReturn(resourceConnector).when(connectorService).getResourceAttributeConnector();
+        Mockito.doReturn(subjectConnector).when(connectorService).getSubjectAttributeConnector();
+        this.cache.resetForPolicySet(ZONE_NAME, POLICY_ONE.getName());
+
+        boolean isResourceConnectorConfigured = resourceConnector != null;
+        boolean isSubjectConnectorConfigured = subjectConnector != null;
+        Mockito.doReturn(isResourceConnectorConfigured).when(connectorService).isResourceAttributeConnectorConfigured();
+        Mockito.doReturn(isSubjectConnectorConfigured).when(connectorService).isSubjectAttributeConnectorConfigured();
+
+        InMemoryPolicyEvaluationCache spiedCache = Mockito.spy(this.cache);
+        ReflectionTestUtils.setField(spiedCache, "connectorService", connectorService);
+
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        request.setPolicySetsEvaluationOrder(EVALUATION_ORDER_POLICYONE);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationResult expectedResult = mockPermitResult();
+        spiedCache.set(key, expectedResult);
+
+        PolicyEvaluationResult actualResult = spiedCache.get(key);
+        Assert.assertEquals(actualResult.getEffect(), expectedResult.getEffect());
+        Assert.assertEquals(actualResult.getResourceAttributes(), expectedResult.getResourceAttributes());
+        Assert.assertEquals(actualResult.getSubjectAttributes(), expectedResult.getSubjectAttributes());
+
+        Mockito.verify(spiedCache, Mockito.times(isResourceConnectorConfigured || isSubjectConnectorConfigured ? 1 : 2))
+                .haveEntitiesChanged(Mockito.any(), Mockito.any());
+        Mockito.verify(spiedCache, Mockito.times(isResourceConnectorConfigured || isSubjectConnectorConfigured ? 1 : 0))
+                .haveConnectorCacheIntervalsLapsed(Mockito.any(), Mockito.any());
+        Assert.assertEquals(this.cache.haveConnectorCacheIntervalsLapsed(connectorService, currentTime),
+                haveConnectorCacheIntervalsLapsed);
+
+    }
+
+    private static PolicyEvaluationResult mockPermitResult() {
+        PolicyEvaluationResult result = new PolicyEvaluationResult(Effect.PERMIT);
+        result.setResolvedResourceUris(Collections.singleton(XFILES_ID));
+        return result;
+    }
+
+    @DataProvider
+    private Object[][] intervalProvider() {
+        return new Object[][] { allConnectorsConfiguredNoneElapsed(), allConnectorsConfiguredOnlyResourceElapsed(),
+                allConnectorsConfiguredOnlySubjectElapsed(), connectorsNotConfigured(),
+                onlyResourceConnectorConfiguredAndElapsed(), onlySubjectConnectorConfiguredAndElapsed() };
+    }
+
+    private Object[] allConnectorsConfiguredNoneElapsed() {
+        AttributeConnector resourceConnector = new AttributeConnector();
+        AttributeConnector subjectConnector = new AttributeConnector();
+        resourceConnector.setMaxCachedIntervalMinutes(1);
+        subjectConnector.setMaxCachedIntervalMinutes(1);
+
+        return new Object[] { resourceConnector, subjectConnector, DateTime.now(), false };
+    }
+
+    private Object[] allConnectorsConfiguredOnlyResourceElapsed() {
+        AttributeConnector resourceConnector = new AttributeConnector();
+        AttributeConnector subjectConnector = new AttributeConnector();
+        resourceConnector.setMaxCachedIntervalMinutes(1);
+        subjectConnector.setMaxCachedIntervalMinutes(4);
+
+        return new Object[] { resourceConnector, subjectConnector, DateTime.now().minusMinutes(3), true };
+    }
+
+    private Object[] allConnectorsConfiguredOnlySubjectElapsed() {
+        AttributeConnector resourceConnector = new AttributeConnector();
+        AttributeConnector subjectConnector = new AttributeConnector();
+        resourceConnector.setMaxCachedIntervalMinutes(4);
+        subjectConnector.setMaxCachedIntervalMinutes(1);
+
+        return new Object[] { resourceConnector, subjectConnector, DateTime.now().minusMinutes(3), true };
+    }
+
+    private Object[] onlyResourceConnectorConfiguredAndElapsed() {
+        AttributeConnector resourceConnector = new AttributeConnector();
+        resourceConnector.setMaxCachedIntervalMinutes(1);
+
+        return new Object[] { resourceConnector, null, DateTime.now().minusMinutes(3), true };
+    }
+
+    private Object[] onlySubjectConnectorConfiguredAndElapsed() {
+        AttributeConnector subjectConnector = new AttributeConnector();
+        subjectConnector.setMaxCachedIntervalMinutes(1);
+
+        return new Object[] { null, subjectConnector, DateTime.now().minusMinutes(3), true };
+    }
+
+    private Object[] connectorsNotConfigured() {
+        return new Object[] { null, null, DateTime.now().minusMinutes(3), false };
+    }
+
+}
diff --git a/service/src/test/java/com/ge/predix/acs/policy/evaluation/cache/InMemoryPolicyEvaluationCacheTest.java b/service/src/test/java/com/ge/predix/acs/policy/evaluation/cache/InMemoryPolicyEvaluationCacheTest.java
new file mode 100644
index 0000000..4b2d71a
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/policy/evaluation/cache/InMemoryPolicyEvaluationCacheTest.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.policy.evaluation.cache;
+
+import static com.ge.predix.acs.testutils.XFiles.AGENT_MULDER;
+import static com.ge.predix.acs.testutils.XFiles.XFILES_ID;
+import static org.mockito.internal.util.reflection.Whitebox.getInternalState;
+import static org.testng.Assert.assertEquals;
+
+import java.util.Map;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.joda.time.DateTime;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+
+public class InMemoryPolicyEvaluationCacheTest {
+
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+    private static final String ZONE_NAME = "testzone1";
+    public static final String ACTION_GET = "GET";
+    private final InMemoryPolicyEvaluationCache cache = new InMemoryPolicyEvaluationCache();
+
+    @AfterMethod
+    public void cleanupTest() {
+        this.cache.reset();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSetPolicyEvalResult() throws Exception {
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationResult result = new PolicyEvaluationResult(Effect.PERMIT);
+        String value = OBJECT_MAPPER.writeValueAsString(result);
+        this.cache.set(key.toDecisionKey(), value);
+
+        Map<String, String> evalCache = (Map<String, String>) getInternalState(this.cache, "evalCache");
+        assertEquals(evalCache.size(), 1);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSetPolicySetChangedTimestamp() throws Exception {
+        String key = AbstractPolicyEvaluationCache.policySetKey(ZONE_NAME, "testSetPolicyPolicySetChangedTimestamp");
+        String value = OBJECT_MAPPER.writeValueAsString(new DateTime());
+        this.cache.set(key, value);
+
+        Map<String, String> evalCache = (Map<String, String>) getInternalState(this.cache, "evalCache");
+        assertEquals(evalCache.size(), 1);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSetPolicyResourceChangedTimestamp() throws Exception {
+        String key = AbstractPolicyEvaluationCache.resourceKey(ZONE_NAME, XFILES_ID);
+        String value = OBJECT_MAPPER.writeValueAsString(new DateTime());
+        this.cache.set(key, value);
+
+        Map<String, String> evalCache = (Map<String, String>) getInternalState(this.cache, "evalCache");
+        assertEquals(evalCache.size(), 1);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testSetPolicySubjectChangedTimestamp() throws Exception {
+        String key = AbstractPolicyEvaluationCache.subjectKey(ZONE_NAME, AGENT_MULDER);
+        String value = OBJECT_MAPPER.writeValueAsString(new DateTime());
+        this.cache.set(key, value);
+
+        Map<String, String> evalCache = (Map<String, String>) getInternalState(this.cache, "evalCache");
+        assertEquals(evalCache.size(), 1);
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void testSetUnsupportedKeyFormat() {
+        this.cache.set("key", "");
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/policy/evaluation/cache/PolicyEvaluationRequestCacheKeyTest.java b/service/src/test/java/com/ge/predix/acs/policy/evaluation/cache/PolicyEvaluationRequestCacheKeyTest.java
new file mode 100644
index 0000000..b07a362
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/policy/evaluation/cache/PolicyEvaluationRequestCacheKeyTest.java
@@ -0,0 +1,175 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.policy.evaluation.cache;
+
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import org.testng.annotations.Test;
+
+import java.util.LinkedHashSet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static com.ge.predix.acs.testutils.XFiles.AGENT_MULDER;
+import static com.ge.predix.acs.testutils.XFiles.XFILES_ID;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+public class PolicyEvaluationRequestCacheKeyTest {
+    public static final String ZONE_NAME = "testzone1";
+    public static final String ACTION_GET = "GET";
+
+    @Test
+    public void testBuild() {
+        String subjectId = AGENT_MULDER;
+        String resourceId = XFILES_ID;
+        LinkedHashSet<String> policyEvaluationOrder = Stream.of("policyOne")
+                .collect(Collectors.toCollection(LinkedHashSet::new));
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .resourceId(resourceId).subjectId(subjectId).policySetIds(policyEvaluationOrder).build();
+
+        assertEquals(key.getZoneId(), ZONE_NAME);
+        assertEquals(key.getSubjectId(), subjectId);
+        assertEquals(key.getResourceId(), resourceId);
+        assertEquals(key.getPolicySetIds(), policyEvaluationOrder);
+        assertNull(key.getRequest());
+    }
+
+    @Test
+    public void testBuildByRequest() {
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        request.setPolicySetsEvaluationOrder(
+                Stream.of("policyOne").collect(Collectors.toCollection(LinkedHashSet::new)));
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        assertEquals(key.getZoneId(), ZONE_NAME);
+        assertEquals(key.getSubjectId(), request.getSubjectIdentifier());
+        assertEquals(key.getResourceId(), request.getResourceIdentifier());
+        assertEquals(key.getPolicySetIds(), request.getPolicySetsEvaluationOrder());
+        assertEquals(key.getRequest(), request);
+    }
+
+    @Test
+    public void testBuildByRequestAndPolicySetEvaluationOrder() {
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        LinkedHashSet<String> policyEvaluationOrder = Stream.of("policyOne")
+                .collect(Collectors.toCollection(LinkedHashSet::new));
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .policySetIds(policyEvaluationOrder).request(request).build();
+
+        assertEquals(key.getZoneId(), ZONE_NAME);
+        assertEquals(key.getSubjectId(), request.getSubjectIdentifier());
+        assertEquals(key.getResourceId(), request.getResourceIdentifier());
+        assertEquals(key.getPolicySetIds(), policyEvaluationOrder);
+        assertEquals(key.getRequest(), request);
+    }
+
+    @SuppressWarnings("unused")
+    @Test(expectedExceptions = IllegalStateException.class)
+    public void testIllegalStateExceptionForSettingPolicySetIds() {
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        request.setPolicySetsEvaluationOrder(
+                Stream.of("policyOne").collect(Collectors.toCollection(LinkedHashSet::new)));
+        PolicyEvaluationRequestCacheKey policyEvaluationRequestCacheKey =
+            new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                                                         .request(request)
+                                                         .policySetIds(request.getPolicySetsEvaluationOrder())
+                                                         .build();
+    }
+
+    @SuppressWarnings("unused")
+    @Test(expectedExceptions = IllegalStateException.class)
+    public void testIllegalStateExceptionForSettingSubjectId() {
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        PolicyEvaluationRequestCacheKey policyEvaluationRequestCacheKey =
+            new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                                                         .request(request)
+                                                         .subjectId("subject")
+                                                         .build();
+    }
+
+    @SuppressWarnings("unused")
+    @Test(expectedExceptions = IllegalStateException.class)
+    public void testIllegalStateExceptionForSettingResourceId() {
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        PolicyEvaluationRequestCacheKey policyEvaluationRequestCacheKey =
+            new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                                                         .request(request)
+                                                         .resourceId("resource")
+                                                         .build();
+    }
+
+    @Test
+    public void testKeyEqualsForSameRequests() {
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        request.setPolicySetsEvaluationOrder(
+                Stream.of("policyOne").collect(Collectors.toCollection(LinkedHashSet::new)));
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationRequestV1 otherRequest = new PolicyEvaluationRequestV1();
+        otherRequest.setAction(ACTION_GET);
+        otherRequest.setSubjectIdentifier(AGENT_MULDER);
+        otherRequest.setResourceIdentifier(XFILES_ID);
+        otherRequest.setPolicySetsEvaluationOrder(
+                Stream.of("policyOne").collect(Collectors.toCollection(LinkedHashSet::new)));
+        PolicyEvaluationRequestCacheKey otherKey = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(otherRequest).build();
+        assertTrue(key.equals(otherKey));
+    }
+
+    @Test
+    public void testKeyEqualsForDifferentRequests() {
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        request.setAction(ACTION_GET);
+        request.setSubjectIdentifier(AGENT_MULDER);
+        request.setResourceIdentifier(XFILES_ID);
+        request.setPolicySetsEvaluationOrder(
+                Stream.of("policyOne").collect(Collectors.toCollection(LinkedHashSet::new)));
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+
+        PolicyEvaluationRequestV1 otherRequest = new PolicyEvaluationRequestV1();
+        otherRequest.setAction(ACTION_GET);
+        otherRequest.setSubjectIdentifier(AGENT_MULDER);
+        otherRequest.setResourceIdentifier(XFILES_ID);
+        PolicyEvaluationRequestCacheKey otherKey = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(otherRequest).build();
+        assertFalse(key.equals(otherKey));
+    }
+
+    @Test
+    public void testToRedisKey() {
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        PolicyEvaluationRequestCacheKey key = new PolicyEvaluationRequestCacheKey.Builder().zoneId(ZONE_NAME)
+                .request(request).build();
+        assertEquals(key.toDecisionKey(), ZONE_NAME + ":*:*:" + Integer.toHexString(request.hashCode()));
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/privilege/management/AttributesUtilities.java b/service/src/test/java/com/ge/predix/acs/privilege/management/AttributesUtilities.java
new file mode 100644
index 0000000..5b6551f
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/privilege/management/AttributesUtilities.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.ge.predix.acs.model.Attribute;
+
+/**
+ * @author acs-engineers@ge.com
+ */
+public class AttributesUtilities {
+
+    /**
+     * Converts a list of attributes into a Set.
+     *
+     * @param attrs
+     *            dynamic list of attributes
+     * @return a set of the attributes
+     */
+    public Set<Attribute> getSetOfAttributes(final Attribute... attrs) {
+        Set<Attribute> attributes = new HashSet<>();
+        attributes.addAll(Arrays.asList(attrs));
+        return attributes;
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/privilege/management/BeanUtilTests.java b/service/src/test/java/com/ge/predix/acs/privilege/management/BeanUtilTests.java
new file mode 100644
index 0000000..bcf7d09
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/privilege/management/BeanUtilTests.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.utils.JsonUtils;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+public class BeanUtilTests {
+
+    @Test
+    public void test() {
+        JsonUtils ju = new JsonUtils();
+        Attribute a1 = new Attribute("acs", "role", "admin");
+        Set<Attribute> s = new HashSet<>();
+        s.add(a1);
+        String serialize = ju.serialize(s);
+        System.out.println(serialize);
+
+        @SuppressWarnings({ "unchecked" })
+        Set<Attribute> deserialize = ju.deserialize(serialize, Set.class, Attribute.class);
+        Iterator<Attribute> iterator = deserialize.iterator();
+        Attribute next = iterator.next();
+        System.out.println(next);
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/privilege/management/PrivilegeManagementNoRollbackTest.java b/service/src/test/java/com/ge/predix/acs/privilege/management/PrivilegeManagementNoRollbackTest.java
new file mode 100644
index 0000000..d553858
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/privilege/management/PrivilegeManagementNoRollbackTest.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.SpringSecurityPolicyContextResolver;
+import com.ge.predix.acs.attribute.cache.AttributeCacheFactory;
+import com.ge.predix.acs.attribute.connector.management.AttributeConnectorServiceImpl;
+import com.ge.predix.acs.attribute.readers.AttributeReaderFactory;
+import com.ge.predix.acs.attribute.readers.PrivilegeServiceResourceAttributeReader;
+import com.ge.predix.acs.attribute.readers.PrivilegeServiceSubjectAttributeReader;
+import com.ge.predix.acs.config.GraphBeanDefinitionRegistryPostProcessor;
+import com.ge.predix.acs.config.GraphConfig;
+import com.ge.predix.acs.config.InMemoryDataSourceConfig;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.policy.evaluation.cache.InMemoryPolicyEvaluationCache;
+import com.ge.predix.acs.privilege.management.dao.ResourceRepositoryProxy;
+import com.ge.predix.acs.privilege.management.dao.SubjectRepositoryProxy;
+import com.ge.predix.acs.request.context.AcsRequestContextHolder;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.testutils.TestUtils;
+import com.ge.predix.acs.zone.management.ZoneService;
+import com.ge.predix.acs.zone.management.ZoneServiceImpl;
+import com.ge.predix.acs.zone.resolver.SpringSecurityZoneResolver;
+
+@ContextConfiguration(classes = { AcsRequestContextHolder.class, InMemoryDataSourceConfig.class,
+        InMemoryPolicyEvaluationCache.class, AttributeCacheFactory.class, PrivilegeManagementServiceImpl.class,
+        GraphBeanDefinitionRegistryPostProcessor.class, GraphConfig.class, ZoneServiceImpl.class,
+        SpringSecurityPolicyContextResolver.class, SpringSecurityZoneResolver.class, SubjectRepositoryProxy.class,
+        ResourceRepositoryProxy.class, AttributeConnectorServiceImpl.class, AttributeReaderFactory.class,
+        PrivilegeServiceResourceAttributeReader.class, PrivilegeServiceSubjectAttributeReader.class })
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+@TestPropertySource("classpath:application.properties")
+@Test
+public class PrivilegeManagementNoRollbackTest extends AbstractTestNGSpringContextTests {
+
+    @Autowired
+    private PrivilegeManagementService service;
+
+    @Autowired
+    private ZoneService zoneService;
+
+    private final AttributesUtilities attributesUtilities = new AttributesUtilities();
+    private Set<Attribute> fixedAttributes;
+    private final TestUtils testUtils = new TestUtils();
+    private Zone testZone;
+
+    @BeforeClass
+    public void setup() throws InterruptedException, ExecutionException {
+        this.testZone = this.testUtils.setupTestZone("PrivilegeManagementNoRollbackTest", this.zoneService);
+        this.fixedAttributes = this.attributesUtilities.getSetOfAttributes(new Attribute("acs", "group", "admin"));
+    }
+
+    @AfterClass
+    public void cleanup() {
+        this.zoneService.deleteZone(this.testZone.getName());
+    }
+
+    public void testCreateMultipleSubjectWithConstraintViolationSubjectIdentifier() {
+        String subjectIdentifier = "Dave-ID123";
+
+        List<BaseSubject> subjects = new ArrayList<>();
+        subjects.add(createSubject(subjectIdentifier));
+        subjects.add(createSubject(subjectIdentifier));
+        try {
+            this.service.appendSubjects(subjects);
+        } catch (PrivilegeManagementException e) {
+            // not checking id in toString(), just validating rest of error
+            // message due to id mismatch on CI
+            boolean checkMessage = (e.getMessage().contains("Unable to persist Subject(s) for zone") || (e.getMessage()
+                    .contains("Duplicate Subject(s)")));
+            Assert.assertTrue(checkMessage, "Invalid Error Message: " + e.getMessage());
+            Assert.assertEquals(this.service.getSubjects().size(), 0);
+            return;
+        }
+
+        Assert.fail("Expected PrivilegeManagementException to be thrown.");
+    }
+
+    public void testCreateMultipleResourceWithConstraintViolationResourceIdentifier() {
+
+        List<BaseResource> resourceList = new ArrayList<>();
+
+        String resourceIdentifier = "Brittany123";
+
+        resourceList.add(createResource(resourceIdentifier));
+        resourceList.add(createResource(resourceIdentifier));
+
+        try {
+            this.service.appendResources(resourceList);
+
+        } catch (PrivilegeManagementException e) {
+            boolean checkMessage = (e.getMessage().contains("Unable to persist Resource(s) for zone") || (e.getMessage()
+                    .contains("Duplicate Resource(s)")));
+            Assert.assertTrue(checkMessage, "Invalid Error Message: " + e.getMessage());
+            Assert.assertEquals(this.service.getResources().size(), 0);
+            return;
+        }
+        Assert.fail("Expected PrivilegeManagementException to be thrown.");
+    }
+
+    private BaseSubject createSubject(final String subjectIdentifier) {
+        BaseSubject subject = new BaseSubject();
+        subject.setSubjectIdentifier(subjectIdentifier);
+        subject.setAttributes(this.fixedAttributes);
+        return subject;
+    }
+
+    private BaseResource createResource(final String resourceIdentifier) {
+        BaseResource resource = new BaseResource();
+        resource.setResourceIdentifier(resourceIdentifier);
+        resource.setAttributes(this.fixedAttributes);
+        return resource;
+    }
+
+}
diff --git a/service/src/test/java/com/ge/predix/acs/privilege/management/PrivilegeManagementServiceImplTest.java b/service/src/test/java/com/ge/predix/acs/privilege/management/PrivilegeManagementServiceImplTest.java
new file mode 100644
index 0000000..bcbe081
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/privilege/management/PrivilegeManagementServiceImplTest.java
@@ -0,0 +1,381 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+import static java.util.Arrays.asList;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.SpringSecurityPolicyContextResolver;
+import com.ge.predix.acs.attribute.cache.AttributeCacheFactory;
+import com.ge.predix.acs.attribute.connector.management.AttributeConnectorServiceImpl;
+import com.ge.predix.acs.attribute.readers.AttributeReaderFactory;
+import com.ge.predix.acs.attribute.readers.PrivilegeServiceResourceAttributeReader;
+import com.ge.predix.acs.attribute.readers.PrivilegeServiceSubjectAttributeReader;
+import com.ge.predix.acs.config.GraphBeanDefinitionRegistryPostProcessor;
+import com.ge.predix.acs.config.GraphConfig;
+import com.ge.predix.acs.config.InMemoryDataSourceConfig;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.policy.evaluation.cache.InMemoryPolicyEvaluationCache;
+import com.ge.predix.acs.privilege.management.dao.ResourceRepositoryProxy;
+import com.ge.predix.acs.privilege.management.dao.SubjectRepositoryProxy;
+import com.ge.predix.acs.request.context.AcsRequestContextHolder;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.Parent;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.testutils.MockSecurityContext;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.testutils.TestUtils;
+import com.ge.predix.acs.zone.management.ZoneService;
+import com.ge.predix.acs.zone.management.ZoneServiceImpl;
+import com.ge.predix.acs.zone.resolver.SpringSecurityZoneResolver;
+
+@ContextConfiguration(classes = { AcsRequestContextHolder.class, InMemoryDataSourceConfig.class,
+        InMemoryPolicyEvaluationCache.class, AttributeCacheFactory.class, PrivilegeManagementServiceImpl.class,
+        SpringSecurityPolicyContextResolver.class, SpringSecurityZoneResolver.class, ZoneServiceImpl.class,
+        GraphBeanDefinitionRegistryPostProcessor.class, GraphConfig.class, SubjectRepositoryProxy.class,
+        ResourceRepositoryProxy.class, AttributeConnectorServiceImpl.class, AttributeReaderFactory.class,
+        PrivilegeServiceResourceAttributeReader.class, PrivilegeServiceSubjectAttributeReader.class })
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+@TestPropertySource("classpath:application.properties")
+public class PrivilegeManagementServiceImplTest extends AbstractTransactionalTestNGSpringContextTests {
+
+    @Autowired
+    private PrivilegeManagementService service;
+
+    private final AttributesUtilities attributesUtilities = new AttributesUtilities();
+
+    private Set<Attribute> fixedAttributes;
+
+    @Autowired
+    private ZoneService zoneService;
+
+    private final TestUtils testUtils = new TestUtils();
+    private Zone testZone;
+
+    @BeforeClass
+    public void beforeClass() {
+        this.testZone = this.testUtils.setupTestZone("PrivilegeManagementServiceImplTest", this.zoneService);
+    }
+
+    @AfterClass
+    public void cleanupAfterClass() {
+        this.zoneService.deleteZone(this.testZone.getName());
+    }
+
+    @BeforeMethod
+    public void setup() {
+        this.fixedAttributes = this.attributesUtilities.getSetOfAttributes(new Attribute("acs", "group", "admin"));
+    }
+
+    @Test
+    public void testAppendResources() {
+        doAppendResourcesAndAssert("/asset/sanramon", "/asset/ny");
+    }
+
+    @Test(dataProvider = "emptyIdDataProvider", expectedExceptions = PrivilegeManagementException.class)
+    public void testAppendResourceWithEmptyResourceId(final String resourceIdentifier) {
+        BaseResource resource = createResource(resourceIdentifier);
+        this.service.appendResources(asList(resource));
+    }
+
+    @DataProvider(name = "emptyIdDataProvider")
+    private Object[][] emptyIdDataProvider() {
+        return new Object[][] {
+
+                { "" }, { null }, };
+    }
+
+    @Test(expectedExceptions = PrivilegeManagementException.class)
+    public void testAppendNullResources() {
+        this.service.appendResources(null);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test(expectedExceptions = PrivilegeManagementException.class)
+    public void testAppendEmptyResources() {
+        this.service.appendResources(Collections.EMPTY_LIST);
+    }
+
+    @Test
+    public void testCreateResource() {
+        doCreateResourceAndAssert("/asset/sanrafael");
+    }
+
+    @Test(expectedExceptions = PrivilegeManagementException.class)
+    public void testCreateNullResources() {
+        this.service.upsertResource(null);
+    }
+
+    @Test
+    public void testUpdateResource() {
+        String resourceIdentifier = "/asset/sananselmo";
+        BaseResource resource = doCreateResourceAndAssert(resourceIdentifier);
+
+        resource.getAttributes().add(new Attribute("acs", "group", "analyst"));
+
+        boolean created = this.service.upsertResource(resource);
+        Assert.assertFalse(created);
+        BaseResource savedResource = this.service.getByResourceIdentifier(resource.getResourceIdentifier());
+        assertResource(savedResource, resource, 2);
+    }
+
+    @Test
+    public void testDeleteResource() {
+        String resourceIdentifier = "/asset/santarita";
+        BaseResource resource = doCreateResourceAndAssert(resourceIdentifier);
+        String id = resource.getResourceIdentifier();
+
+        boolean deleted = this.service.deleteResource(id);
+        Assert.assertTrue(deleted);
+        Assert.assertNull(this.service.getByResourceIdentifier(id));
+    }
+
+    @Test
+    public void testDeleteInvalidResource() {
+        Assert.assertFalse(this.service.deleteResource("invalid_id"));
+    }
+
+    @Test
+    public void testAppendSubjects() {
+        try {
+            BaseSubject s1 = createSubject("dave", this.fixedAttributes);
+            BaseSubject s2 = createSubject("sanjeev", this.fixedAttributes);
+
+            this.service.appendSubjects(asList(s1, s2));
+
+            // able to get subject by identifier
+            Assert.assertNotNull(this.service.getBySubjectIdentifier(s1.getSubjectIdentifier()));
+            Assert.assertNotNull(this.service.getBySubjectIdentifier(s2.getSubjectIdentifier()));
+        } finally {
+            this.service.deleteSubject("dave");
+            this.service.deleteSubject("sanjeev");
+        }
+    }
+
+    @Test(expectedExceptions = PrivilegeManagementException.class)
+    public void testAppendNullSubjects() {
+        this.service.appendSubjects(null);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test(expectedExceptions = PrivilegeManagementException.class)
+    public void testAppendEmptySubjects() {
+        this.service.appendSubjects(Collections.EMPTY_LIST);
+    }
+
+    @Test
+    public void testCreateSubject() {
+        String subjectIdentifier = "marissa";
+        try {
+            BaseSubject subject = createSubject(subjectIdentifier, this.fixedAttributes);
+
+            boolean created = this.service.upsertSubject(subject);
+            Assert.assertTrue(created);
+            Assert.assertTrue(this.service.getBySubjectIdentifier(subjectIdentifier).equals(subject));
+        } finally {
+            this.service.deleteSubject("marissa");
+        }
+    }
+
+    @Test
+    public void testCreateSubjectWithParent() {
+        String testSubjectId = "marissa";
+        final String parentSubjectId = "bob";
+        try {
+            BaseSubject marissa = createSubject(testSubjectId, this.fixedAttributes);
+
+            BaseSubject bob = createSubject(parentSubjectId,
+                    this.attributesUtilities.getSetOfAttributes(new Attribute("acs", "group", "parent")));
+            this.service.upsertSubject(bob);
+            marissa.setParents(new HashSet<>(Arrays.asList(new Parent(parentSubjectId))));
+            this.service.upsertSubject(marissa);
+
+            Assert.assertEquals(this.service.getBySubjectIdentifier(testSubjectId), marissa);
+            Assert.assertEquals(this.service.getBySubjectIdentifier(testSubjectId).getAttributes(),
+                    marissa.getAttributes());
+        } finally {
+            this.service.deleteSubject(testSubjectId);
+            this.service.deleteSubject(parentSubjectId);
+        }
+    }
+
+    @Test(expectedExceptions = PrivilegeManagementException.class)
+    public void testCreateNullSubject() {
+        this.service.upsertSubject(null);
+    }
+
+    // TODO enable it back when the zone resolver is fully implemented
+    @Test(expectedExceptions = SecurityException.class, enabled = false)
+    public void testCreateSubjectAndGetWithDifferentClientId() {
+        String subjectIdentifier = "Dave-ID123";
+        BaseSubject subject = createSubject(subjectIdentifier, this.fixedAttributes);
+
+        boolean created = this.service.upsertSubject(subject);
+        Assert.assertTrue(created);
+        Assert.assertTrue(this.service.getBySubjectIdentifier(subjectIdentifier).equals(subject));
+
+        MockSecurityContext.mockSecurityContext(this.testZone);
+        BaseSubject returnedSubject = this.service.getBySubjectIdentifier(subjectIdentifier);
+        Assert.assertNull(returnedSubject);
+    }
+
+    // TODO enable it back when the zone resolver is fully implemented
+    @Test(expectedExceptions = SecurityException.class, enabled = false)
+    public void testCreateResourceAndGetWithDifferentClientId() {
+        String resourceIdentifier = "Dave-ID123";
+        BaseResource resource = createResource(resourceIdentifier);
+
+        boolean created = this.service.upsertResource(resource);
+        Assert.assertTrue(created);
+        Assert.assertTrue(this.service.getByResourceIdentifier(resourceIdentifier).equals(resource));
+
+        MockSecurityContext.mockSecurityContext(this.testZone);
+        BaseResource returnedResource = this.service.getByResourceIdentifier(resourceIdentifier);
+        Assert.assertNull(returnedResource);
+    }
+
+    /*
+     * TODO Need this test when we start supporting multiple issuers
+     *
+     * public void testCreateSubjectAndGetWithDifferentIssuerId(){ String subjectIdentifier = "Dave-ID123"; Subject
+     * subject = createSubject(subjectIdentifier); boolean created = this.service.upsertSubject(subject);
+     * Assert.assertTrue(created);
+     *
+     * PolicyContextResloverUtilTest scope = new PolicyContextResloverUtilTest();
+     * scope.mockSecurityContext("ISSUER_1234", CLIENT_ID); }
+     */
+
+    @Test
+    public void testUpdateSubject() {
+        String subjectIdentifier = "/asset/sananselmo";
+        BaseSubject subject = createSubject(subjectIdentifier, this.fixedAttributes);
+
+        boolean created = this.service.upsertSubject(subject);
+        Assert.assertTrue(created);
+        Assert.assertTrue(this.service.getBySubjectIdentifier(subjectIdentifier).equals(subject));
+
+        subject.getAttributes().add(new Attribute("acs", "group", "analyst"));
+
+        created = this.service.upsertSubject(subject);
+        Assert.assertFalse(created);
+
+        BaseSubject savedSubject = this.service.getBySubjectIdentifier(subject.getSubjectIdentifier());
+        assertSubject(savedSubject, subject, 2);
+    }
+
+    @Test
+    public void testDeleteSubject() {
+        String subjectIdentifier = "/asset/santarita";
+        BaseSubject subject = createSubject(subjectIdentifier, this.fixedAttributes);
+        this.service.upsertSubject(subject);
+
+        boolean deleted = this.service.deleteSubject(subjectIdentifier);
+        Assert.assertTrue(deleted);
+        Assert.assertNull(this.service.getBySubjectIdentifier(subjectIdentifier));
+    }
+
+    @Test
+    public void testDeleteInvalidSubject() {
+        Assert.assertFalse(this.service.deleteSubject("invalid_id"));
+    }
+
+    @Test
+    public void testGetSubjects() {
+        BaseSubject r1 = createSubject("/asset/macfarland", this.fixedAttributes);
+        BaseSubject r2 = createSubject("/asset/oregon", this.fixedAttributes);
+
+        this.service.appendSubjects(asList(r1, r2));
+        List<BaseSubject> subjects = this.service.getSubjects();
+        Assert.assertEquals(subjects.size(), 2);
+
+    }
+
+    private BaseSubject createSubject(final String subjectIdentifier, final Set<Attribute> attributes) {
+        BaseSubject subject = new BaseSubject();
+        subject.setSubjectIdentifier(subjectIdentifier);
+        subject.setAttributes(attributes);
+        return subject;
+    }
+
+    private BaseResource createResource(final String resourceIdentifier) {
+        BaseResource resource = new BaseResource();
+        resource.setResourceIdentifier(resourceIdentifier);
+        resource.setAttributes(this.fixedAttributes);
+        return resource;
+    }
+
+    private void assertResource(final BaseResource savedResource, final BaseResource resource,
+            final int numOfAttributes) {
+        Assert.assertNotNull(savedResource);
+        Assert.assertEquals(savedResource.getResourceIdentifier(), resource.getResourceIdentifier());
+        Assert.assertEquals(savedResource.getAttributes().size(), numOfAttributes);
+    }
+
+    private void assertSubject(final BaseSubject savedSubject, final BaseSubject subject, final int numOfAttributes) {
+        Assert.assertNotNull(savedSubject);
+        Assert.assertEquals(savedSubject.getSubjectIdentifier(), subject.getSubjectIdentifier());
+        Assert.assertEquals(savedSubject.getAttributes().size(), numOfAttributes);
+    }
+
+    public void doAppendResourcesAndAssert(final String identifier1, final String identifier2) {
+        BaseResource r1 = createResource(identifier1);
+        BaseResource r2 = createResource(identifier2);
+
+        this.service.appendResources(asList(r1, r2));
+
+        BaseResource fetchedResource1 = this.service.getByResourceIdentifier(r1.getResourceIdentifier());
+        BaseResource fetchedResource2 = this.service.getByResourceIdentifier(r2.getResourceIdentifier());
+
+        List<BaseResource> resources = this.service.getResources();
+        Assert.assertTrue(resources.size() >= 2);
+
+        Assert.assertEquals(fetchedResource1, r1);
+        Assert.assertEquals(fetchedResource2, r2);
+
+    }
+
+    private BaseResource doCreateResourceAndAssert(final String resourceIdentifier) {
+        BaseResource resource = createResource(resourceIdentifier);
+
+        boolean created = this.service.upsertResource(resource);
+        Assert.assertTrue(created);
+
+        Assert.assertTrue(this.service.getByResourceIdentifier(resourceIdentifier).equals(resource));
+        return resource;
+    }
+
+}
diff --git a/service/src/test/java/com/ge/predix/acs/privilege/management/ResourceHttpMethodsFilterTest.java b/service/src/test/java/com/ge/predix/acs/privilege/management/ResourceHttpMethodsFilterTest.java
new file mode 100644
index 0000000..bca65bb
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/privilege/management/ResourceHttpMethodsFilterTest.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mockito.InjectMocks;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpMethod;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public final class ResourceHttpMethodsFilterTest {
+
+    @InjectMocks
+    private ResourcePrivilegeManagementController resourcePrivilegeManagementController;
+
+    private static final Set<HttpMethod> ALL_HTTP_METHODS = new HashSet<>(Arrays.asList(HttpMethod.values()));
+
+    private MockMvc mockMvc;
+
+    @BeforeClass
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        this.mockMvc = MockMvcBuilders.standaloneSetup(this.resourcePrivilegeManagementController)
+                .addFilters(new ResourceHttpMethodsFilter()).build();
+    }
+
+    @Test(dataProvider = "urisAndTheirAllowedHttpMethods")
+    public void testUriPatternsAndTheirAllowedHttpMethods(final String uri, final Set<HttpMethod> allowedHttpMethods)
+            throws Exception {
+        Set<HttpMethod> disallowedHttpMethods = new HashSet<>(ALL_HTTP_METHODS);
+        disallowedHttpMethods.removeAll(allowedHttpMethods);
+        for (HttpMethod disallowedHttpMethod : disallowedHttpMethods) {
+            this.mockMvc.perform(MockMvcRequestBuilders.request(disallowedHttpMethod, URI.create(uri)))
+                    .andExpect(MockMvcResultMatchers.status().isMethodNotAllowed());
+        }
+    }
+
+    @DataProvider
+    public Object[][] urisAndTheirAllowedHttpMethods() {
+        return new Object[][] {
+                new Object[] { "/v1/resource/foo",
+                        new HashSet<>(Arrays.asList(HttpMethod.PUT, HttpMethod.GET, HttpMethod.DELETE, HttpMethod.HEAD,
+                                HttpMethod.OPTIONS)) },
+                { "/v1/resource", new HashSet<>(
+                        Arrays.asList(HttpMethod.POST, HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS)) } };
+    }
+}
\ No newline at end of file
diff --git a/service/src/test/java/com/ge/predix/acs/privilege/management/SubjectHttpMethodsFilterTest.java b/service/src/test/java/com/ge/predix/acs/privilege/management/SubjectHttpMethodsFilterTest.java
new file mode 100644
index 0000000..77228dd
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/privilege/management/SubjectHttpMethodsFilterTest.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mockito.InjectMocks;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpMethod;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public final class SubjectHttpMethodsFilterTest {
+
+    @InjectMocks
+    private SubjectPrivilegeManagementController subjectPrivilegeManagementController;
+
+    private static final Set<HttpMethod> ALL_HTTP_METHODS = new HashSet<>(Arrays.asList(HttpMethod.values()));
+
+    private MockMvc mockMvc;
+
+    @BeforeClass
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        this.mockMvc = MockMvcBuilders.standaloneSetup(this.subjectPrivilegeManagementController)
+                .addFilters(new SubjectHttpMethodsFilter()).build();
+    }
+
+    @Test(dataProvider = "urisAndTheirAllowedHttpMethods")
+    public void testUriPatternsAndTheirAllowedHttpMethods(final String uri, final Set<HttpMethod> allowedHttpMethods)
+            throws Exception {
+        Set<HttpMethod> disallowedHttpMethods = new HashSet<>(ALL_HTTP_METHODS);
+        disallowedHttpMethods.removeAll(allowedHttpMethods);
+        for (HttpMethod disallowedHttpMethod : disallowedHttpMethods) {
+            this.mockMvc.perform(MockMvcRequestBuilders.request(disallowedHttpMethod, URI.create(uri)))
+                    .andExpect(MockMvcResultMatchers.status().isMethodNotAllowed());
+        }
+    }
+
+    @DataProvider
+    public Object[][] urisAndTheirAllowedHttpMethods() {
+        return new Object[][] {
+                new Object[] { "/v1/subject/foo",
+                        new HashSet<>(Arrays.asList(HttpMethod.PUT, HttpMethod.GET, HttpMethod.DELETE, HttpMethod.HEAD,
+                                HttpMethod.OPTIONS)) },
+                { "/v1/subject", new HashSet<>(
+                        Arrays.asList(HttpMethod.POST, HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS)) } };
+    }
+}
\ No newline at end of file
diff --git a/service/src/test/java/com/ge/predix/acs/privilege/management/dao/GraphResourceRepositoryTest.java b/service/src/test/java/com/ge/predix/acs/privilege/management/dao/GraphResourceRepositoryTest.java
new file mode 100644
index 0000000..2dbc966
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/privilege/management/dao/GraphResourceRepositoryTest.java
@@ -0,0 +1,688 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import static com.ge.predix.acs.privilege.management.dao.GraphResourceRepository.RESOURCE_ID_KEY;
+import static com.ge.predix.acs.testutils.XFiles.ASCENSION_ATTRIBUTES;
+import static com.ge.predix.acs.testutils.XFiles.ASCENSION_ID;
+import static com.ge.predix.acs.testutils.XFiles.BASEMENT_ATTRIBUTES;
+import static com.ge.predix.acs.testutils.XFiles.BASEMENT_SITE_ID;
+import static com.ge.predix.acs.testutils.XFiles.DRIVE_ATTRIBUTES;
+import static com.ge.predix.acs.testutils.XFiles.DRIVE_ID;
+import static com.ge.predix.acs.testutils.XFiles.EVIDENCE_IMPLANT_ATTRIBUTES;
+import static com.ge.predix.acs.testutils.XFiles.EVIDENCE_IMPLANT_ID;
+import static com.ge.predix.acs.testutils.XFiles.EVIDENCE_SCULLYS_TESTIMONY_ID;
+import static com.ge.predix.acs.testutils.XFiles.JOSECHUNG_ID;
+import static com.ge.predix.acs.testutils.XFiles.SCULLYS_TESTIMONY_ATTRIBUTES;
+import static com.ge.predix.acs.testutils.XFiles.SITE_BASEMENT;
+import static com.ge.predix.acs.testutils.XFiles.TOP_SECRET_CLASSIFICATION;
+import static com.ge.predix.acs.testutils.XFiles.TYPE_MONSTER_OF_THE_WEEK;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.nullValue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
+import org.apache.commons.lang.time.StopWatch;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.config.GraphConfig;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.Parent;
+import com.ge.predix.acs.utils.JsonUtils;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.thinkaurelius.titan.core.SchemaViolationException;
+import com.thinkaurelius.titan.core.TitanException;
+import com.thinkaurelius.titan.core.TitanFactory;
+
+public class GraphResourceRepositoryTest {
+    private static final JsonUtils JSON_UTILS = new JsonUtils();
+    private static final ZoneEntity TEST_ZONE_1 = new ZoneEntity(1L, "testzone1");
+    private static final ZoneEntity TEST_ZONE_2 = new ZoneEntity(2L, "testzone2");
+    private static final int CONCURRENT_TEST_THREAD_COUNT = 3;
+    private static final int CONCURRENT_TEST_INVOCATIONS = 20;
+
+    private GraphResourceRepository resourceRepository;
+    private GraphTraversalSource graphTraversalSource;
+    private Random randomGenerator = new Random();
+
+    @BeforeClass
+    public void setup() throws Exception {
+        this.resourceRepository = new GraphResourceRepository();
+        setupTitanGraph();
+        this.resourceRepository.setGraphTraversal(this.graphTraversalSource);
+    }
+
+    @AfterClass
+    public void teardown() {
+        this.dropAllResources();
+    }
+
+    private void setupTitanGraph() throws InterruptedException, ExecutionException {
+        Graph graph = TitanFactory.build().set("storage.backend", "inmemory").open();
+        GraphConfig.createSchemaElements(graph);
+        this.graphTraversalSource = graph.traversal();
+        this.dropAllResources();
+    }
+
+    private void dropAllResources() {
+        this.graphTraversalSource.V().drop().iterate();
+    }
+
+    @Test
+    public void testCount() {
+        dropAllResources();
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(0));
+
+        persistRandomResourcetoZone1AndAssert();
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(1));
+
+        persistResource2toZone1AndAssert();
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(2));
+        assertThat(this.resourceRepository.count(), equalTo(2L));
+        dropAllResources();
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testDelete() {
+        ResourceEntity resourceEntity = persistRandomResourcetoZone1AndAssert();
+        long id = resourceEntity.getId();
+
+        this.resourceRepository.delete(id);
+        assertThat(this.resourceRepository.findOne(id), nullValue());
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testDeleteMultiple() {
+        List<ResourceEntity> resourceEntities = new ArrayList<>();
+
+        ResourceEntity resourceEntity1 = persistRandomResourcetoZone1AndAssert();
+        Long resourceId1 = resourceEntity1.getId();
+        resourceEntities.add(resourceEntity1);
+
+        ResourceEntity resourceEntity2 = persistRandomResourcetoZone1AndAssert();
+        Long resourceId2 = resourceEntity2.getId();
+        resourceEntities.add(resourceEntity2);
+
+        this.resourceRepository.delete(resourceEntities);
+
+        assertThat(this.resourceRepository.findOne(resourceId1), nullValue());
+        assertThat(this.resourceRepository.findOne(resourceId2), nullValue());
+    }
+
+    @Test
+    public void testDeleteAll() {
+        dropAllResources();
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(0));
+        Long resourceId1 = persistRandomResourcetoZone1AndAssert().getId();
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(1));
+        Long resourceId2 = persistResource2toZone1AndAssert().getId();
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(2));
+        this.resourceRepository.deleteAll();
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(0));
+        assertThat(this.resourceRepository.findOne(resourceId1), nullValue());
+        assertThat(this.resourceRepository.findOne(resourceId2), nullValue());
+        dropAllResources();
+    }
+
+    @Test
+    public void testFindAll() {
+        dropAllResources();
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(0));
+        ResourceEntity resourceEntity1 = persistRandomResourcetoZone1AndAssert();
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(1));
+        ResourceEntity resourceEntity2 = persistResource2toZone1AndAssert();
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(2));
+
+        List<ResourceEntity> resources = this.resourceRepository.findAll();
+        assertThat(resources.size(), equalTo(2));
+        assertThat(resources, hasItems(resourceEntity1, resourceEntity2));
+        dropAllResources();
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testFindByZone() {
+        ResourceEntity resourceEntity1 = persistRandomResourcetoZone1AndAssert();
+        ResourceEntity resourceEntity2 = persistRandomResourcetoZone1AndAssert();
+
+        List<ResourceEntity> resources = this.resourceRepository.findByZone(TEST_ZONE_1);
+        assertThat(resources, hasItems(resourceEntity1, resourceEntity2));
+        this.resourceRepository.delete(resourceEntity1);
+        this.resourceRepository.delete(resourceEntity2);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testGetByZoneAndResourceIdentifier() {
+        ResourceEntity resourceEntity1 = persist2LevelRandomResourcetoZone1();
+
+        ResourceEntity resource = this.resourceRepository
+                .getByZoneAndResourceIdentifier(TEST_ZONE_1, resourceEntity1.getResourceIdentifier());
+        assertThat(resource, equalTo(resourceEntity1));
+        assertThat(resource.getAttributes().contains(TYPE_MONSTER_OF_THE_WEEK), equalTo(true));
+
+        //Check that the result does not contain inherited attribute. use getResourceWithInheritedAttributes inherit
+        //attributes
+        assertThat(resource.getAttributes().contains(SITE_BASEMENT), equalTo(false));
+        deleteTwoLevelEntityAndParents(resourceEntity1, TEST_ZONE_1, this.resourceRepository);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testGetResourceWithInheritedAttributesWithInheritedAttributes() {
+        ResourceEntity resourceEntity1 = persist2LevelRandomResourcetoZone1();
+
+        ResourceEntity resource = this.resourceRepository
+                .getResourceWithInheritedAttributes(TEST_ZONE_1, resourceEntity1.getResourceIdentifier());
+        assertThat(resource.getZone().getName(), equalTo(resourceEntity1.getZone().getName()));
+        assertThat(resource.getResourceIdentifier(), equalTo(resourceEntity1.getResourceIdentifier()));
+        assertThat(resource.getAttributes().contains(TYPE_MONSTER_OF_THE_WEEK), equalTo(true));
+        // Check that the result contains inherited attribute
+        assertThat(resource.getAttributes().contains(SITE_BASEMENT), equalTo(true));
+        deleteTwoLevelEntityAndParents(resourceEntity1, TEST_ZONE_1, this.resourceRepository);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testGetByZoneAndResourceIdentifierWithEmptyAttributes() {
+
+        ResourceEntity persistedResourceEntity = persistResourceToZoneAndAssert(TEST_ZONE_1,
+                DRIVE_ID + getRandomNumber(), Collections.emptySet());
+
+        ResourceEntity resource = this.resourceRepository
+                .getByZoneAndResourceIdentifier(TEST_ZONE_1, persistedResourceEntity.getResourceIdentifier());
+        assertThat(resource, equalTo(persistedResourceEntity));
+        this.resourceRepository.delete(persistedResourceEntity);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testGetByZoneAndResourceIdentifierWithNullAttributes() {
+        ResourceEntity persistedResourceEntity = persistResourceToZoneAndAssert(TEST_ZONE_1,
+                DRIVE_ID + getRandomNumber(), null);
+        ResourceEntity resource = this.resourceRepository
+                .getByZoneAndResourceIdentifier(TEST_ZONE_1, persistedResourceEntity.getResourceIdentifier());
+        assertThat(resource, equalTo(persistedResourceEntity));
+        this.resourceRepository.delete(persistedResourceEntity);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testGetByZoneAndResourceIdentifierWithInheritedAttributes3LevelHierarchical() {
+        ResourceEntity expectedResource = persist3LevelRandomResourcetoZone1();
+
+        ResourceEntity actualResource = this.resourceRepository
+                .getResourceWithInheritedAttributes(TEST_ZONE_1, expectedResource.getResourceIdentifier());
+        assertThat(actualResource.getAttributes().contains(SITE_BASEMENT), equalTo(true));
+        assertThat(actualResource.getAttributes().contains(TYPE_MONSTER_OF_THE_WEEK), equalTo(true));
+        assertThat(actualResource.getAttributes().contains(TOP_SECRET_CLASSIFICATION), equalTo(true));
+        deleteThreeLevelEntityAndParents(expectedResource, TEST_ZONE_1, this.resourceRepository);
+    }
+
+    @Test(expectedExceptions = SchemaViolationException.class)
+    public void testPreventEntityParentSelfReference() {
+        ResourceEntity resource = new ResourceEntity(TEST_ZONE_1, BASEMENT_SITE_ID);
+        resource.setAttributes(BASEMENT_ATTRIBUTES);
+        resource.setAttributesAsJson(JSON_UTILS.serialize(resource.getAttributes()));
+        resource.setParents(new HashSet<>(Collections.singletonList(new Parent(BASEMENT_SITE_ID))));
+        this.resourceRepository.save(resource);
+    }
+
+    /**
+     * First we setup a 3 level graph where 3 -> 2 -> 1. Next, we try to update vertex 1 so that 1 -> 3. We expect
+     * this to result in a SchemaViolationException because it would introduce a cyclic reference.
+     */
+    @Test
+    public void testPreventEntityParentCyclicReference() {
+        dropAllResources();
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(0));
+        ResourceEntity resource1 = persistResource0toZone1AndAssert();
+
+        ResourceEntity resource2 = new ResourceEntity(TEST_ZONE_1, DRIVE_ID);
+        resource2.setAttributes(DRIVE_ATTRIBUTES);
+        resource2.setAttributesAsJson(JSON_UTILS.serialize(resource2.getAttributes()));
+        resource2.setParents(new HashSet<>(Collections.singletonList(new Parent(resource1.getResourceIdentifier()))));
+        this.resourceRepository.save(resource2);
+
+        ResourceEntity resource3 = new ResourceEntity(TEST_ZONE_1, EVIDENCE_IMPLANT_ID);
+        resource3.setAttributes(EVIDENCE_IMPLANT_ATTRIBUTES);
+        resource3.setAttributesAsJson(JSON_UTILS.serialize(resource3.getAttributes()));
+        resource3.setParents(new HashSet<>(Collections.singletonList(new Parent(resource2.getResourceIdentifier()))));
+        this.resourceRepository.save(resource3);
+
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(3));
+
+        resource1.setParents(new HashSet<>(Collections.singletonList(new Parent(resource3.getResourceIdentifier()))));
+        try {
+            this.resourceRepository.save(resource1);
+        } catch (SchemaViolationException ex) {
+            assertThat(ex.getMessage(),
+                    equalTo("Updating entity '/site/basement' with parent '/evidence/implant' introduces a cyclic "
+                            + "reference."));
+            return;
+        }
+        Assert.fail("save() did not throw the expected exception.");
+        dropAllResources();
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testSave() {
+        ResourceEntity resource = persistRandomResourcetoZone1AndAssert();
+        String resourceId = resource.getResourceIdentifier();
+
+        GraphTraversal<Vertex, Vertex> traversal = this.graphTraversalSource.V().has(RESOURCE_ID_KEY, resourceId);
+
+        assertThat(traversal.hasNext(), equalTo(true));
+        assertThat(traversal.next().property(RESOURCE_ID_KEY).value(), equalTo(resourceId));
+        this.resourceRepository.delete(resource);
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void testSaveWithNoZoneName() {
+        ResourceEntity resourceEntity = new ResourceEntity();
+        resourceEntity.setResourceIdentifier("testResource");
+        resourceEntity.setZone(new ZoneEntity());
+        this.resourceRepository.save(resourceEntity);
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void testSaveWithNoZoneEntity() {
+        ResourceEntity resourceEntity = new ResourceEntity();
+        resourceEntity.setResourceIdentifier("testResource");
+        this.resourceRepository.save(resourceEntity);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testSaveHierarchical() {
+        ResourceEntity childResource = persist2LevelRandomResourcetoZone1();
+        String childResourceId = childResource.getResourceIdentifier();
+
+        GraphTraversal<Vertex, Vertex> traversal = this.graphTraversalSource.V().has(RESOURCE_ID_KEY, childResourceId);
+        assertThat(traversal.hasNext(), equalTo(true));
+        Vertex childResourceVertex = traversal.next();
+        assertThat(childResourceVertex.property(RESOURCE_ID_KEY).value(), equalTo(childResourceId));
+
+        Parent parent = (Parent) childResource.getParents().toArray()[0];
+        traversal = this.graphTraversalSource.V(childResourceVertex.id()).out("parent")
+                .has(RESOURCE_ID_KEY, parent.getIdentifier());
+        assertThat(traversal.hasNext(), equalTo(true));
+        Vertex parentVertex = traversal.next();
+        assertThat(parentVertex.property(RESOURCE_ID_KEY).value(), equalTo(parent.getIdentifier()));
+        deleteTwoLevelEntityAndParents(childResource, TEST_ZONE_1, this.resourceRepository);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testUpdateAttachedEntity() {
+        ResourceEntity resourceEntity = persistRandomResourcetoZone1AndAssert();
+        String resourceId = resourceEntity.getResourceIdentifier();
+
+        GraphTraversalSource g = this.graphTraversalSource;
+        GraphTraversal<Vertex, Vertex> traversal = g.V().has(RESOURCE_ID_KEY, resourceId);
+        assertThat(traversal.hasNext(), equalTo(true));
+        assertThat(traversal.next().property(RESOURCE_ID_KEY).value(), equalTo(resourceId));
+
+        // Update the resource.
+        String updateJSON = "{\'status':'test'}";
+        resourceEntity.setAttributesAsJson(updateJSON);
+        saveWithRetry(this.resourceRepository, resourceEntity, 3);
+        assertThat(this.resourceRepository.getEntity(TEST_ZONE_1, resourceEntity.getResourceIdentifier())
+                .getAttributesAsJson(), equalTo(updateJSON));
+        this.resourceRepository.delete(resourceEntity);
+    }
+
+    @Test(expectedExceptions = SchemaViolationException.class)
+    public void testUpdateUnattachedEntity() {
+        String resourceId = persistRandomResourcetoZone1AndAssert().getResourceIdentifier();
+
+        GraphTraversalSource g = this.graphTraversalSource;
+        GraphTraversal<Vertex, Vertex> traversal = g.V().has(RESOURCE_ID_KEY, resourceId);
+        assertThat(traversal.hasNext(), equalTo(true));
+        assertThat(traversal.next().property(RESOURCE_ID_KEY).value(), equalTo(resourceId));
+
+        // Save a new resource which violates the uniqueness constraint.
+        persistResourceToZoneAndAssert(TEST_ZONE_1, resourceId, DRIVE_ATTRIBUTES);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testSaveMultiple() {
+        List<ResourceEntity> resourceEntitiesToSave = new ArrayList<>();
+        ResourceEntity resourceEntity1 = new ResourceEntity(TEST_ZONE_1, DRIVE_ID + getRandomNumber());
+        ResourceEntity resourceEntity2 = new ResourceEntity(TEST_ZONE_1, JOSECHUNG_ID + getRandomNumber());
+        resourceEntitiesToSave.add(resourceEntity1);
+        resourceEntitiesToSave.add(resourceEntity2);
+        this.resourceRepository.save(resourceEntitiesToSave);
+        assertThat(this.resourceRepository.getEntity(TEST_ZONE_1, resourceEntity1.getResourceIdentifier()),
+                equalTo(resourceEntity1));
+        assertThat(this.resourceRepository.getEntity(TEST_ZONE_1, resourceEntity2.getResourceIdentifier()),
+                equalTo(resourceEntity2));
+        this.resourceRepository.delete(resourceEntity1);
+        this.resourceRepository.delete(resourceEntity2);
+    }
+
+    @Test
+    public void testSaveResourceWithNonexistentParent() {
+        this.dropAllResources();
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(0));
+        ResourceEntity resource = new ResourceEntity(TEST_ZONE_1, DRIVE_ID);
+        resource.setParents(new HashSet<>(Collections.singletonList(new Parent(BASEMENT_SITE_ID))));
+
+        // Save a resource with nonexistent parent which should throw IllegalStateException exception
+        // while saving parent relationships.
+        try {
+            this.resourceRepository.save(resource);
+        } catch (IllegalStateException ex) {
+            assertThat(ex.getMessage(),
+                    equalTo("No parent exists in zone 'testzone1' with 'resourceId' value of '/site/basement'."));
+            return;
+        }
+        Assert.fail("save() did not throw the expected IllegalStateException exception.");
+
+    }
+
+    @Test(enabled = false)
+    public void testPerformance() throws Exception {
+        for (int i = 0; i < 100000; i++) {
+            StopWatch stopWatch = new StopWatch();
+            stopWatch.start();
+            this.resourceRepository.save(new ResourceEntity(TEST_ZONE_1, String.format("/x-files/%d", i)));
+            this.resourceRepository.getByZoneAndResourceIdentifier(TEST_ZONE_1, String.format("/x-files/%d", i));
+            stopWatch.stop();
+            System.out.println(String.format("Test %d took: %d ms", i, stopWatch.getTime()));
+            Thread.sleep(1L);
+        }
+    }
+
+    @Test(expectedExceptions = AttributeLimitExceededException.class,
+            expectedExceptionsMessageRegExp = "The number of attributes on this resource .* has exceeded the maximum "
+                    + "limit of .*")
+    public void testSearchAttributesTraversalLimitException() {
+        long traversalLimit = 256L;
+        try {
+            ResourceEntity resource1 = persist3LevelRandomResourcetoZone1();
+            traversalLimit = this.resourceRepository.getTraversalLimit();
+            assertThat(traversalLimit, equalTo(256L));
+            this.resourceRepository.setTraversalLimit(2);
+            assertThat(this.resourceRepository.getTraversalLimit(), equalTo(2L));
+            this.resourceRepository.getResourceWithInheritedAttributes(TEST_ZONE_1, resource1.getResourceIdentifier());
+        } finally {
+            this.resourceRepository.setTraversalLimit(traversalLimit);
+            assertThat(this.resourceRepository.getTraversalLimit(), equalTo(256L));
+        }
+    }
+
+    @Test
+    public void testSearchAttributesEqualsTraversalLimit() {
+        long traversalLimit = 256L;
+        try {
+            traversalLimit = this.resourceRepository.getTraversalLimit();
+            assertThat(traversalLimit, equalTo(256L));
+            this.resourceRepository.setTraversalLimit(3);
+            assertThat(this.resourceRepository.getTraversalLimit(), equalTo(3L));
+            ResourceEntity resource1 = persist3LevelRandomResourcetoZone1();
+            ResourceEntity actualResource = this.resourceRepository
+                    .getResourceWithInheritedAttributes(TEST_ZONE_1, resource1.getResourceIdentifier());
+            assertThat(actualResource.getAttributes().size(), equalTo(3));
+        } finally {
+            this.resourceRepository.setTraversalLimit(traversalLimit);
+            assertThat(this.resourceRepository.getTraversalLimit(), equalTo(256L));
+        }
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testGetResourceEntityAndDescendantsIds() {
+
+        ResourceEntity basement = persistResourceToZoneAndAssert(TEST_ZONE_1, BASEMENT_SITE_ID + getRandomNumber(),
+                BASEMENT_ATTRIBUTES);
+
+        ResourceEntity drive = persistResourceWithParentsToZoneAndAssert(TEST_ZONE_1, DRIVE_ID + getRandomNumber(),
+                DRIVE_ATTRIBUTES,
+                new HashSet<>(Collections.singletonList(new Parent(basement.getResourceIdentifier()))));
+        ResourceEntity ascension = persistResourceWithParentsToZoneAndAssert(TEST_ZONE_1,
+                ASCENSION_ID + getRandomNumber(), ASCENSION_ATTRIBUTES,
+                new HashSet<>(Collections.singletonList(new Parent(basement.getResourceIdentifier()))));
+
+        ResourceEntity implant = persistResourceWithParentsToZoneAndAssert(TEST_ZONE_1,
+                EVIDENCE_IMPLANT_ID + getRandomNumber(), EVIDENCE_IMPLANT_ATTRIBUTES, new HashSet<>(
+                        Arrays.asList(new Parent(drive.getResourceIdentifier()),
+                                new Parent(ascension.getResourceIdentifier()))));
+        ResourceEntity scullysTestimony = persistResourceWithParentsToZoneAndAssert(TEST_ZONE_1,
+                EVIDENCE_SCULLYS_TESTIMONY_ID + getRandomNumber(), SCULLYS_TESTIMONY_ATTRIBUTES,
+                new HashSet<>(Collections.singletonList(new Parent(ascension.getResourceIdentifier()))));
+
+        Set<String> descendantsIds = this.resourceRepository.getResourceEntityAndDescendantsIds(basement);
+
+        assertThat(descendantsIds, hasSize(5));
+
+        assertThat(descendantsIds, hasItems(basement.getResourceIdentifier(), drive.
+
+                        getResourceIdentifier(), ascension.getResourceIdentifier(), implant.getResourceIdentifier(),
+                scullysTestimony.getResourceIdentifier()));
+
+        descendantsIds = this.resourceRepository.getResourceEntityAndDescendantsIds(ascension);
+
+        assertThat(descendantsIds, hasSize(3));
+
+        assertThat(descendantsIds, hasItems(ascension.getResourceIdentifier(), implant.
+
+                getResourceIdentifier(), scullysTestimony.getResourceIdentifier()));
+
+        descendantsIds = this.resourceRepository.getResourceEntityAndDescendantsIds(drive);
+
+        assertThat(descendantsIds, hasSize(2));
+
+        assertThat(descendantsIds, hasItems(drive.getResourceIdentifier(), implant.
+
+                getResourceIdentifier()));
+
+        descendantsIds = this.resourceRepository.getResourceEntityAndDescendantsIds(implant);
+
+        assertThat(descendantsIds, hasSize(1));
+
+        assertThat(descendantsIds, hasItems(implant.getResourceIdentifier()));
+
+        descendantsIds = this.resourceRepository.getResourceEntityAndDescendantsIds(scullysTestimony);
+
+        assertThat(descendantsIds, hasSize(1));
+
+        assertThat(descendantsIds, hasItems(scullysTestimony.getResourceIdentifier()));
+
+        descendantsIds = this.resourceRepository.getResourceEntityAndDescendantsIds(null);
+
+        assertThat(descendantsIds, empty());
+
+        descendantsIds = this.resourceRepository.getResourceEntityAndDescendantsIds(new
+
+                ResourceEntity(TEST_ZONE_1, "/nonexistent-resource"));
+
+        assertThat(descendantsIds, empty());
+        deleteThreeLevelEntityAndParents(basement, TEST_ZONE_1, this.resourceRepository);
+    }
+
+    @Test(expectedExceptions = IllegalStateException.class)
+    public void testChildWithDifferentZone() {
+        ResourceEntity basement = persistResourceToZoneAndAssert(TEST_ZONE_1, BASEMENT_SITE_ID + getRandomNumber(),
+                BASEMENT_ATTRIBUTES);
+
+        persistResourceWithParentsToZoneAndAssert(TEST_ZONE_2, DRIVE_ID + getRandomNumber(), DRIVE_ATTRIBUTES,
+                new HashSet<>(Collections.singletonList(new Parent(basement.getResourceIdentifier()))));
+
+    }
+
+    @Test
+    public void testVersion() {
+        this.dropAllResources();
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(0));
+        assertThat(this.resourceRepository.checkVersionVertexExists(1), equalTo(false));
+        this.resourceRepository.createVersionVertex(1);
+        assertThat(this.resourceRepository.checkVersionVertexExists(1), equalTo(true));
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(1));
+
+        // assert that createVersionVertex creates a new vertex.
+        this.resourceRepository.createVersionVertex(2);
+        assertThat(this.graphTraversalSource.V().count().next().intValue(), equalTo(2));
+        dropAllResources();
+    }
+
+    private ResourceEntity persist2LevelRandomResourcetoZone1() {
+        ResourceEntity parentResource = persistResourceToZoneAndAssert(TEST_ZONE_1,
+                BASEMENT_SITE_ID + getRandomNumber(), BASEMENT_ATTRIBUTES);
+        HashSet<Parent> parents = new HashSet<>(
+                Collections.singletonList(new Parent(parentResource.getResourceIdentifier())));
+        return persistResourceWithParentsToZoneAndAssert(TEST_ZONE_1, DRIVE_ID + getRandomNumber(), DRIVE_ATTRIBUTES,
+                parents);
+    }
+
+    private ResourceEntity persist3LevelRandomResourcetoZone1() {
+        ResourceEntity parentResource = persist2LevelRandomResourcetoZone1();
+        HashSet<Parent> parents = new HashSet<>(
+                Collections.singletonList(new Parent(parentResource.getResourceIdentifier())));
+        return persistResourceWithParentsToZoneAndAssert(TEST_ZONE_1, EVIDENCE_IMPLANT_ID + getRandomNumber(),
+                EVIDENCE_IMPLANT_ATTRIBUTES, parents);
+    }
+
+    private ResourceEntity persistResourceWithParentsToZoneAndAssert(final ZoneEntity zoneEntity,
+            final String resourceIdentifier, final Set<Attribute> attributes, final Set<Parent> parents) {
+        ResourceEntity resource = new ResourceEntity(zoneEntity, resourceIdentifier);
+        resource.setAttributes(attributes);
+        resource.setAttributesAsJson(JSON_UTILS.serialize(resource.getAttributes()));
+        resource.setParents(parents);
+        ResourceEntity resourceEntity = saveWithRetry(this.resourceRepository, resource, 3);
+        assertThat(this.resourceRepository.findOne(resourceEntity.getId()), equalTo(resource));
+        return resourceEntity;
+    }
+
+    private ResourceEntity persistRandomResourcetoZone1AndAssert() {
+        return persistResourceToZoneAndAssert(TEST_ZONE_1, DRIVE_ID + getRandomNumber(), DRIVE_ATTRIBUTES);
+    }
+
+    private ResourceEntity persistResource0toZone1AndAssert() {
+        return persistResourceToZoneAndAssert(TEST_ZONE_1, BASEMENT_SITE_ID, BASEMENT_ATTRIBUTES);
+    }
+
+    private ResourceEntity persistResource2toZone1AndAssert() {
+        return persistResourceToZoneAndAssert(TEST_ZONE_1, JOSECHUNG_ID, DRIVE_ATTRIBUTES);
+    }
+
+    private int getRandomNumber() {
+        return randomGenerator.nextInt(100000000);
+    }
+
+    //Because of unique indices, saves on ZonableEntity can throw a lock exception due to contention on the index
+    //update. This method allows does a sleep and retry the save.
+
+    /*
+     * Sample Exception: [Z: C:] 2016-11-03 15:13:33 ERROR [TestNG] database.StandardTitanGraph
+     * [StandardTitanGraph.java:779] Could not commit transaction [10] due to exceptionsg
+     * com.thinkaurelius.titan.diskstorage.locking.PermanentLockingException: Local lock contention at
+     * com.thinkaurelius.titan.diskstorage.locking.AbstractLocker.writeLock(AbstractLocker.java:313) at
+     * com.thinkaurelius.titan.diskstorage.locking.consistentkey.ExpectedValueCheckingStore.acquireLock(
+     * ExpectedValueCheckingStore.java:89) at
+     * com.thinkaurelius.titan.diskstorage.keycolumnvalue.KCVSProxy.acquireLock(KCVSProxy.java:40) at
+     * com.thinkaurelius.titan.diskstorage.BackendTransaction.acquireIndexLock(BackendTransaction.java:240) at
+     * com.thinkaurelius.titan.graphdb.database.StandardTitanGraph.prepareCommit(StandardTitanGraph.java:554) at
+     * com.thinkaurelius.titan.graphdb.database.StandardTitanGraph.commit(StandardTitanGraph.java:683) at
+     * com.thinkaurelius.titan.graphdb.transaction.StandardTitanTx.commit(StandardTitanTx.java:1352) at
+     * com.thinkaurelius.titan.graphdb.tinkerpop.TitanBlueprintsGraph$GraphTransaction.doCommit(TitanBlueprintsGraph.
+     * java:263) at
+     * org.apache.tinkerpop.gremlin.structure.util.AbstractTransaction.commit(AbstractTransaction.java:105) at
+     * com.ge.predix.acs.privilege.management.dao.GraphGenericRepository.save(GraphGenericRepository.java:221) at
+     * com.ge.predix.acs.privilege.management.dao.GraphResourceRepositoryTest.saveWithRetry(
+     * GraphResourceRepositoryTest.java:554) at
+     * com.ge.predix.acs.privilege.management.dao.GraphSubjectRepositoryTest.saveWithRetry(GraphSubjectRepositoryTest.
+     * java:277) at
+     * com.ge.predix.acs.privilege.management.dao.GraphSubjectRepositoryTest.persistSubjectToZoneAndAssert(
+     * GraphSubjectRepositoryTest.java:246) at
+     * com.ge.predix.acs.privilege.management.dao.GraphSubjectRepositoryTest.persistRandomSubjectToZone1AndAssert(
+     * GraphSubjectRepositoryTest.java:221) at
+     * com.ge.predix.acs.privilege.management.dao.GraphSubjectRepositoryTest.testGetByZoneAndSubjectIdentifier(
+     * GraphSubjectRepositoryTest.java:71)
+     */
+    static <E extends ZonableEntity> E saveWithRetry(final GraphGenericRepository<E> repository,
+            final E zonableEntity, final int retryCount) throws TitanException {
+        try {
+            repository.save(zonableEntity);
+        } catch (TitanException te) {
+            if (te.getCause().getCause().getMessage().contains("Local lock") && retryCount > 0) {
+                try {
+                    Thread.sleep(250);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+                zonableEntity.setId(0L); // Repository does not reset the vertex Id, on commit failure. Clear.
+                saveWithRetry(repository, zonableEntity, retryCount - 1);
+            } else {
+                throw te;
+            }
+        }
+        return zonableEntity;
+    }
+
+    private ResourceEntity persistResourceToZoneAndAssert(final ZoneEntity zoneEntity, final String resourceIdentifier,
+            final Set<Attribute> attributes) {
+
+        ResourceEntity resource = new ResourceEntity(zoneEntity, resourceIdentifier);
+        resource.setAttributes(attributes);
+        resource.setAttributesAsJson(JSON_UTILS.serialize(resource.getAttributes()));
+        ResourceEntity resourceEntity = saveWithRetry(this.resourceRepository, resource, 3);
+        assertThat(this.resourceRepository.findOne(resourceEntity.getId()), equalTo(resource));
+        return resourceEntity;
+    }
+
+    static <E extends ZonableEntity> void deleteTwoLevelEntityAndParents(final E entity, final ZoneEntity zone,
+            final GraphGenericRepository<E> repository) {
+        Set<Parent> parents = entity.getParents();
+        for (Parent parent : parents) {
+            repository.delete(repository.getEntity(zone, parent.getIdentifier()));
+        }
+        repository.delete(entity);
+    }
+
+    private static <E extends ZonableEntity> void deleteThreeLevelEntityAndParents(final E entity,
+            final ZoneEntity zone, final GraphGenericRepository<E> repository) {
+        Set<Parent> parents = entity.getParents();
+        for (Parent parent : parents) {
+            deleteTwoLevelEntityAndParents(repository.getEntity(zone, parent.getIdentifier()), zone, repository);
+        }
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/privilege/management/dao/GraphSubjectRepositoryTest.java b/service/src/test/java/com/ge/predix/acs/privilege/management/dao/GraphSubjectRepositoryTest.java
new file mode 100644
index 0000000..5c4201e
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/privilege/management/dao/GraphSubjectRepositoryTest.java
@@ -0,0 +1,320 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import static com.ge.predix.acs.privilege.management.dao.GraphGenericRepository.PARENT_EDGE_LABEL;
+import static com.ge.predix.acs.privilege.management.dao.GraphSubjectRepository.SUBJECT_ID_KEY;
+import static com.ge.predix.acs.testutils.XFiles.AGENT_MULDER;
+import static com.ge.predix.acs.testutils.XFiles.AGENT_SCULLY;
+import static com.ge.predix.acs.testutils.XFiles.FBI;
+import static com.ge.predix.acs.testutils.XFiles.FBI_ATTRIBUTES;
+import static com.ge.predix.acs.testutils.XFiles.MULDERS_ATTRIBUTES;
+import static com.ge.predix.acs.testutils.XFiles.PENTAGON_ATTRIBUTES;
+import static com.ge.predix.acs.testutils.XFiles.SECRET_CLASSIFICATION;
+import static com.ge.predix.acs.testutils.XFiles.SECRET_GROUP;
+import static com.ge.predix.acs.testutils.XFiles.SECRET_GROUP_ATTRIBUTES;
+import static com.ge.predix.acs.testutils.XFiles.SITE_BASEMENT;
+import static com.ge.predix.acs.testutils.XFiles.SITE_PENTAGON;
+import static com.ge.predix.acs.testutils.XFiles.SPECIAL_AGENTS_GROUP;
+import static com.ge.predix.acs.testutils.XFiles.SPECIAL_AGENTS_GROUP_ATTRIBUTES;
+import static com.ge.predix.acs.testutils.XFiles.TOP_SECRET_CLASSIFICATION;
+import static com.ge.predix.acs.testutils.XFiles.TOP_SECRET_GROUP;
+import static com.ge.predix.acs.testutils.XFiles.TOP_SECRET_GROUP_ATTRIBUTES;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.hasSize;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Random;
+import java.util.Set;
+
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.config.GraphConfig;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.Parent;
+import com.ge.predix.acs.utils.JsonUtils;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.thinkaurelius.titan.core.TitanException;
+import com.thinkaurelius.titan.core.TitanFactory;
+
+public class GraphSubjectRepositoryTest {
+    private static final JsonUtils JSON_UTILS = new JsonUtils();
+    private static final ZoneEntity TEST_ZONE_1 = new ZoneEntity(1L, "testzone1");
+    private static final ZoneEntity TEST_ZONE_2 = new ZoneEntity(2L, "testzone2");
+    private static final int CONCURRENT_TEST_THREAD_COUNT = 3;
+    private static final int CONCURRENT_TEST_INVOCATIONS = 20;
+
+    private GraphSubjectRepository subjectRepository;
+    private GraphTraversalSource graphTraversalSource;
+    private Random randomGenerator = new Random();
+
+    @BeforeClass
+    public void setup() throws Exception {
+        this.subjectRepository = new GraphSubjectRepository();
+        Graph graph = TitanFactory.build().set("storage.backend", "inmemory").open();
+        GraphConfig.createSchemaElements(graph);
+        this.graphTraversalSource = graph.traversal();
+        this.subjectRepository.setGraphTraversal(this.graphTraversalSource);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testGetByZoneAndSubjectIdentifier() {
+        SubjectEntity subjectEntityForZone1 = persistRandomSubjectToZone1AndAssert();
+        SubjectEntity subjectEntityForZone2 = persistRandomSubjectToZone2AndAssert();
+
+        SubjectEntity actualSubjectForZone1 = this.subjectRepository
+                .getByZoneAndSubjectIdentifier(TEST_ZONE_1, subjectEntityForZone1.getSubjectIdentifier());
+        SubjectEntity actualSubjectForZone2 = this.subjectRepository
+                .getByZoneAndSubjectIdentifier(TEST_ZONE_2, subjectEntityForZone2.getSubjectIdentifier());
+        assertThat(actualSubjectForZone1, equalTo(subjectEntityForZone1));
+        assertThat(actualSubjectForZone2, equalTo(subjectEntityForZone2));
+        this.subjectRepository.delete(subjectEntityForZone1);
+        this.subjectRepository.delete(subjectEntityForZone2);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testGetByZoneAndSubjectIdentifierAndScopes() {
+        SubjectEntity expectedSubject = persistScopedHierarchy(AGENT_MULDER + getRandomNumber(), SITE_BASEMENT);
+        String subjectIdentifier = expectedSubject.getSubjectIdentifier();
+
+        HashSet<Attribute> expectedAttributes = new HashSet<>(
+                Arrays.asList(SECRET_CLASSIFICATION, TOP_SECRET_CLASSIFICATION, SITE_BASEMENT));
+        expectedSubject.setAttributes(expectedAttributes);
+        expectedSubject.setAttributesAsJson(JSON_UTILS.serialize(expectedAttributes));
+
+        SubjectEntity actualSubject = this.subjectRepository
+                .getSubjectWithInheritedAttributesForScopes(TEST_ZONE_1, subjectIdentifier,
+                        new HashSet<>(Collections.singletonList(SITE_BASEMENT)));
+        assertThat(actualSubject, equalTo(expectedSubject));
+
+        expectedAttributes = new HashSet<>(Arrays.asList(SECRET_CLASSIFICATION, SITE_BASEMENT));
+        expectedSubject.setAttributes(expectedAttributes);
+        expectedSubject.setAttributesAsJson(JSON_UTILS.serialize(expectedAttributes));
+        actualSubject = this.subjectRepository
+                .getSubjectWithInheritedAttributesForScopes(TEST_ZONE_1, subjectIdentifier,
+                        new HashSet<>(Collections.singletonList(SITE_PENTAGON)));
+        assertThat(actualSubject, equalTo(expectedSubject));
+        GraphResourceRepositoryTest
+                .deleteTwoLevelEntityAndParents(expectedSubject, TEST_ZONE_1, this.subjectRepository);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testParentAndChildSameAttribute() {
+        SubjectEntity agentScully = new SubjectEntity(TEST_ZONE_1, AGENT_SCULLY + getRandomNumber());
+        agentScully.setAttributes(MULDERS_ATTRIBUTES);
+        agentScully.setAttributesAsJson(JSON_UTILS.serialize(agentScully.getAttributes()));
+        saveWithRetry(agentScully, 3);
+        SubjectEntity agentMulder = new SubjectEntity(TEST_ZONE_1, AGENT_MULDER + getRandomNumber());
+        agentMulder.setAttributes(MULDERS_ATTRIBUTES);
+        agentMulder.setAttributesAsJson(JSON_UTILS.serialize(agentMulder.getAttributes()));
+        agentMulder
+                .setParents(new HashSet<>(Collections.singletonList(new Parent(agentScully.getSubjectIdentifier()))));
+        saveWithRetry(agentMulder, 3);
+        SubjectEntity actualAgentMulder = this.subjectRepository
+                .getSubjectWithInheritedAttributesForScopes(TEST_ZONE_1, agentMulder.getSubjectIdentifier(), null);
+        assertThat(actualAgentMulder.getAttributesAsJson(),
+                equalTo("[{\"issuer\":\"acs.example.org\",\"name\":\"site\",\"value\":\"basement\"}]"));
+        GraphResourceRepositoryTest.deleteTwoLevelEntityAndParents(agentScully, TEST_ZONE_1, this.subjectRepository);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testParentAndChildAttributeSameNameDifferentValues() {
+        SubjectEntity agentScully = new SubjectEntity(TEST_ZONE_1, AGENT_SCULLY + getRandomNumber());
+        agentScully.setAttributes(PENTAGON_ATTRIBUTES);
+        agentScully.setAttributesAsJson(JSON_UTILS.serialize(agentScully.getAttributes()));
+        saveWithRetry(agentScully, 3);
+        SubjectEntity agentMulder = new SubjectEntity(TEST_ZONE_1, AGENT_MULDER + getRandomNumber());
+        agentMulder.setAttributes(MULDERS_ATTRIBUTES);
+        agentMulder.setAttributesAsJson(JSON_UTILS.serialize(agentMulder.getAttributes()));
+        agentMulder
+                .setParents(new HashSet<>(Collections.singletonList(new Parent(agentScully.getSubjectIdentifier()))));
+        saveWithRetry(agentMulder, 3);
+        SubjectEntity actualAgentMulder = this.subjectRepository
+                .getSubjectWithInheritedAttributesForScopes(TEST_ZONE_1, agentMulder.getSubjectIdentifier(), null);
+        assertThat(actualAgentMulder.getAttributesAsJson(),
+                equalTo("[{\"issuer\":\"acs.example.org\",\"name\":\"site\",\"value\":\"basement\"},"
+                        + "{\"issuer\":\"acs.example.org\",\"name\":\"site\",\"value\":\"pentagon\"}]"));
+        GraphResourceRepositoryTest.deleteTwoLevelEntityAndParents(agentScully, TEST_ZONE_1, this.subjectRepository);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testSave() {
+        SubjectEntity subjectEntity = persistRandomSubjectToZone1AndAssert();
+        String subjectId = subjectEntity.getSubjectIdentifier();
+        GraphTraversal<Vertex, Vertex> traversal = this.graphTraversalSource.V().has(SUBJECT_ID_KEY, subjectId);
+        assertThat(traversal.hasNext(), equalTo(true));
+        assertThat(traversal.next().property(SUBJECT_ID_KEY).value(), equalTo(subjectId));
+        this.subjectRepository.delete(subjectEntity);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testSaveWithNoAttributes() {
+        SubjectEntity subject = new SubjectEntity(TEST_ZONE_1, AGENT_SCULLY + getRandomNumber());
+        saveWithRetry(subject, 3);
+        assertThat(this.subjectRepository.getByZoneAndSubjectIdentifier(TEST_ZONE_1, subject.getSubjectIdentifier()),
+                equalTo(subject));
+        this.subjectRepository.delete(subject);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testSaveScopes() {
+        SubjectEntity subject = persistScopedHierarchy(AGENT_MULDER + getRandomNumber(), SITE_BASEMENT);
+        assertThat(IteratorUtils.count(this.graphTraversalSource.V(subject.getId()).outE(PARENT_EDGE_LABEL)),
+                equalTo(2L));
+
+        // Persist again (i.e. update) and make sure vertex and edge count are stable.
+        this.subjectRepository.save(subject);
+        assertThat(IteratorUtils.count(this.graphTraversalSource.V(subject.getId()).outE(PARENT_EDGE_LABEL)),
+                equalTo(2L));
+
+        Parent parent = null;
+        for (Parent tempParent : this.subjectRepository.findOne(subject.getId()).getParents()) {
+            if (tempParent.getIdentifier().contains(TOP_SECRET_GROUP)) {
+                parent = tempParent;
+            }
+        }
+        assertThat(parent, notNullValue());
+        assertThat("Expected scope not found on subject.", parent.getScopes().contains(SITE_BASEMENT));
+        GraphResourceRepositoryTest.deleteTwoLevelEntityAndParents(subject, TEST_ZONE_1, this.subjectRepository);
+    }
+
+    @Test(threadPoolSize = CONCURRENT_TEST_THREAD_COUNT,
+            invocationCount = CONCURRENT_TEST_INVOCATIONS)
+    public void testGetSubjectEntityAndDescendantsIds() {
+
+        SubjectEntity fbi = persistSubjectToZoneAndAssert(TEST_ZONE_1, FBI + getRandomNumber(), FBI_ATTRIBUTES);
+
+        SubjectEntity specialAgentsGroup = persistSubjectWithParentsToZoneAndAssert(TEST_ZONE_1,
+                SPECIAL_AGENTS_GROUP + getRandomNumber(), SPECIAL_AGENTS_GROUP_ATTRIBUTES,
+                new HashSet<>(Collections.singletonList(new Parent(fbi.getSubjectIdentifier()))));
+
+        SubjectEntity topSecretGroup = persistSubjectToZoneAndAssert(TEST_ZONE_1, TOP_SECRET_GROUP + getRandomNumber(),
+                TOP_SECRET_GROUP_ATTRIBUTES);
+
+        SubjectEntity agentMulder = persistSubjectWithParentsToZoneAndAssert(TEST_ZONE_1,
+                AGENT_MULDER + getRandomNumber(), MULDERS_ATTRIBUTES, new HashSet<>(
+                        Arrays.asList(new Parent(specialAgentsGroup.getSubjectIdentifier()),
+                                new Parent(topSecretGroup.getSubjectIdentifier()))));
+
+        Set<String> descendantsIds = this.subjectRepository.getSubjectEntityAndDescendantsIds(fbi);
+        assertThat(descendantsIds, hasSize(3));
+        assertThat(descendantsIds, hasItems(fbi.getSubjectIdentifier(), specialAgentsGroup.getSubjectIdentifier(),
+                agentMulder.getSubjectIdentifier()));
+
+        descendantsIds = this.subjectRepository.getSubjectEntityAndDescendantsIds(specialAgentsGroup);
+        assertThat(descendantsIds, hasSize(2));
+        assertThat(descendantsIds,
+                hasItems(specialAgentsGroup.getSubjectIdentifier(), agentMulder.getSubjectIdentifier()));
+
+        descendantsIds = this.subjectRepository.getSubjectEntityAndDescendantsIds(topSecretGroup);
+        assertThat(descendantsIds, hasSize(2));
+        assertThat(descendantsIds, hasItems(topSecretGroup.getSubjectIdentifier(), agentMulder.getSubjectIdentifier()));
+
+        descendantsIds = this.subjectRepository.getSubjectEntityAndDescendantsIds(agentMulder);
+        assertThat(descendantsIds, hasSize(1));
+        assertThat(descendantsIds, hasItems(agentMulder.getSubjectIdentifier()));
+
+        descendantsIds = this.subjectRepository.getSubjectEntityAndDescendantsIds(null);
+        assertThat(descendantsIds, empty());
+
+        descendantsIds = this.subjectRepository
+                .getSubjectEntityAndDescendantsIds(new SubjectEntity(TEST_ZONE_1, "/nonexistent-subject"));
+        assertThat(descendantsIds, empty());
+        GraphResourceRepositoryTest.deleteTwoLevelEntityAndParents(agentMulder, TEST_ZONE_1, this.subjectRepository);
+    }
+
+    private SubjectEntity persistRandomSubjectToZone1AndAssert() {
+        return persistSubjectToZoneAndAssert(TEST_ZONE_1, AGENT_SCULLY + getRandomNumber(), Collections.emptySet());
+    }
+
+    private SubjectEntity persistRandomSubjectToZone2AndAssert() {
+        return persistSubjectToZoneAndAssert(TEST_ZONE_2, AGENT_SCULLY + getRandomNumber(), Collections.emptySet());
+    }
+
+    private SubjectEntity persistRandomTopSecretGroupAndAssert() {
+        return persistSubjectToZoneAndAssert(TEST_ZONE_1, TOP_SECRET_GROUP + getRandomNumber(),
+                TOP_SECRET_GROUP_ATTRIBUTES);
+    }
+
+    private SubjectEntity persistRandomSecretGroupAndAssert() {
+        return persistSubjectToZoneAndAssert(TEST_ZONE_1, SECRET_GROUP + getRandomNumber(), SECRET_GROUP_ATTRIBUTES);
+    }
+
+    private int getRandomNumber() {
+        return randomGenerator.nextInt(10000000);
+    }
+
+    private SubjectEntity persistSubjectToZoneAndAssert(final ZoneEntity zone, final String subjectIdentifier,
+            final Set<Attribute> attributes) {
+        SubjectEntity subject = new SubjectEntity(zone, subjectIdentifier);
+        subject.setAttributes(attributes);
+        subject.setAttributesAsJson(JSON_UTILS.serialize(subject.getAttributes()));
+        SubjectEntity subjectEntity = saveWithRetry(subject, 3);
+        assertThat(this.subjectRepository.findOne(subjectEntity.getId()), equalTo(subject));
+        return subjectEntity;
+    }
+
+    private SubjectEntity persistSubjectWithParentsToZoneAndAssert(final ZoneEntity zoneEntity,
+            final String subjectIdentifier, final Set<Attribute> attributes, final Set<Parent> parents) {
+        SubjectEntity subject = new SubjectEntity(zoneEntity, subjectIdentifier);
+        subject.setAttributes(attributes);
+        subject.setAttributesAsJson(JSON_UTILS.serialize(subject.getAttributes()));
+        subject.setParents(parents);
+        SubjectEntity subjectEntity = saveWithRetry(subject, 3);
+        assertThat(this.subjectRepository.findOne(subjectEntity.getId()), equalTo(subject));
+        return subjectEntity;
+    }
+
+    private SubjectEntity persistScopedHierarchy(final String subjectIdentifier, final Attribute scope) {
+        SubjectEntity secretGroup = persistRandomSecretGroupAndAssert();
+        SubjectEntity topSecretGroup = persistRandomTopSecretGroupAndAssert();
+
+        SubjectEntity agentMulder = new SubjectEntity(TEST_ZONE_1, subjectIdentifier);
+        agentMulder.setAttributes(MULDERS_ATTRIBUTES);
+        agentMulder.setAttributesAsJson(JSON_UTILS.serialize(agentMulder.getAttributes()));
+        agentMulder.setParents(new HashSet<>(Arrays.asList(
+                new Parent(topSecretGroup.getSubjectIdentifier(), new HashSet<>(Collections.singletonList(scope))),
+                new Parent(secretGroup.getSubjectIdentifier()))));
+        return this.subjectRepository.save(agentMulder);
+    }
+
+    private SubjectEntity saveWithRetry(final SubjectEntity subject, final int retryCount) throws TitanException {
+        return GraphResourceRepositoryTest.saveWithRetry(this.subjectRepository, subject, retryCount);
+    }
+
+}
diff --git a/service/src/test/java/com/ge/predix/acs/privilege/management/dao/TitanMigrationManagerTest.java b/service/src/test/java/com/ge/predix/acs/privilege/management/dao/TitanMigrationManagerTest.java
new file mode 100644
index 0000000..041e825
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/privilege/management/dao/TitanMigrationManagerTest.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyCollectionOf;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+public class TitanMigrationManagerTest {
+
+    private ResourceRepository resourceRepository;
+    private GraphResourceRepository resourceHierarchicalRepository;
+    private SubjectRepository subjectRepository;
+    private GraphSubjectRepository subjectHierarchicalRepository;
+
+    private List<ResourceEntity> fromListForResource = new ArrayList<ResourceEntity>();
+    private List<SubjectEntity> fromListForSubject = new ArrayList<SubjectEntity>();
+    private List<ResourceEntity> toListForResource = new ArrayList<ResourceEntity>();
+    private List<SubjectEntity> toListForSubject = new ArrayList<SubjectEntity>();
+
+    @BeforeClass
+    public void setup() throws Exception {
+        this.resourceRepository = Mockito.mock(ResourceRepository.class);
+        this.subjectRepository = Mockito.mock(SubjectRepository.class);
+        this.resourceHierarchicalRepository = Mockito.mock(GraphResourceRepository.class);
+        this.subjectHierarchicalRepository = Mockito.mock(GraphSubjectRepository.class);
+    }
+
+    // Save the given Resource entities to a local list to mock graph-save
+    private List<ResourceEntity> saveResourcesToGraph(final Iterable<ResourceEntity> entities) {
+        List<ResourceEntity> savedEntities = new ArrayList<>();
+        entities.forEach(item -> savedEntities.add(item));
+        toListForResource.addAll(savedEntities);
+        return savedEntities;
+    }
+
+    private List<SubjectEntity> saveSubjectsToGraph(final Iterable<SubjectEntity> entities) {
+        List<SubjectEntity> savedEntities = new ArrayList<>();
+        entities.forEach(item -> savedEntities.add(item));
+        toListForSubject.addAll(savedEntities);
+        return savedEntities;
+    }
+
+    @Test
+    public void migrationManagerTest() {
+        ZoneEntity zone1 = new ZoneEntity((long) 1, "testzone1");
+
+        ResourceEntity entityResource1 = new ResourceEntity(zone1, "testresource1");
+        ResourceEntity entityResource2 = new ResourceEntity(zone1, "testresource2");
+        SubjectEntity entitySubject1 = new SubjectEntity(zone1, "testsubject1");
+        SubjectEntity entitySubject2 = new SubjectEntity(zone1, "testsubject2");
+
+        Mockito.when(this.resourceRepository.findAll()).thenReturn(fromListForResource);
+        Mockito.when(this.resourceHierarchicalRepository.findAll()).thenReturn(this.toListForResource);
+        Mockito.when(this.subjectRepository.findAll()).thenReturn(fromListForSubject);
+        Mockito.when(this.subjectHierarchicalRepository.findAll()).thenReturn(this.toListForSubject);
+
+        // Verify that both graph and non-graph repos are empty
+        assertThat(this.resourceRepository.findAll().size(), equalTo(0));
+        assertThat(this.resourceHierarchicalRepository.findAll().size(), equalTo(0));
+        assertThat(this.subjectRepository.findAll().size(), equalTo(0));
+        assertThat(this.subjectHierarchicalRepository.findAll().size(), equalTo(0));
+
+        fromListForResource.addAll(Arrays.asList(entityResource1, entityResource2));
+        fromListForSubject.addAll(Arrays.asList(entitySubject1, entitySubject2));
+
+        // This mocked findAll() takes in a Pageable and returns a Page.
+        Mockito.when(this.resourceRepository.findAll(any(Pageable.class)))
+                .thenAnswer(new Answer<PageImpl<ResourceEntity>>() {
+                    public PageImpl<ResourceEntity> answer(final InvocationOnMock invocation) {
+                        return new PageImpl<ResourceEntity>(fromListForResource, null, fromListForResource.size());
+                    }
+                });
+        Mockito.when(this.subjectRepository.findAll(any(Pageable.class)))
+                .thenAnswer(new Answer<PageImpl<SubjectEntity>>() {
+                    public PageImpl<SubjectEntity> answer(final InvocationOnMock invocation) {
+                        return new PageImpl<SubjectEntity>(fromListForSubject, null, fromListForSubject.size());
+                    }
+                });
+        Mockito.when(this.resourceRepository.count()).thenReturn((long) fromListForResource.size());
+        Mockito.when(this.subjectRepository.count()).thenReturn((long) fromListForSubject.size());
+
+        // Verify that the non-graph repos are populated
+        assertThat(this.resourceRepository.findAll().size(), equalTo(2));
+        assertThat(this.resourceHierarchicalRepository.findAll().size(), equalTo(0));
+
+        assertThat(this.subjectRepository.findAll().size(), equalTo(2));
+        assertThat(this.subjectHierarchicalRepository.findAll().size(), equalTo(0));
+
+        // Mock graph-save function with a locally defined saveResourcesToGraph()
+        Mockito.when(this.resourceHierarchicalRepository.save(anyCollectionOf(ResourceEntity.class)))
+                .thenAnswer(new Answer<List<ResourceEntity>>() {
+                    @SuppressWarnings("unchecked")
+                    public List<ResourceEntity> answer(final InvocationOnMock invocation) {
+                        Object[] args = invocation.getArguments();
+                        return saveResourcesToGraph((List<ResourceEntity>) args[0]);
+                    }
+                });
+        Mockito.when(this.subjectHierarchicalRepository.save(anyCollectionOf(SubjectEntity.class)))
+                .thenAnswer(new Answer<List<SubjectEntity>>() {
+                    public List<SubjectEntity> answer(final InvocationOnMock invocation) {
+                        Object[] args = invocation.getArguments();
+                        @SuppressWarnings("unchecked")
+                        List<SubjectEntity> list = (List<SubjectEntity>) args[0];
+                        return saveSubjectsToGraph(list);
+                    }
+                });
+
+        new ResourceMigrationManager().doResourceMigration(resourceRepository,
+                resourceHierarchicalRepository, TitanMigrationManager.PAGE_SIZE);
+        new SubjectMigrationManager().doSubjectMigration(subjectRepository,
+                subjectHierarchicalRepository, TitanMigrationManager.PAGE_SIZE);
+
+        // Verify that the data from non-graph repo has been copied over to graph-repo, post-migration.
+        assertThat(this.resourceRepository.findAll().size(), equalTo(2));
+        assertThat(this.resourceHierarchicalRepository.findAll().size(), equalTo(2));
+
+        assertThat(this.subjectRepository.findAll().size(), equalTo(2));
+        assertThat(this.subjectHierarchicalRepository.findAll().size(), equalTo(2));
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/privilege/management/dao/TitanMigrationRollbackTest.java b/service/src/test/java/com/ge/predix/acs/privilege/management/dao/TitanMigrationRollbackTest.java
new file mode 100644
index 0000000..98cf74e
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/privilege/management/dao/TitanMigrationRollbackTest.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.privilege.management.dao;
+
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+
+import org.mockito.Mockito;
+import org.mockito.internal.util.reflection.Whitebox;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class TitanMigrationRollbackTest {
+
+    private final GraphResourceRepository resourceHierarchicalRepository = mock(
+            GraphResourceRepository.class);
+    private final SubjectHierarchicalRepository subjectHierarchicalRepository = mock(GraphSubjectRepository.class);
+    private ResourceMigrationManager resourceMigrationManager;
+    private TitanMigrationManager titanMigrationManager = new TitanMigrationManager();
+    private SubjectMigrationManager subjectMigrationManager;
+
+    @BeforeMethod
+    public void beforeMethod() {
+        this.resourceMigrationManager = Mockito.mock(ResourceMigrationManager.class);
+        this.subjectMigrationManager = Mockito.mock(SubjectMigrationManager.class);
+
+        Whitebox.setInternalState(this.titanMigrationManager, "resourceHierarchicalRepository",
+                this.resourceHierarchicalRepository);
+        Whitebox.setInternalState(this.titanMigrationManager, "subjectHierarchicalRepository",
+                this.subjectHierarchicalRepository);
+
+        Whitebox.setInternalState(this.titanMigrationManager, "resourceMigrationManager",
+                this.resourceMigrationManager);
+        Whitebox.setInternalState(this.titanMigrationManager, "subjectMigrationManager",
+                this.subjectMigrationManager);
+    }
+
+    @Test
+    public void testMigrationRollbackCalled() throws InterruptedException {
+        Mockito.when(this.resourceHierarchicalRepository.checkVersionVertexExists(1)).thenReturn(false);
+        this.titanMigrationManager.doMigration();
+
+        // This sleep is because TitanMigrationManager invokes resourceMigrationManager asynchronously
+        Thread.sleep(100);
+
+        Mockito.verify(this.subjectMigrationManager).rollbackMigratedData(anyObject());
+        Mockito.verify(this.resourceMigrationManager).rollbackMigratedData(anyObject());
+    }
+
+    @Test
+    public void testMigrationRollbackNotCalled() throws InterruptedException {
+        Mockito.when(this.resourceHierarchicalRepository.checkVersionVertexExists(1)).thenReturn(true);
+        this.titanMigrationManager.doMigration();
+
+        // This sleep is because TitanMigrationManager invokes resourceMigrationManager asynchronously
+        Thread.sleep(100);
+
+        Mockito.verify(this.subjectMigrationManager, times(0)).rollbackMigratedData(anyObject());
+        Mockito.verify(this.resourceMigrationManager, times(0)).rollbackMigratedData(anyObject());
+    }
+
+}
diff --git a/service/src/test/java/com/ge/predix/acs/request/context/AcsRequestContextHolderTest.java b/service/src/test/java/com/ge/predix/acs/request/context/AcsRequestContextHolderTest.java
new file mode 100644
index 0000000..2844adc
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/request/context/AcsRequestContextHolderTest.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.request.context;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.config.GraphConfig;
+import com.ge.predix.acs.config.InMemoryDataSourceConfig;
+import com.ge.predix.acs.request.context.AcsRequestContext.ACSRequestContextAttribute;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.testutils.TestUtils;
+import com.ge.predix.acs.zone.management.ZoneService;
+import com.ge.predix.acs.zone.management.ZoneServiceImpl;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+@ContextConfiguration(
+        classes = { InMemoryDataSourceConfig.class, ZoneServiceImpl.class, AcsRequestContextHolder.class,
+                GraphConfig.class })
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+@Test
+public class AcsRequestContextHolderTest extends AbstractTestNGSpringContextTests {
+
+    private static final String ZONE_NAME = "AcsRequestContextHolderTest";
+    private static final String ZONE_NAME_SUFFIX = ".zone";
+    private static final String ZONE_SUBDOMAIN_SUFFIX = "-subdomain";
+
+    @Autowired
+    private ZoneService zoneService;
+
+    private final TestUtils testUtils = new TestUtils();
+    private Zone testZone;
+
+    @BeforeClass
+    public void setup() {
+        this.testZone = this.testUtils.setupTestZone("AcsRequestContextHolderTest", this.zoneService);
+    }
+
+    @AfterClass
+    public void cleanup() {
+        this.zoneService.deleteZone(this.testZone.getName());
+    }
+
+    @Test
+    public void testAcsRequestContextSet() {
+        AcsRequestContext acsRequestContext = AcsRequestContextHolder.getAcsRequestContext();
+        ZoneEntity zoneEntity = (ZoneEntity) acsRequestContext.get(ACSRequestContextAttribute.ZONE_ENTITY);
+        Assert.assertEquals(zoneEntity.getName(), ZONE_NAME + ZONE_NAME_SUFFIX);
+        Assert.assertEquals(zoneEntity.getSubdomain(), ZONE_NAME + ZONE_SUBDOMAIN_SUFFIX);
+    }
+
+    @Test
+    public void testClearAcsRequestContext() {
+        AcsRequestContextHolder.clear();
+        Assert.assertNull(AcsRequestContextHolder.getAcsRequestContext());
+    }
+}
\ No newline at end of file
diff --git a/service/src/test/java/com/ge/predix/acs/security/AbstractHttpMethodsFilterTest.java b/service/src/test/java/com/ge/predix/acs/security/AbstractHttpMethodsFilterTest.java
new file mode 100644
index 0000000..e3aae7b
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/security/AbstractHttpMethodsFilterTest.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.security;
+
+import java.net.URI;
+import java.util.Collections;
+
+import org.eclipse.jetty.http.MimeTypes;
+import org.mockito.InjectMocks;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultMatcher;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.commons.web.ResponseEntityBuilder;
+
+public final class AbstractHttpMethodsFilterTest {
+
+    private static final String V1_DUMMY = "/v1/dummy";
+
+    private static final class DummyHttpMethodsFilter extends AbstractHttpMethodsFilter {
+
+        DummyHttpMethodsFilter() {
+            super(Collections.singletonMap("\\A" + V1_DUMMY + "/??\\Z", Collections.singleton(HttpMethod.GET)));
+        }
+    }
+
+    @RestController
+    private static final class DummyController {
+
+        @RequestMapping(method = RequestMethod.GET, value = V1_DUMMY)
+        public ResponseEntity<String> getDummy() {
+            return ResponseEntityBuilder.ok();
+        }
+    }
+
+    @InjectMocks
+    private DummyController dummyController;
+
+    private MockMvc mockMvc;
+
+    @BeforeClass
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        this.mockMvc =
+                MockMvcBuilders.standaloneSetup(this.dummyController).addFilters(new DummyHttpMethodsFilter()).build();
+    }
+
+    @Test
+    public void testWithNoAcceptHeaderInRequest() throws Exception {
+        this.mockMvc.perform(MockMvcRequestBuilders.request(HttpMethod.GET, URI.create(V1_DUMMY)))
+                .andExpect(MockMvcResultMatchers.status().isOk());
+    }
+
+    @Test(dataProvider = "mediaTypesAndExpectedStatuses")
+    public void testUnacceptableMediaTypes(final String mediaType, final ResultMatcher resultMatcher) throws Exception {
+        this.mockMvc.perform(MockMvcRequestBuilders.request(HttpMethod.GET, URI.create(V1_DUMMY))
+                .header(HttpHeaders.ACCEPT, mediaType)).andExpect(resultMatcher);
+    }
+
+    @DataProvider
+    public Object[][] mediaTypesAndExpectedStatuses() {
+        return new Object[][] { new Object[] { MediaType.ALL_VALUE, MockMvcResultMatchers.status().isOk() },
+                { MediaType.APPLICATION_JSON_VALUE, MockMvcResultMatchers.status().isOk() },
+                { MimeTypes.Type.APPLICATION_JSON_UTF_8.toString(), MockMvcResultMatchers.status().isOk() },
+                { MediaType.APPLICATION_JSON_VALUE + ", application/*+json", MockMvcResultMatchers.status().isOk() },
+                { MediaType.TEXT_PLAIN_VALUE, MockMvcResultMatchers.status().isOk() },
+                { MimeTypes.Type.TEXT_PLAIN_UTF_8.toString(), MockMvcResultMatchers.status().isOk() },
+                { "text/*+plain, " + MediaType.TEXT_PLAIN_VALUE, MockMvcResultMatchers.status().isOk() },
+                { "fake/type, " + MediaType.TEXT_PLAIN_VALUE, MockMvcResultMatchers.status().isOk() },
+                { "fake/type", MockMvcResultMatchers.status().isNotAcceptable() } };
+    }
+}
\ No newline at end of file
diff --git a/service/src/test/java/com/ge/predix/acs/security/SecurityFilterChainTest.java b/service/src/test/java/com/ge/predix/acs/security/SecurityFilterChainTest.java
new file mode 100644
index 0000000..b28133e
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/security/SecurityFilterChainTest.java
@@ -0,0 +1,220 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.security;
+
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.HEALTH_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.HEARTBEAT_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.MANAGED_RESOURCES_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.POLICY_EVALUATION_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.POLICY_SETS_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.RESOURCE_CONNECTOR_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.SUBJECTS_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.SUBJECT_CONNECTOR_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.V1;
+import static org.hamcrest.Matchers.containsString;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.RequestBuilder;
+import org.springframework.test.web.servlet.ResultMatcher;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+@WebAppConfiguration
+@ContextConfiguration("classpath:controller-tests-context.xml")
+public final class SecurityFilterChainTest extends AbstractTestNGSpringContextTests {
+
+    private static final URI SUBJECT_URI = URI.create(V1 + SUBJECTS_URL + "/test");
+    private static final URI SUBJECTS_URI = URI.create(V1 + SUBJECTS_URL);
+    private static final URI RESOURCE_URI = URI.create(V1 + MANAGED_RESOURCES_URL + "/test");
+    private static final URI RESOURCES_URI = URI.create(V1 + MANAGED_RESOURCES_URL);
+    private static final URI POLICY_SET_URI = URI.create(V1 + POLICY_SETS_URL + "/test");
+    private static final URI POLICY_SETS_URI = URI.create(V1 + POLICY_SETS_URL);
+    private static final URI POLICY_EVAL_URI = URI.create(V1 + POLICY_EVALUATION_URL);
+    private static final URI RESOURCE_CONNECTOR_URI = URI.create(V1 + RESOURCE_CONNECTOR_URL);
+    private static final URI SUBJECT_CONNECTOR_URI = URI.create(V1 + SUBJECT_CONNECTOR_URL);
+    private static final URI ZONE_URI = URI.create(V1 + "/zone/test");
+    private static final URI HEALTH_URI = URI.create(HEALTH_URL);
+
+    private static final String CONTENT = "test content";
+    private static final String MALFORMED_BEARER_TOKEN = "Bearer foo";
+
+    @Autowired
+    private WebApplicationContext wac;
+
+    private MockMvc mockMvc;
+
+    @BeforeClass
+    public void setup() {
+        // https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#test-mockmvc
+        // https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#web-app-security
+        // https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#filter-ordering
+        // http://projects.spring.io/spring-security-oauth/docs/oauth2.html
+        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(SecurityMockMvcConfigurers.springSecurity())
+                .alwaysDo(print()).build();
+    }
+
+    @Test(dataProvider = "anonymousRequestBuilder")
+    public void testAnonymousAccess(final RequestBuilder request, final ResultMatcher expectedStatus,
+            final ResultMatcher expectedContent) throws Exception {
+        this.mockMvc.perform(request).andExpect(expectedStatus).andExpect(expectedContent);
+    }
+
+    @Test(dataProvider = "invalidTokenRequestBuilder")
+    public void testInvalidTokenAccess(final RequestBuilder request) throws Exception {
+        this.mockMvc.perform(request).andExpect(status().isUnauthorized())
+                .andExpect(content().string(containsString("invalid_token")));
+    }
+
+    @DataProvider
+    private Object[][] anonymousRequestBuilder() {
+        return combine(testAnonymousHealth(), testAnonymousHeartbeat());
+    }
+
+    private Object[][] testAnonymousHeartbeat() {
+        return new Object[][] {
+                { MockMvcRequestBuilders.get(HEARTBEAT_URL), status().isOk(), content().string("alive") } };
+    }
+
+    private Object[][] testAnonymousHealth() {
+        return new Object[][] { { MockMvcRequestBuilders.get(HEALTH_URL), status().isServiceUnavailable(),
+                content().string("{\"status\":\"DOWN\"}") } };
+    }
+
+    @DataProvider
+    private Object[][] invalidTokenRequestBuilder() throws JsonProcessingException {
+        return combine(testHealth(), testZoneController(), testAttributeConnectorController(),
+                testPolicyEvaluationController(), testPolicyManagementController(),
+                testResourcePrivilegeManagementController(), testSubjectPrivilegeManagementController());
+    }
+
+    private Object[][] testHealth() {
+        return new Object[][] { get(HEALTH_URI, httpHeaders(MALFORMED_BEARER_TOKEN)) };
+    }
+
+    private Object[][] testZoneController() {
+        return new Object[][] { put(ZONE_URI, httpHeaders(MALFORMED_BEARER_TOKEN)),
+                get(ZONE_URI, httpHeaders(MALFORMED_BEARER_TOKEN)),
+                delete(ZONE_URI, httpHeaders(MALFORMED_BEARER_TOKEN)) };
+    }
+
+    private Object[][] testAttributeConnectorController() {
+        return new Object[][] { putWithMalformedBearerToken(RESOURCE_CONNECTOR_URI),
+                getWithMalformedBearerToken(RESOURCE_CONNECTOR_URI),
+                deleteWithMalformedBearerToken(RESOURCE_CONNECTOR_URI),
+                putWithMalformedBearerToken(SUBJECT_CONNECTOR_URI), getWithMalformedBearerToken(SUBJECT_CONNECTOR_URI),
+                deleteWithMalformedBearerToken(SUBJECT_CONNECTOR_URI) };
+    }
+
+    private Object[][] testPolicyEvaluationController() {
+        return new Object[][] { postWithMalformedBearerToken(POLICY_EVAL_URI) };
+    }
+
+    private Object[][] testPolicyManagementController() {
+        return new Object[][] { putWithMalformedBearerToken(POLICY_SET_URI),
+                getWithMalformedBearerToken(POLICY_SET_URI), deleteWithMalformedBearerToken(POLICY_SET_URI),
+                getWithMalformedBearerToken(POLICY_SETS_URI) };
+    }
+
+    private Object[][] testResourcePrivilegeManagementController() {
+        return new Object[][] { postWithMalformedBearerToken(RESOURCES_URI), getWithMalformedBearerToken(RESOURCES_URI),
+                getWithMalformedBearerToken(RESOURCE_URI), putWithMalformedBearerToken(RESOURCE_URI),
+                deleteWithMalformedBearerToken(RESOURCE_URI) };
+    }
+
+    private Object[][] testSubjectPrivilegeManagementController() {
+        return new Object[][] { postWithMalformedBearerToken(SUBJECTS_URI), getWithMalformedBearerToken(SUBJECTS_URI),
+                getWithMalformedBearerToken(SUBJECT_URI), putWithMalformedBearerToken(SUBJECT_URI),
+                deleteWithMalformedBearerToken(SUBJECT_URI) };
+    }
+
+    private static Object[] postWithMalformedBearerToken(final URI uri) {
+        return post(uri, httpZoneHeaders(MALFORMED_BEARER_TOKEN));
+    }
+
+    private static Object[] post(final URI uri, final HttpHeaders headers) {
+        return new Object[] { MockMvcRequestBuilders.post(uri).headers(headers)
+                .content(CONTENT).contentType(MediaType.APPLICATION_JSON) };
+    }
+
+    private static Object[] putWithMalformedBearerToken(final URI uri) {
+        return put(uri, httpZoneHeaders(MALFORMED_BEARER_TOKEN));
+    }
+
+    private static Object[] put(final URI uri, final HttpHeaders headers) {
+        return new Object[] { MockMvcRequestBuilders.put(uri).headers(headers).content(CONTENT)
+                .contentType(MediaType.APPLICATION_JSON) };
+    }
+
+    private static Object[] getWithMalformedBearerToken(final URI uri) {
+        return get(uri, httpZoneHeaders(MALFORMED_BEARER_TOKEN));
+    }
+
+    private static Object[] get(final URI uri, final HttpHeaders headers) {
+        return new Object[] { MockMvcRequestBuilders.get(uri).headers(headers).accept(MediaType.APPLICATION_JSON) };
+    }
+
+    private static Object[] deleteWithMalformedBearerToken(final URI uri) {
+        return delete(uri, httpZoneHeaders(MALFORMED_BEARER_TOKEN));
+    }
+
+    private static Object[] delete(final URI uri, final HttpHeaders headers) {
+        return new Object[] { MockMvcRequestBuilders.delete(uri).headers(headers) };
+    }
+
+    private static HttpHeaders httpHeaders(final String token) {
+        HttpHeaders httpHeaders = new HttpHeaders();
+        httpHeaders.add(HttpHeaders.AUTHORIZATION, token);
+        return httpHeaders;
+    }
+
+    private static HttpHeaders httpZoneHeaders(final String token) {
+        HttpHeaders httpHeaders = httpHeaders(token);
+        httpHeaders.add("Predix-Zone-Id", "myzone");
+        return httpHeaders;
+    }
+
+    private static Object[][] combine(final Object[][]... testData) {
+        List<Object[]> result = Lists.newArrayList();
+        for (Object[][] t : testData) {
+            result.addAll(Arrays.asList(t));
+        }
+        return result.toArray(new Object[result.size()][]);
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/service/policy/admin/PolicyHttpMethodsFilterTest.java b/service/src/test/java/com/ge/predix/acs/service/policy/admin/PolicyHttpMethodsFilterTest.java
new file mode 100644
index 0000000..746f747
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/service/policy/admin/PolicyHttpMethodsFilterTest.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.admin;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mockito.InjectMocks;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpMethod;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public final class PolicyHttpMethodsFilterTest {
+
+    @InjectMocks
+    private PolicyManagementController policyManagementController;
+
+    private static final Set<HttpMethod> ALL_HTTP_METHODS = new HashSet<>(Arrays.asList(HttpMethod.values()));
+
+    private MockMvc mockMvc;
+
+    @BeforeClass
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        this.mockMvc = MockMvcBuilders.standaloneSetup(this.policyManagementController)
+                .addFilters(new PolicyHttpMethodsFilter()).build();
+    }
+
+    @Test(dataProvider = "urisAndTheirAllowedHttpMethods")
+    public void testUriPatternsAndTheirAllowedHttpMethods(final String uri, final Set<HttpMethod> allowedHttpMethods)
+            throws Exception {
+        Set<HttpMethod> disallowedHttpMethods = new HashSet<>(ALL_HTTP_METHODS);
+        disallowedHttpMethods.removeAll(allowedHttpMethods);
+        for (HttpMethod disallowedHttpMethod : disallowedHttpMethods) {
+            this.mockMvc.perform(MockMvcRequestBuilders.request(disallowedHttpMethod, URI.create(uri)))
+                    .andExpect(MockMvcResultMatchers.status().isMethodNotAllowed());
+        }
+    }
+
+    @DataProvider
+    public Object[][] urisAndTheirAllowedHttpMethods() {
+        return new Object[][] {
+                new Object[] { "/v1/policy-set/foo",
+                        new HashSet<>(Arrays.asList(HttpMethod.PUT, HttpMethod.GET, HttpMethod.DELETE, HttpMethod.HEAD,
+                                HttpMethod.OPTIONS)) },
+                { "/v1/policy-set",
+                        new HashSet<>(Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS)) } };
+    }
+}
\ No newline at end of file
diff --git a/service/src/test/java/com/ge/predix/acs/service/policy/admin/PolicyManagementServiceTest.java b/service/src/test/java/com/ge/predix/acs/service/policy/admin/PolicyManagementServiceTest.java
new file mode 100644
index 0000000..8414037
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/service/policy/admin/PolicyManagementServiceTest.java
@@ -0,0 +1,311 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.admin;
+
+import static org.mockito.Mockito.mock;
+
+import java.util.List;
+
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.SpringSecurityPolicyContextResolver;
+import com.ge.predix.acs.attribute.cache.AttributeCacheFactory;
+import com.ge.predix.acs.attribute.connector.management.AttributeConnectorServiceImpl;
+import com.ge.predix.acs.attribute.readers.AttributeReaderFactory;
+import com.ge.predix.acs.attribute.readers.PrivilegeServiceResourceAttributeReader;
+import com.ge.predix.acs.attribute.readers.PrivilegeServiceSubjectAttributeReader;
+import com.ge.predix.acs.commons.policy.condition.groovy.GroovyConditionCache;
+import com.ge.predix.acs.commons.policy.condition.groovy.GroovyConditionShell;
+import com.ge.predix.acs.config.InMemoryDataSourceConfig;
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.policy.evaluation.cache.InMemoryPolicyEvaluationCache;
+import com.ge.predix.acs.privilege.management.PrivilegeManagementServiceImpl;
+import com.ge.predix.acs.privilege.management.dao.ResourceRepositoryProxy;
+import com.ge.predix.acs.privilege.management.dao.SubjectRepositoryProxy;
+import com.ge.predix.acs.service.InvalidACSRequestException;
+import com.ge.predix.acs.service.policy.validation.PolicySetValidator;
+import com.ge.predix.acs.service.policy.validation.PolicySetValidatorImpl;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.utils.JsonUtils;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.ge.predix.acs.zone.management.dao.ZoneRepository;
+import com.ge.predix.acs.zone.resolver.SpringSecurityZoneResolver;
+import com.ge.predix.acs.zone.resolver.ZoneResolver;
+
+@Test
+@TestPropertySource("classpath:application.properties")
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+@ContextConfiguration(classes = { InMemoryPolicyEvaluationCache.class, InMemoryDataSourceConfig.class,
+        AttributeCacheFactory.class, PolicyManagementServiceImpl.class, SpringSecurityPolicyContextResolver.class,
+        PolicySetValidatorImpl.class, GroovyConditionShell.class, SpringSecurityZoneResolver.class,
+        GroovyConditionCache.class, AttributeConnectorServiceImpl.class, AttributeReaderFactory.class,
+        PrivilegeServiceResourceAttributeReader.class, PrivilegeServiceSubjectAttributeReader.class,
+        PrivilegeManagementServiceImpl.class, SubjectRepositoryProxy.class, ResourceRepositoryProxy.class })
+public class PolicyManagementServiceTest extends AbstractTransactionalTestNGSpringContextTests {
+
+    private static final String SUBDOMAIN1 = "tenant1";
+    private static final String SUBDOMAIN2 = "tenant2";
+    private static final String DEFAULT_SUBDOMAIN = "defaultTenant";
+
+    @Autowired
+    @Spy
+    private PolicySetValidator policySetValidator;
+
+    @Autowired
+    @InjectMocks
+    private PolicyManagementServiceImpl policyService;
+
+    @Autowired
+    private ZoneRepository zoneRepository;
+
+    @Mock
+    private final ZoneResolver mockZoneResolver = mock(ZoneResolver.class);
+
+    private final JsonUtils jsonUtils = new JsonUtils();
+
+    private final ZoneEntity zone1 = this.createZone("zone1", SUBDOMAIN1, "description for Zone1");
+    private final ZoneEntity zone2 = this.createZone("zone2", SUBDOMAIN2, "description for Zone2");
+    private final ZoneEntity defaultZone = this
+            .createZone("defaultZone", DEFAULT_SUBDOMAIN, "description for defaultZone");
+
+    @BeforeClass
+    public void beforeClass() {
+        this.zoneRepository.save(this.zone1);
+        this.zoneRepository.save(this.zone2);
+        this.zoneRepository.save(this.defaultZone);
+    }
+
+    @AfterClass
+    public void afterClass() {
+
+        this.zoneRepository.delete(this.defaultZone);
+        this.zoneRepository.delete(this.zone1);
+        this.zoneRepository.delete(this.zone2);
+    }
+
+    @BeforeMethod
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        initializeDefaultResolverBehavior();
+    }
+
+    public void testDeleteWhenPolicySetExists() {
+        PolicySet policySet = this.jsonUtils.deserializeFromFile("set-with-1-policy.json", PolicySet.class);
+        this.policyService.upsertPolicySet(policySet);
+        this.policyService.deletePolicySet(policySet.getName());
+        Mockito.verify(this.policySetValidator, Mockito.times(1)).removeCachedConditions(Mockito.any());
+        PolicySet retrievedPolicySet = this.policyService.getPolicySet(policySet.getName());
+        Assert.assertNull(retrievedPolicySet);
+    }
+
+    public void testDeleteWhenPolicySetDoesNotExists() {
+        this.policyService.deletePolicySet("policyId");
+        Assert.assertTrue(true); // no exception throw means, the test passed
+    }
+
+    public void testDeleteWhenPolicySetIdIsNull() {
+        this.policyService.deletePolicySet(null);
+        Assert.assertTrue(true); // no exception throw means, the test passed
+    }
+
+    public void testCreatePolicySetPositive() {
+        PolicySet policySet = this.jsonUtils.deserializeFromFile("set-with-1-policy.json", PolicySet.class);
+        String policyName = policySet.getName();
+        this.policyService.upsertPolicySet(policySet);
+        PolicySet savedPolicySet = this.policyService.getPolicySet(policyName);
+        Assert.assertNotNull(savedPolicySet);
+        Assert.assertEquals(savedPolicySet.getPolicies().size(), 1);
+        Assert.assertEquals(savedPolicySet.getPolicies().get(0).getTarget().getResource().getUriTemplate(),
+                "/secured-by-value/sites/sanramon");
+        this.policyService.deletePolicySet(policyName);
+        Assert.assertEquals(this.policyService.getAllPolicySets().size(), 0);
+    }
+
+    @Test
+    public void testCreateApmPolicySetPositive() {
+        PolicySet policySet = this.jsonUtils
+                .deserializeFromFile("testApmPolicySetLoadsSuccessfully.json", PolicySet.class);
+        String policyName = policySet.getName();
+        this.policyService.upsertPolicySet(policySet);
+        PolicySet savedPolicySet = this.policyService.getPolicySet(policyName);
+        Assert.assertNotNull(savedPolicySet);
+        Assert.assertEquals(savedPolicySet.getName(), policyName);
+        this.policyService.deletePolicySet(policyName);
+        Assert.assertEquals(this.policyService.getAllPolicySets().size(), 0);
+    }
+
+    public void testUpdatePolicySet() {
+        PolicySet policySet = this.jsonUtils.deserializeFromFile("set-with-1-policy.json", PolicySet.class);
+
+        this.policyService.upsertPolicySet(policySet);
+
+        PolicySet retPolicySet = this.policyService.getPolicySet(policySet.getName());
+        Assert.assertEquals(retPolicySet.getName(), policySet.getName());
+        Assert.assertEquals(retPolicySet.getPolicies().size(), policySet.getPolicies().size());
+
+        // Now we want to update
+        policySet.getPolicies().get(0).setEffect(Effect.DENY);
+        this.policyService.upsertPolicySet(policySet);
+
+        // get the policy back
+        retPolicySet = this.policyService.getPolicySet(policySet.getName());
+
+        Assert.assertEquals(retPolicySet.getPolicies().get(0).getEffect(), Effect.DENY);
+
+        this.policyService.deletePolicySet(policySet.getName());
+        Assert.assertEquals(this.policyService.getAllPolicySets().size(), 0);
+
+    }
+
+    public void testCreateMultiplePolicySets() {
+        PolicySet policySet = this.jsonUtils.deserializeFromFile("set-with-1-policy.json", PolicySet.class);
+        PolicySet policySet2 = this.jsonUtils
+                .deserializeFromFile("policy-set-with-one-policy-one-condition.json", PolicySet.class);
+
+        this.policyService.upsertPolicySet(policySet);
+        try {
+            this.policyService.upsertPolicySet(policySet2);
+            List<PolicySet> expectedPolicySets = this.policyService.getAllPolicySets();
+            Assert.assertEquals(expectedPolicySets.size(), 2);
+        } catch (PolicyManagementException e) {
+            Assert.fail("Creation of 2nd policySet failed.");
+        } finally {
+            this.policyService.deletePolicySet(policySet.getName());
+            this.policyService.deletePolicySet(policySet2.getName());
+            Assert.assertEquals(this.policyService.getAllPolicySets().size(), 0);
+        }
+    }
+
+    @Test(expectedExceptions = { PolicyManagementException.class })
+    public void testCreatePolicySetWithInvalidConditions() {
+        PolicySet policySet = this.jsonUtils
+                .deserializeFromFile("policy-set-with-one-policy-invalid-condition.json", PolicySet.class);
+        this.policyService.upsertPolicySet(policySet);
+    }
+
+    @Test(expectedExceptions = { PolicyManagementException.class })
+    public void testCreatePolicySetWithInvalidJson() {
+        PolicySet policySet = this.jsonUtils
+                .deserializeFromFile("policyset/validator/test/missing-effect-policy.json", PolicySet.class);
+        this.policyService.upsertPolicySet(policySet);
+    }
+
+    @Test(expectedExceptions = { PolicyManagementException.class })
+    public void testCreatePolicySetWithMissingClientId() {
+        Mockito.when(this.mockZoneResolver.getZoneEntityOrFail()).thenThrow(new InvalidACSRequestException());
+        this.createSimplePolicySet();
+    }
+
+    @Test(expectedExceptions = { PolicyManagementException.class })
+    public void testCreatePolicySetWithMissingIssuer() {
+        Mockito.when(this.mockZoneResolver.getZoneEntityOrFail()).thenThrow(new InvalidACSRequestException());
+        this.createSimplePolicySet();
+    }
+
+    public void testCreatePolicySetsForMultipleApplications() {
+        PolicySet client1PolicySet = this.jsonUtils.deserializeFromFile("set-with-1-policy.json", PolicySet.class);
+        PolicySet client2PolicySet = this.jsonUtils
+                .deserializeFromFile("policy-set-with-one-policy-one-condition.json", PolicySet.class);
+
+        Mockito.when(this.mockZoneResolver.getZoneEntityOrFail()).thenReturn(this.zone1);
+
+        this.policyService.upsertPolicySet(client1PolicySet);
+        Assert.assertEquals(this.policyService.getAllPolicySets().size(), 1);
+        Assert.assertEquals(this.policyService.getAllPolicySets().get(0).getName(), client1PolicySet.getName());
+
+        // Add and assert policyset for client2, with client1 policySet already
+        // created
+
+        Mockito.when(this.mockZoneResolver.getZoneEntityOrFail()).thenReturn(this.zone2);
+        this.policyService.upsertPolicySet(client2PolicySet);
+        Assert.assertEquals(this.policyService.getAllPolicySets().size(), 1);
+        Assert.assertEquals(this.policyService.getAllPolicySets().get(0).getName(), client2PolicySet.getName());
+
+        this.policyService.deletePolicySet(client2PolicySet.getName());
+
+        // Cleanup PolicySet for client1
+        Mockito.when(this.mockZoneResolver.getZoneEntityOrFail()).thenReturn(this.zone1);
+        this.policyService.deletePolicySet(client1PolicySet.getName());
+        Assert.assertEquals(this.policyService.getAllPolicySets().size(), 0);
+    }
+
+    // TODO Enable this test once the service is updated to use real zones
+    @Test(enabled = false)
+    public void testGetAllPolicySetAndReturnEmptyList() {
+        Mockito.when(this.mockZoneResolver.getZoneEntityOrFail()).thenReturn(this.zone1);
+
+        List<PolicySet> allPolicySets = this.policyService.getAllPolicySets();
+        Assert.assertEquals(allPolicySets.size(), 0);
+    }
+
+    public void testCreatePolicySetsForMultipleZones() {
+        PolicySet issuer1PolicySet = this.jsonUtils.deserializeFromFile("set-with-1-policy.json", PolicySet.class);
+        PolicySet issuer2PolicySet = this.jsonUtils
+                .deserializeFromFile("policy-set-with-one-policy-one-condition.json", PolicySet.class);
+
+        Mockito.when(this.mockZoneResolver.getZoneEntityOrFail()).thenReturn(this.zone1);
+        this.policyService.upsertPolicySet(issuer1PolicySet);
+
+        Mockito.when(this.mockZoneResolver.getZoneEntityOrFail()).thenReturn(this.zone2);
+        this.policyService.upsertPolicySet(issuer2PolicySet);
+
+        Assert.assertEquals(this.policyService.getAllPolicySets().size(), 1);
+
+        this.policyService.deletePolicySet(issuer2PolicySet.getName());
+        // need this to delete issuer1PolicySet properly (policy-set-id and
+        // zone_id are used to find the row)
+        Mockito.when(this.mockZoneResolver.getZoneEntityOrFail()).thenReturn(this.zone1);
+        this.policyService.deletePolicySet(issuer1PolicySet.getName());
+        Assert.assertEquals(this.policyService.getAllPolicySets().size(), 0);
+
+    }
+
+    private void createSimplePolicySet() {
+        PolicySet policySet = this.jsonUtils.deserializeFromFile("set-with-1-policy.json", PolicySet.class);
+        this.policyService.upsertPolicySet(policySet);
+    }
+
+    private void initializeDefaultResolverBehavior() {
+        Mockito.when(this.mockZoneResolver.getZoneEntityOrFail()).thenReturn(this.defaultZone);
+    }
+
+    private ZoneEntity createZone(final String name, final String subdomain, final String description) {
+        ZoneEntity zone = new ZoneEntity();
+        zone.setName(name);
+        zone.setSubdomain(subdomain);
+        zone.setDescription(description);
+        return zone;
+    }
+
+}
diff --git a/service/src/test/java/com/ge/predix/acs/service/policy/admin/dao/PolicySetRepositoryTest.java b/service/src/test/java/com/ge/predix/acs/service/policy/admin/dao/PolicySetRepositoryTest.java
new file mode 100644
index 0000000..59d4589
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/service/policy/admin/dao/PolicySetRepositoryTest.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.admin.dao;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.config.InMemoryDataSourceConfig;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.ge.predix.acs.zone.management.dao.ZoneRepository;
+
+@ContextConfiguration(classes = InMemoryDataSourceConfig.class)
+@EnableAutoConfiguration
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+public class PolicySetRepositoryTest extends AbstractTransactionalTestNGSpringContextTests {
+    private static final String SUBDOMAIN = "PolicySetRepositoryTest-acs";
+
+    @Autowired
+    private PolicySetRepository policySetRepository;
+
+    @Autowired
+    private ZoneRepository zoneRepository;
+
+    @Test
+    public void testPersistPolicy() {
+
+        ZoneEntity zone = createZone();
+        this.zoneRepository.save(zone);
+
+        PolicySetEntity policySetEntity = new PolicySetEntity(zone, "policy-set-2", "{}");
+        PolicySetEntity savedPolicySet = this.policySetRepository.save(policySetEntity);
+        Assert.assertEquals(this.policySetRepository.count(), 1);
+        Assert.assertTrue(savedPolicySet.getId() > 0);
+    }
+
+    private ZoneEntity createZone() {
+        ZoneEntity zone = new ZoneEntity();
+        zone.setName("PolicySetRepositoryTest-ACS");
+        zone.setSubdomain(SUBDOMAIN);
+        zone.setDescription("PolicySetRepositoryTest zone description");
+        return zone;
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/ConditionEvaluationTest.java b/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/ConditionEvaluationTest.java
new file mode 100644
index 0000000..a42b211
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/ConditionEvaluationTest.java
@@ -0,0 +1,220 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.mockito.internal.util.reflection.Whitebox;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.commons.policy.condition.groovy.GroovyConditionCache;
+import com.ge.predix.acs.commons.policy.condition.groovy.GroovyConditionShell;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Condition;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.service.policy.validation.PolicySetValidator;
+import com.ge.predix.acs.service.policy.validation.PolicySetValidatorImpl;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@ContextConfiguration(
+        classes = { GroovyConditionCache.class, GroovyConditionShell.class, PolicySetValidatorImpl.class })
+public class ConditionEvaluationTest extends AbstractTestNGSpringContextTests {
+
+    @Autowired
+    private PolicySetValidator policySetValidator;
+
+    @BeforeClass
+    public void setup() {
+        ((PolicySetValidatorImpl) this.policySetValidator)
+                .setValidAcsPolicyHttpActions("GET, POST, PUT, DELETE, PATCH, SUBSCRIBE, MESSAGE");
+        ((PolicySetValidatorImpl) this.policySetValidator).init();
+    }
+
+    @Test(dataProvider = "conditionsProvider")
+    public void testConditionEvaluationWithConstants(final List<Condition> conditions, final boolean expectedResult,
+            final boolean throwsException) {
+        PolicyEvaluationServiceImpl evaluationService = new PolicyEvaluationServiceImpl();
+        Whitebox.setInternalState(evaluationService, "policySetValidator", this.policySetValidator);
+        Set<Attribute> subjectAttributes = Collections.emptySet();
+        try {
+            Assert.assertEquals(evaluationService.evaluateConditions(subjectAttributes, new HashSet<>(), "",
+                    conditions, ""), expectedResult);
+        } catch (Exception e) {
+            if (throwsException) {
+                Assert.assertTrue(e instanceof PolicyEvaluationException);
+            }
+        }
+    }
+
+    @DataProvider(name = "conditionsProvider")
+    public Object[][] getConditions() {
+        Object[][] data = new Object[][] { { Arrays.asList(new Condition("\"a\" == \"a\"")), true, false },
+                { Arrays.asList(new Condition("\"a\" == \"b\"")), false, false },
+                { Arrays.asList(new Condition("")), false, true }, { Collections.emptyList(), true, true }, // TODO:
+                                                                                                            // Should
+                // no
+                // exception
+                // just
+                // return the
+                // effect
+                // if the
+                // policy
+                // matched
+                { Arrays.asList(new Condition("\"a\" == \"a\""), new Condition("\"b\" == \"b\"")), true, false },
+                { Arrays.asList(new Condition("\"a\" == \"a\""), new Condition("\"a\" == \"b\"")), false, false },
+                { Arrays.asList(new Condition("\"a\" == \"b\""), new Condition("\"c\" == \"b\"")), false, false },
+                { null, true, false }
+
+        };
+        return data;
+    }
+
+    @Test(dataProvider = "conditionsWithVariablesProvider")
+    public void testConditionEvaluationWithVariables(final Set<Attribute> resourceAttributes,
+            final Set<Attribute> subjectAttributes, final List<Condition> conditions, final boolean expectedResult,
+            final boolean throwsException, final String resourceURI, final String resourceURITemplate) {
+
+        BaseResource resource = new BaseResource();
+        resource.setAttributes(resourceAttributes);
+
+        BaseSubject subject = new BaseSubject();
+        subject.setAttributes(subjectAttributes);
+
+        PolicyEvaluationServiceImpl evaluationService = new PolicyEvaluationServiceImpl();
+        Whitebox.setInternalState(evaluationService, "policySetValidator", this.policySetValidator);
+
+        try {
+            Assert.assertEquals(evaluationService.evaluateConditions(subjectAttributes, resourceAttributes, resourceURI,
+                    conditions, resourceURITemplate), expectedResult);
+
+        } catch (Exception e) {
+
+            if (throwsException) {
+                Assert.assertTrue(e instanceof PolicyEvaluationException);
+            } else {
+                Assert.fail("Unexpected exception.", e);
+            }
+
+        }
+
+    }
+
+    @DataProvider(name = "conditionsWithVariablesProvider")
+    public Object[][] getConditionsWithVariables() {
+        Object[][] data = new Object[][] {
+
+                { new HashSet<>(Arrays.asList(new Attribute("acs", "site", "San Ramon"),
+                        new Attribute("acs", "site", "New York"))),
+                        new HashSet<>(Arrays.asList(new Attribute("acs", "site", "San Ramon"),
+                                new Attribute("acs", "site", "Boston"))),
+                        Arrays.asList(new Condition("match.any(subject.attributes(\"acs\", \"site\"),"
+                                + " resource.attributes(\"acs\", \"site\"))")),
+                        true, false, "", "" },
+                { new HashSet<>(Arrays.asList(new Attribute("acs", "site", "San Ramon"),
+                        new Attribute("acs", "site", "New York"))),
+                        new HashSet<>(Arrays.asList(new Attribute("acs", "site", "LA"),
+                                new Attribute("acs", "site", "Boston"))),
+                        Arrays.asList(new Condition("match.any(subject.attributes(\"acs\", \"site\"),"
+                                + " resource.attributes(\"acs\", \"site\"))")),
+                        false, false, "", "" },
+                { new HashSet<>(Arrays.asList(new Attribute("acs", "site", "San Ramon"),
+                        new Attribute("acs", "site", "New York"))),
+                        new HashSet<>(Arrays.asList(new Attribute("acs", "site", "San Ramon"),
+                                new Attribute("acs", "site", "New York"))),
+                        Arrays.asList(new Condition("match.any(subject.attributes(\"acs\", \"site\"),"
+                                + " resource.attributes(\"acs\", \"site\"))")),
+                        true, false, "", ""
+
+                }, { new HashSet<>(Arrays.asList(new Attribute("acs", "location", "San Ramon"),
+                                                 new Attribute("acs", "location", "New York"))),
+                     new HashSet<>(Arrays.asList(new Attribute("acs", "site", "San Ramon"),
+                                                 new Attribute("acs", "site", "New York"))),
+                     Arrays.asList(new Condition("match.any(resource.attributes(\"acs\", \"location\"),"
+                                                 + " subject.attributes(\"acs\", \"site\"))")), true, false, "", ""
+
+                }, { new HashSet<>(Arrays.asList(new Attribute("acs", "site", "San Ramon"),
+                                                 new Attribute("acs", "site", "New York"))),
+                     Collections.emptySet(),
+                     Arrays.asList(new Condition(
+                         "match.single(resource.attributes(\"acs\", \"site\"), \"San Ramon\")")),
+                     true, false, "", ""
+
+                }, { Collections.emptySet(), Collections.emptySet(),
+                     Arrays.asList(new Condition("resource.uriVariable(\"site_id\").equals(\"boston\")")),
+                     true, false, "http://assets.predix.io/site/boston", "site/{site_id}"
+
+                }, { Collections.emptySet(), Collections.emptySet(),
+                     Arrays.asList(new Condition("resource.uriVariable(\"site_id\").equals(\"newyork\")")),
+                     false, false, "http://assets.predix.io/site/boston", "site/{site_id}"
+
+                }, { Collections.emptySet(), new HashSet<>(Arrays.asList(new Attribute("acs", "site", "boston"),
+                                                                         new Attribute("acs", "site", "New York"))),
+                     Arrays.asList(new Condition("match.single(subject.attributes(\"acs\", \"site\"),"
+                                                 + " resource.uriVariable(\"site_id\"))")),
+                     true, false, "http://assets.predix.io/site/boston", "site/{site_id}"
+
+                }, { Collections.emptySet(), new HashSet<>(Arrays.asList(new Attribute("acs", "site", "LA"),
+                                                                         new Attribute("acs", "site", "New York"))),
+                     Arrays.asList(new Condition("match.single(subject.attributes(\"acs\", \"site\"),"
+                                                 + " resource.uriVariable(\"site_id\"))")),
+                     false, false, "http://assets.predix.io/site/boston", "site/{site_id}"
+
+                }, { Collections.emptySet(), new HashSet<>(Arrays.asList(new Attribute("acs", "site", "LA"),
+                                                                         new Attribute("acs", "site", "New York"))),
+                     Arrays.asList(new Condition("match.single(subject.attributes(\"acs\", \"site\"),"
+                                                 + " resource.uriVariable(\"site_id\"))")),
+                     false, false, "http://assets.predix.io/site", "site/{site_id}"
+
+                }, { Collections.emptySet(), new HashSet<>(Arrays.asList(new Attribute("acs", "site", "boston"),
+                                                                         new Attribute("acs", "department", "sales"))),
+                     Arrays.asList(new Condition("match.single(subject.attributes(\"acs\", \"site\"),"
+                                                 + " resource.uriVariable(\"site_id\"))"),
+                                   new Condition("match.single(subject.attributes(\"acs\", \"department\"),"
+                                                 + " resource.uriVariable(\"department_id\"))")),
+                     true, false,
+                     "http://assets.predix.io/site/boston/department/sales", "site/{site_id}/department/{department_id}"
+
+                }, { Collections.emptySet(), new HashSet<>(Arrays.asList(new Attribute("acs", "site", "boston"),
+                                                                         new Attribute("acs", "department", "sales"))),
+                     Arrays.asList(new Condition("match.single(subject.attributes(\"acs\", \"site\"),"
+                                                 + " resource.uriVariable(\"site_id\")) "
+                                                 + "and match.single(subject.attributes(\"acs\", \"department\"),"
+                                                 + " resource.uriVariable(\"department_id\"))")),
+                     true, false,
+                     "http://assets.predix.io/site/boston/department/sales", "site/{site_id}/department/{department_id}"
+
+                }, };
+        return data;
+    }
+
+}
diff --git a/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/EvaluationHttpMethodsFilterTest.java b/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/EvaluationHttpMethodsFilterTest.java
new file mode 100644
index 0000000..3055979
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/EvaluationHttpMethodsFilterTest.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mockito.InjectMocks;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpMethod;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public final class EvaluationHttpMethodsFilterTest {
+
+    @InjectMocks
+    private PolicyEvaluationController policyEvaluationController;
+
+    private static final Set<HttpMethod> ALL_HTTP_METHODS = new HashSet<>(Arrays.asList(HttpMethod.values()));
+
+    private MockMvc mockMvc;
+
+    @BeforeClass
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        this.mockMvc = MockMvcBuilders.standaloneSetup(this.policyEvaluationController)
+                .addFilters(new EvaluationHttpMethodsFilter()).build();
+    }
+
+    @Test(dataProvider = "urisAndTheirAllowedHttpMethods")
+    public void testUriPatternsAndTheirAllowedHttpMethods(final String uri, final Set<HttpMethod> allowedHttpMethods)
+            throws Exception {
+        Set<HttpMethod> disallowedHttpMethods = new HashSet<>(ALL_HTTP_METHODS);
+        disallowedHttpMethods.removeAll(allowedHttpMethods);
+        for (HttpMethod disallowedHttpMethod : disallowedHttpMethods) {
+            this.mockMvc.perform(MockMvcRequestBuilders.request(disallowedHttpMethod, URI.create(uri)))
+                    .andExpect(MockMvcResultMatchers.status().isMethodNotAllowed());
+        }
+    }
+
+    @DataProvider
+    public Object[][] urisAndTheirAllowedHttpMethods() {
+        return new Object[][] { new Object[] { "/v1/policy-evaluation",
+                new HashSet<>(Arrays.asList(HttpMethod.POST, HttpMethod.OPTIONS)) } };
+    }
+}
\ No newline at end of file
diff --git a/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationServiceTest.java b/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationServiceTest.java
new file mode 100644
index 0000000..47e6548
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationServiceTest.java
@@ -0,0 +1,516 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyListOf;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.internal.util.reflection.Whitebox;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ge.predix.acs.PolicyContextResolver;
+import com.ge.predix.acs.commons.policy.condition.groovy.GroovyConditionCache;
+import com.ge.predix.acs.commons.policy.condition.groovy.GroovyConditionShell;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.model.Policy;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.policy.evaluation.cache.PolicyEvaluationCache;
+import com.ge.predix.acs.policy.evaluation.cache.PolicyEvaluationRequestCacheKey;
+import com.ge.predix.acs.privilege.management.PrivilegeManagementService;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+import com.ge.predix.acs.service.policy.admin.PolicyManagementService;
+import com.ge.predix.acs.service.policy.matcher.MatchResult;
+import com.ge.predix.acs.service.policy.matcher.PolicyMatchCandidate;
+import com.ge.predix.acs.service.policy.matcher.PolicyMatcher;
+import com.ge.predix.acs.service.policy.validation.PolicySetValidator;
+import com.ge.predix.acs.service.policy.validation.PolicySetValidatorImpl;
+import com.ge.predix.acs.utils.JsonUtils;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.ge.predix.acs.zone.resolver.ZoneResolver;
+
+/**
+ * Unit tests for PolicyEvaluationService. Uses mocks, no external dependencies.
+ *
+ * @author acs-engineers@ge.com
+ */
+@Test
+@ContextConfiguration(
+        classes = { GroovyConditionCache.class, GroovyConditionShell.class, PolicySetValidatorImpl.class })
+public class PolicyEvaluationServiceTest extends AbstractTestNGSpringContextTests {
+    private static final String ISSUER = "https://acs.attributes.int";
+    private static final String SUBJECT_ATTRIB_NAME_ROLE = "role";
+    private static final String SUBJECT_ATTRIB_VALUE_ANALYST = "analyst";
+    private static final String RES_ATTRIB_ROLE_REQUIRED_VALUE = "administrator";
+    private static final String SUBJECT_ATTRIB_VALUE_ADMIN = "administrator";
+    private static final String RES_ATTRIB_ROLE_REQUIRED = "role_required";
+    private static final String RES_ATTRIB_LOCATION = "location";
+    private static final String RES_ATTRIB_LOCATION_VALUE = "sanramon";
+
+    private final JsonUtils jsonUtils = new JsonUtils();
+
+    @InjectMocks
+    private PolicyEvaluationServiceImpl evaluationService;
+    @Mock
+    private PolicyManagementService policyService;
+    @Mock
+    private PrivilegeManagementService privilegeManagementService;
+    @Mock
+    private PolicyMatcher policyMatcher;
+    @Mock
+    private PolicyContextResolver policyScopeResolver;
+    @Mock
+    private ZoneResolver zoneResolver;
+    @Mock
+    private PolicyEvaluationCache cache;
+    @Autowired
+    private PolicySetValidator policySetValidator;
+
+    private static final Set<Attribute> EMPTY_ATTRS = Collections.emptySet();
+
+    @BeforeClass
+    public void setupClass() {
+        ((PolicySetValidatorImpl) this.policySetValidator)
+                .setValidAcsPolicyHttpActions("GET, POST, PUT, DELETE, PATCH");
+        ((PolicySetValidatorImpl) this.policySetValidator).init();
+    }
+
+    @BeforeMethod
+    public void setupMethod() throws Exception {
+        this.evaluationService = new PolicyEvaluationServiceImpl();
+        Whitebox.setInternalState(this.evaluationService, "policySetValidator", this.policySetValidator);
+        MockitoAnnotations.initMocks(this);
+        when(this.zoneResolver.getZoneEntityOrFail()).thenReturn(new ZoneEntity(0L, "testzone"));
+        when(this.cache.get(any(PolicyEvaluationRequestCacheKey.class))).thenReturn(null);
+    }
+
+    @Test(dataProvider = "policyRequestParameterProvider",
+            expectedExceptions = IllegalArgumentException.class)
+    public void testEvaluateWithNullParameters(final String resource, final String subject, final String action) {
+        this.evaluationService.evalPolicy(createRequest(resource, subject, action));
+    }
+
+    public void testEvaluateWithNoPolicySet() {
+        PolicyEvaluationResult result = this.evaluationService
+                .evalPolicy(createRequest("resource1", "subject1", "GET"));
+        Assert.assertEquals(result.getEffect(), Effect.NOT_APPLICABLE);
+        Assert.assertEquals(result.getResourceAttributes().size(), 0);
+        Assert.assertEquals(result.getSubjectAttributes().size(), 0);
+    }
+
+    public void testEvaluateWithOnePolicySetNoPolicies() {
+        List<PolicySet> policySets = new ArrayList<>();
+        policySets.add(new PolicySet());
+        when(this.policyService.getAllPolicySets()).thenReturn(policySets);
+        List<MatchedPolicy> matchedPolicies = Collections.emptyList();
+        when(this.policyMatcher.matchForResult(any(PolicyMatchCandidate.class), anyListOf(Policy.class)))
+                .thenReturn(new MatchResult(matchedPolicies, new HashSet<String>()));
+        PolicyEvaluationResult evalPolicy = this.evaluationService
+                .evalPolicy(createRequest("resource1", "subject1", "GET"));
+        Assert.assertEquals(evalPolicy.getEffect(), Effect.NOT_APPLICABLE);
+    }
+
+    @Test(dataProvider = "policyDataProvider")
+    public void testEvaluateWithPolicy(final File inputPolicy, final Effect effect)
+            throws JsonParseException, JsonMappingException, IOException {
+        initializePolicyMock(inputPolicy);
+        PolicyEvaluationResult evalPolicy = this.evaluationService
+                .evalPolicy(createRequest("resource1", "subject1", "GET"));
+        Assert.assertEquals(evalPolicy.getEffect(), effect);
+    }
+
+    @Test(dataProvider = "policyDataProviderForTestWithAttributes")
+    public void testEvaluateWithPolicyAndSubjectResourceAttributes(final String acsSubjectAttributeValue,
+            final File inputPolicy, final Effect effect, final Set<Attribute> subjectAttributes)
+            throws JsonParseException, JsonMappingException, IOException {
+
+        Set<Attribute> resourceAttributes = new HashSet<>();
+        Attribute roleAttribute = new Attribute(ISSUER, RES_ATTRIB_ROLE_REQUIRED, RES_ATTRIB_ROLE_REQUIRED_VALUE);
+        resourceAttributes.add(roleAttribute);
+        Attribute locationAttribute = new Attribute(ISSUER, RES_ATTRIB_LOCATION, RES_ATTRIB_LOCATION_VALUE);
+        resourceAttributes.add(locationAttribute);
+
+        Set<Attribute> mergedSubjectAttributes = new HashSet<>(subjectAttributes);
+        mergedSubjectAttributes.addAll(getSubjectAttributes(acsSubjectAttributeValue));
+        initializePolicyMock(inputPolicy, resourceAttributes, mergedSubjectAttributes);
+        when(this.privilegeManagementService.getByResourceIdentifier(anyString())).thenReturn(this.getResource());
+        when(this.privilegeManagementService.getBySubjectIdentifier(anyString()))
+                .thenReturn(this.getSubject(acsSubjectAttributeValue));
+        PolicyEvaluationResult evalPolicyResponse = this.evaluationService
+                .evalPolicy(createRequest("resource1", "subject1", "GET"));
+        Assert.assertEquals(evalPolicyResponse.getEffect(), effect);
+        Assert.assertTrue(evalPolicyResponse.getResourceAttributes().contains(roleAttribute));
+        Assert.assertTrue(evalPolicyResponse.getResourceAttributes().contains(locationAttribute));
+        if (acsSubjectAttributeValue != null) {
+            Assert.assertTrue(evalPolicyResponse.getSubjectAttributes()
+                    .contains(new Attribute(ISSUER, SUBJECT_ATTRIB_NAME_ROLE, acsSubjectAttributeValue)));
+        }
+
+        for (Attribute attribute : subjectAttributes) {
+            Assert.assertTrue(evalPolicyResponse.getSubjectAttributes().contains(attribute));
+        }
+
+    }
+
+    @Test(dataProvider = "filterPolicySetsInvalidRequestDataProvider",
+            expectedExceptions = IllegalArgumentException.class)
+    public void testFilterPolicySetsByPriorityForInvalidRequest(final List<PolicySet> allPolicySets,
+            final LinkedHashSet<String> policySetsPriority) {
+        this.evaluationService.filterPolicySetsByPriority("subject1", "resource1", allPolicySets, policySetsPriority);
+    }
+
+    @Test(dataProvider = "filterPolicySetsDataProvider")
+    public void testFilterPolicySetsByPriority(final List<PolicySet> allPolicySets,
+            final LinkedHashSet<String> policySetsPriority, final LinkedHashSet<PolicySet> expectedFilteredPolicySets) {
+        LinkedHashSet<PolicySet> actualFilteredPolicySets = this.evaluationService
+                .filterPolicySetsByPriority("subject1", "resource1", allPolicySets, policySetsPriority);
+        Assert.assertEquals(actualFilteredPolicySets, expectedFilteredPolicySets);
+    }
+
+    @Test(dataProvider = "multiplePolicySetsRequestDataProvider")
+    public void testEvaluateWithMultiplePolicySets(final List<PolicySet> allPolicySets,
+            final LinkedHashSet<String> policySetsPriority, final Effect effect) {
+        when(this.policyService.getAllPolicySets()).thenReturn(allPolicySets);
+        when(this.policyMatcher.matchForResult(any(PolicyMatchCandidate.class), anyListOf(Policy.class)))
+                .thenAnswer(new Answer<MatchResult>() {
+                    public MatchResult answer(final InvocationOnMock invocation) {
+                        Object[] args = invocation.getArguments();
+                        @SuppressWarnings("unchecked")
+                        List<Policy> policyList = (List<Policy>) args[1];
+                        List<MatchedPolicy> matchedPolicies = new ArrayList<>();
+                        // Mocking the policyMatcher to return all policies as matched.
+                        policyList.forEach(
+                                policy -> matchedPolicies.add(new MatchedPolicy(policy, EMPTY_ATTRS, EMPTY_ATTRS)));
+                        return new MatchResult(matchedPolicies, new HashSet<String>());
+                    }
+                });
+
+        PolicyEvaluationResult result = this.evaluationService
+                .evalPolicy(createRequest("anyresource", "anysubject", "GET", policySetsPriority));
+        Assert.assertEquals(result.getEffect(), effect);
+    }
+
+    @Test
+    public void testPolicyEvaluationExceptionHandling() {
+        List<PolicySet> twoPolicySets = createNotApplicableAndDenyPolicySets();
+        when(this.policyService.getAllPolicySets()).thenReturn(twoPolicySets);
+        when(this.policyMatcher.matchForResult(any(PolicyMatchCandidate.class), anyListOf(Policy.class)))
+                .thenThrow(new RuntimeException("This policy matcher is designed to throw an exception."));
+
+        PolicyEvaluationResult result = this.evaluationService.evalPolicy(
+                createRequest("anyresource", "anysubject", "GET",
+                        Stream.of(twoPolicySets.get(0).getName(), twoPolicySets.get(1).getName())
+                                .collect(Collectors.toCollection(LinkedHashSet::new))));
+
+        Mockito.verify(this.cache, Mockito.times(0)).set(Mockito.any(), Mockito.any());
+
+        Assert.assertEquals(result.getEffect(), Effect.INDETERMINATE);
+    }
+
+    @Test(dataProvider = "policyDataProvider")
+    public void testEvaluateWithPolicyWithCacheGetException(final File inputPolicy, final Effect effect)
+            throws JsonParseException, JsonMappingException, IOException {
+        when(this.cache.get(Mockito.any(PolicyEvaluationRequestCacheKey.class))).thenThrow(new RuntimeException());
+        testEvaluateWithPolicy(inputPolicy, effect);
+    }
+
+    @Test(dataProvider = "policyDataProvider")
+    public void testEvaluateWithPolicyWithCacheSetException(final File inputPolicy, final Effect effect)
+            throws JsonParseException, JsonMappingException, IOException {
+        Mockito.doThrow(new RuntimeException()).when(this.cache)
+                .set(Mockito.any(PolicyEvaluationRequestCacheKey.class), Mockito.any(PolicyEvaluationResult.class));
+        testEvaluateWithPolicy(inputPolicy, effect);
+    }
+
+    /**
+     * @param inputPolicy
+     * @throws IOException
+     * @throws JsonParseException
+     * @throws JsonMappingException
+     */
+    private void initializePolicyMock(final File inputPolicy)
+            throws IOException, JsonParseException, JsonMappingException {
+        initializePolicyMock(inputPolicy, Collections.emptySet(), Collections.emptySet());
+    }
+
+    /**
+     * @param inputPolicy
+     * @throws IOException
+     * @throws JsonParseException
+     * @throws JsonMappingException
+     */
+    private void initializePolicyMock(final File inputPolicy, final Set<Attribute> resourceAttributes,
+            final Set<Attribute> subjectAttributes) throws IOException, JsonParseException, JsonMappingException {
+        PolicySet policySet = new ObjectMapper().readValue(inputPolicy, PolicySet.class);
+        when(this.policyService.getAllPolicySets()).thenReturn(Arrays.asList(new PolicySet[] { policySet }));
+        List<MatchedPolicy> matchedPolicies = new ArrayList<>();
+        for (Policy policy : policySet.getPolicies()) {
+            matchedPolicies.add(new MatchedPolicy(policy, resourceAttributes, subjectAttributes));
+        }
+        when(this.policyMatcher.match(any(PolicyMatchCandidate.class), anyListOf(Policy.class)))
+                .thenReturn(matchedPolicies);
+        when(this.policyMatcher.matchForResult(any(PolicyMatchCandidate.class), anyListOf(Policy.class)))
+                .thenReturn(new MatchResult(matchedPolicies, new HashSet<String>()));
+    }
+
+    private BaseSubject getSubject(final String roleValue) {
+        BaseSubject subject = new BaseSubject("subject1");
+        Set<Attribute> attributes = getSubjectAttributes(roleValue);
+        subject.setAttributes(attributes);
+        return subject;
+    }
+
+    /**
+     * @param roleValue
+     * @return
+     */
+    private Set<Attribute> getSubjectAttributes(final String roleValue) {
+        Set<Attribute> attributes = new HashSet<>();
+        if (roleValue != null) {
+            attributes.add(new Attribute(ISSUER, SUBJECT_ATTRIB_NAME_ROLE, roleValue));
+        }
+        return attributes;
+    }
+
+    private BaseResource getResource() {
+        BaseResource resource = new BaseResource("name");
+        Set<Attribute> resourceAttributes = new HashSet<>();
+        resourceAttributes.add(new Attribute(ISSUER, RES_ATTRIB_ROLE_REQUIRED, RES_ATTRIB_ROLE_REQUIRED_VALUE));
+        resourceAttributes.add(new Attribute(ISSUER, RES_ATTRIB_LOCATION, RES_ATTRIB_LOCATION_VALUE));
+        resource.setAttributes(resourceAttributes);
+        return resource;
+    }
+
+    @DataProvider(name = "policyDataProviderForTestWithAttributes")
+    private Object[][] policyDataProviderForTestWithAttributes() {
+        return new Object[][] { { SUBJECT_ATTRIB_VALUE_ANALYST,
+                new File("src/test/resources/policy-set-with-one-policy-one-condition-using-attributes.json"),
+                Effect.NOT_APPLICABLE, EMPTY_ATTRS }, { SUBJECT_ATTRIB_VALUE_ANALYST,
+                new File("src/test/resources/policy-set-with-one-policy-one-condition-using-attributes.json"),
+                Effect.PERMIT, getSubjectAttributes(SUBJECT_ATTRIB_VALUE_ADMIN) },
+                { null, new File("src/test/resources/policy-set-with-one-policy-one-condition-using-attributes.json"),
+                        Effect.NOT_APPLICABLE, getSubjectAttributes(SUBJECT_ATTRIB_VALUE_ADMIN) }, { null,
+                new File("src/test/resources/" + "policy-set-with-one-policy-one-condition-using-res-attributes.json"),
+                Effect.PERMIT, getSubjectAttributes(SUBJECT_ATTRIB_VALUE_ADMIN) } };
+    }
+
+    @DataProvider(name = "policyDataProvider")
+    private Object[][] policyDataProvider() {
+        return new Object[][] {
+                { new File("src/test/resources/policy-set-with-one-policy-nocondition.json"), Effect.DENY },
+                { new File("src/test/resources/policy-set-with-one-policy-one-condition.json"), Effect.PERMIT },
+                { new File("src/test/resources/policy-set-with-multiple-policies-first-match.json"), Effect.DENY },
+                { new File("src/test/resources/policy-set-with-multiple-policies-permit-with-condition.json"),
+                        Effect.PERMIT },
+                { new File("src/test/resources/policy-set-with-multiple-policies-deny-with-condition.json"),
+                        Effect.DENY },
+                { new File("src/test/resources/policy-set-with-multiple-policies-na-with-condition.json"),
+                        Effect.NOT_APPLICABLE },
+                { new File("src/test/resources/policy-set-with-multiple-policies-default-deny-with-condition.json"),
+                        Effect.DENY },
+                { new File("src/test/resources/policy-set-with-one-policy-one-condition-indeterminate.json"),
+                        Effect.INDETERMINATE },
+                { new File("src/test/resources/policy-set-with-multiple-policies-deny-missing-optional-tags.json"),
+                        Effect.DENY } };
+    }
+
+    @DataProvider(name = "policyRequestParameterProvider")
+    private Object[][] policyRequestParameterProvider() {
+        return new Object[][] { { null, "s1", "a1" }, { "r1", null, "a1" }, { "r1", "s1", null }, };
+    }
+
+    @DataProvider(name = "filterPolicySetsInvalidRequestDataProvider")
+    private Object[][] filterPolicySetsInvalidRequestDataProvider()
+            throws JsonParseException, JsonMappingException, IOException {
+        List<PolicySet> onePolicySet = createDenyPolicySet();
+        List<PolicySet> twoPolicySets = createNotApplicableAndDenyPolicySets();
+        return new Object[][] { filterOnePolicySetByNonexistentPolicySet(onePolicySet),
+                filterTwoPolisySetsByEmptyList(twoPolicySets),
+                filterTwoPolicySetsByByNonexistentPolicySet(twoPolicySets) };
+    }
+
+    private Object[] filterOnePolicySetByNonexistentPolicySet(final List<PolicySet> onePolicySet) {
+        return new Object[] { onePolicySet,
+                Stream.of("nonexistent-policy-set").collect(Collectors.toCollection(LinkedHashSet::new)) };
+    }
+
+    private Object[] filterTwoPolisySetsByEmptyList(final List<PolicySet> twoPolicySets) {
+        return new Object[] { twoPolicySets, PolicyEvaluationRequestV1.EMPTY_POLICY_EVALUATION_ORDER };
+    }
+
+    private Object[] filterTwoPolicySetsByByNonexistentPolicySet(final List<PolicySet> twoPolicySets) {
+        return new Object[] { twoPolicySets, Stream.of(twoPolicySets.get(0).getName(), "noexistent-policy-set").collect(
+                Collectors.toCollection(LinkedHashSet::new)) };
+    }
+
+    @DataProvider(name = "filterPolicySetsDataProvider")
+    private Object[][] filterPolicySetsDataProvider() {
+        List<PolicySet> denyPolicySet = createDenyPolicySet();
+        List<PolicySet> notApplicableAndDenyPolicySets = createNotApplicableAndDenyPolicySets();
+        return new Object[][] { filterOnePolicySetByEmptyEvaluationOrder(denyPolicySet),
+                filterOnePolicySetByItself(denyPolicySet),
+                filterTwoPolicySetsByFirstSet(notApplicableAndDenyPolicySets),
+                filterTwoPolicySetsBySecondPolicySet(notApplicableAndDenyPolicySets),
+                filterTwoPolicySetsByItself(notApplicableAndDenyPolicySets) };
+    }
+
+    private Object[] filterTwoPolicySetsByItself(final List<PolicySet> twoPolicySets) {
+        return new Object[] { twoPolicySets,
+                Stream.of(twoPolicySets.get(0).getName(), twoPolicySets.get(1).getName()).collect(
+                        Collectors.toCollection(LinkedHashSet::new)),
+                twoPolicySets.stream().collect(Collectors.toCollection(LinkedHashSet::new)) };
+    }
+
+    private Object[] filterTwoPolicySetsBySecondPolicySet(final List<PolicySet> twoPolicySets) {
+        return new Object[] { twoPolicySets,
+                Stream.of(twoPolicySets.get(1).getName()).collect(Collectors.toCollection(LinkedHashSet::new)),
+                Stream.of(twoPolicySets.get(1)).collect(Collectors.toCollection(LinkedHashSet::new)) };
+    }
+
+    private Object[] filterTwoPolicySetsByFirstSet(final List<PolicySet> twoPolicySets) {
+        return new Object[] { twoPolicySets,
+                Stream.of(twoPolicySets.get(0).getName()).collect(Collectors.toCollection(LinkedHashSet::new)),
+                Stream.of(twoPolicySets.get(0)).collect(Collectors.toCollection(LinkedHashSet::new)) };
+    }
+
+    private Object[] filterOnePolicySetByItself(final List<PolicySet> onePolicySet) {
+        return new Object[] { onePolicySet,
+                Stream.of(onePolicySet.get(0).getName()).collect(Collectors.toCollection(LinkedHashSet::new)),
+                onePolicySet.stream().collect(Collectors.toCollection(LinkedHashSet::new)) };
+    }
+
+    private Object[] filterOnePolicySetByEmptyEvaluationOrder(final List<PolicySet> onePolicySet) {
+        return new Object[] { onePolicySet, PolicyEvaluationRequestV1.EMPTY_POLICY_EVALUATION_ORDER,
+                onePolicySet.stream().collect(Collectors.toCollection(LinkedHashSet::new)) };
+    }
+
+    @DataProvider(name = "multiplePolicySetsRequestDataProvider")
+    private Object[][] multiplePolicySetsRequestDataProvider()
+            throws JsonParseException, JsonMappingException, IOException {
+        List<PolicySet> denyPolicySet = createDenyPolicySet();
+        List<PolicySet> notApplicableAndDenyPolicySets = createNotApplicableAndDenyPolicySets();
+        return new Object[][] { requestEvaluationWithEmptyPolicySetsListAndEmptyPriorityList(),
+                requestEvaluationWithOnePolicySetAndEmptyPriorityList(denyPolicySet),
+                requestEvaluationWithFirstOfOnePolicySets(denyPolicySet),
+                requestEvaluationWithFirstOfTwoPolicySets(notApplicableAndDenyPolicySets),
+                requestEvaluationWithSecondOfTwoPolicySets(notApplicableAndDenyPolicySets),
+                requestEvaluationWithAllOfTwoPolicySets(notApplicableAndDenyPolicySets) };
+    }
+
+    private Object[] requestEvaluationWithAllOfTwoPolicySets(final List<PolicySet> twoPolicySets) {
+        return new Object[] { twoPolicySets,
+                Stream.of(twoPolicySets.get(0).getName(), twoPolicySets.get(1).getName()).collect(
+                        Collectors.toCollection(LinkedHashSet::new)), Effect.DENY };
+    }
+
+    private Object[] requestEvaluationWithSecondOfTwoPolicySets(final List<PolicySet> twoPolicySets) {
+        return new Object[] { twoPolicySets,
+                Stream.of(twoPolicySets.get(1).getName()).collect(Collectors.toCollection(LinkedHashSet::new)),
+                Effect.DENY };
+    }
+
+    private Object[] requestEvaluationWithFirstOfTwoPolicySets(final List<PolicySet> twoPolicySets) {
+        return new Object[] { twoPolicySets,
+                Stream.of(twoPolicySets.get(0).getName()).collect(Collectors.toCollection(LinkedHashSet::new)),
+                Effect.NOT_APPLICABLE };
+    }
+
+    private Object[] requestEvaluationWithFirstOfOnePolicySets(final List<PolicySet> onePolicySet) {
+        return new Object[] { onePolicySet,
+                Stream.of(onePolicySet.get(0).getName()).collect(Collectors.toCollection(LinkedHashSet::new)),
+                Effect.DENY };
+    }
+
+    private Object[] requestEvaluationWithOnePolicySetAndEmptyPriorityList(final List<PolicySet> onePolicySet) {
+        return new Object[] { onePolicySet, PolicyEvaluationRequestV1.EMPTY_POLICY_EVALUATION_ORDER, Effect.DENY };
+    }
+
+    private Object[] requestEvaluationWithEmptyPolicySetsListAndEmptyPriorityList() {
+        return new Object[] { Collections.emptyList(), PolicyEvaluationRequestV1.EMPTY_POLICY_EVALUATION_ORDER,
+                Effect.NOT_APPLICABLE };
+    }
+
+    private List<PolicySet> createDenyPolicySet() {
+        List<PolicySet> policySets = new ArrayList<PolicySet>();
+        policySets.add(this.jsonUtils.deserializeFromFile("policies/testPolicyEvalDeny.json", PolicySet.class));
+        Assert.assertNotNull(policySets, "Policy set file is not found or invalid");
+        return policySets;
+    }
+
+    private List<PolicySet> createNotApplicableAndDenyPolicySets() {
+        List<PolicySet> policySets = new ArrayList<PolicySet>();
+        policySets
+                .add(this.jsonUtils.deserializeFromFile("policies/testPolicyEvalNotApplicable.json", PolicySet.class));
+        policySets.add(this.jsonUtils.deserializeFromFile("policies/testPolicyEvalDeny.json", PolicySet.class));
+        Assert.assertNotNull(policySets, "Policy set files are not found or invalid");
+        Assert.assertTrue(policySets.size() == 2, "One or more policy set files are not found or invalid");
+        return policySets;
+    }
+
+    private PolicyEvaluationRequestV1 createRequest(final String resource, final String subject, final String action) {
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        request.setAction(action);
+        request.setSubjectIdentifier(subject);
+        request.setResourceIdentifier(resource);
+        return request;
+    }
+
+    private PolicyEvaluationRequestV1 createRequest(final String resource, final String subject, final String action,
+            final LinkedHashSet<String> policySetsEvaluationOrder) {
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        request.setAction(action);
+        request.setSubjectIdentifier(subject);
+        request.setResourceIdentifier(resource);
+        request.setPolicySetsEvaluationOrder(policySetsEvaluationOrder);
+        return request;
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationWithAttributeReaderTest.java b/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationWithAttributeReaderTest.java
new file mode 100644
index 0000000..e7f2b2a
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationWithAttributeReaderTest.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anySetOf;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.internal.util.reflection.Whitebox;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ge.predix.acs.PolicyContextResolver;
+import com.ge.predix.acs.attribute.readers.AttributeReaderFactory;
+import com.ge.predix.acs.attribute.readers.AttributeRetrievalException;
+import com.ge.predix.acs.attribute.readers.ExternalResourceAttributeReader;
+import com.ge.predix.acs.attribute.readers.ExternalSubjectAttributeReader;
+import com.ge.predix.acs.commons.policy.condition.groovy.GroovyConditionCache;
+import com.ge.predix.acs.commons.policy.condition.groovy.GroovyConditionShell;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.policy.evaluation.cache.PolicyEvaluationCache;
+import com.ge.predix.acs.policy.evaluation.cache.PolicyEvaluationRequestCacheKey;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+import com.ge.predix.acs.service.policy.admin.PolicyManagementService;
+import com.ge.predix.acs.service.policy.matcher.PolicyMatcherImpl;
+import com.ge.predix.acs.service.policy.validation.PolicySetValidator;
+import com.ge.predix.acs.service.policy.validation.PolicySetValidatorImpl;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.ge.predix.acs.zone.resolver.ZoneResolver;
+
+@ContextConfiguration(
+        classes = { GroovyConditionCache.class, GroovyConditionShell.class, PolicySetValidatorImpl.class })
+public class PolicyEvaluationWithAttributeReaderTest extends AbstractTestNGSpringContextTests {
+
+    @InjectMocks
+    private PolicyEvaluationServiceImpl evaluationService;
+    @Mock
+    private PolicyManagementService policyService;
+    @Mock
+    private PolicyContextResolver policyScopeResolver;
+    @Mock
+    private ZoneResolver zoneResolver;
+    @Mock
+    private PolicyEvaluationCache cache;
+    @Mock
+    private AttributeReaderFactory attributeReaderFactory;
+    @Mock
+    private ExternalResourceAttributeReader externalResourceAttributeReader;
+    @Mock
+    private ExternalSubjectAttributeReader externalSubjectAttributeReader;
+    @Autowired
+    private PolicySetValidator policySetValidator;
+
+    private static final String RESOURCE_IDENTIFIER = "/sites/1234";
+    private static final String SUBJECT_IDENTIFIER = "test-subject";
+    private static final String ACTION = "GET";
+
+    private final PolicyMatcherImpl policyMatcher = new PolicyMatcherImpl();
+
+    @BeforeClass
+    public void setupClass() {
+        ((PolicySetValidatorImpl) this.policySetValidator)
+                .setValidAcsPolicyHttpActions("GET, POST, PUT, DELETE, PATCH");
+        ((PolicySetValidatorImpl) this.policySetValidator).init();
+    }
+
+    @BeforeMethod
+    public void setupMethod() throws Exception {
+        this.evaluationService = new PolicyEvaluationServiceImpl();
+        MockitoAnnotations.initMocks(this);
+        Whitebox.setInternalState(this.policyMatcher, "attributeReaderFactory", this.attributeReaderFactory);
+        Whitebox.setInternalState(this.evaluationService, "policyMatcher", this.policyMatcher);
+        Whitebox.setInternalState(this.evaluationService, "policySetValidator", this.policySetValidator);
+        when(this.zoneResolver.getZoneEntityOrFail()).thenReturn(new ZoneEntity(0L, "testzone"));
+        when(this.cache.get(any(PolicyEvaluationRequestCacheKey.class))).thenReturn(null);
+        when(this.attributeReaderFactory.getResourceAttributeReader()).thenReturn(this.externalResourceAttributeReader);
+        when(this.attributeReaderFactory.getSubjectAttributeReader()).thenReturn(this.externalSubjectAttributeReader);
+        PolicySet policySet = new ObjectMapper().readValue(
+                new File("src/test/resources/policy-set-with-one-policy-one-condition-using-res-attributes.json"),
+                PolicySet.class);
+        when(this.policyService.getAllPolicySets()).thenReturn(Collections.singletonList(policySet));
+    }
+
+    @Test
+    public void testPolicyEvaluation() throws Exception {
+        Set<Attribute> resourceAttributes = new HashSet<>();
+        resourceAttributes.add(new Attribute("https://acs.attributes.int", "location", "sanramon"));
+        resourceAttributes.add(new Attribute("https://acs.attributes.int", "role_required", "admin"));
+        BaseResource testResource = new BaseResource(RESOURCE_IDENTIFIER, resourceAttributes);
+
+        Set<Attribute> subjectAttributes = new HashSet<>();
+        subjectAttributes.add(new Attribute("https://acs.attributes.int", "role", "admin"));
+        BaseSubject testSubject = new BaseSubject(SUBJECT_IDENTIFIER, subjectAttributes);
+
+        when(this.externalResourceAttributeReader.getAttributes(anyString())).thenReturn(testResource.getAttributes());
+        when(this.externalSubjectAttributeReader.getAttributesByScope(anyString(), anySetOf(Attribute.class)))
+                .thenReturn(testSubject.getAttributes());
+
+        PolicyEvaluationResult evalResult = this.evaluationService
+                .evalPolicy(createRequest(RESOURCE_IDENTIFIER, SUBJECT_IDENTIFIER, ACTION));
+        Assert.assertEquals(evalResult.getEffect(), Effect.PERMIT);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testPolicyEvaluationWhenAdaptersTimeOut() throws Exception {
+        String attributeRetrievalExceptionMessage = "attribute retrieval exception";
+        when(this.externalResourceAttributeReader.getAttributes(Mockito.anyString()))
+                .thenThrow(new AttributeRetrievalException(attributeRetrievalExceptionMessage, new Exception()));
+
+        PolicyEvaluationResult evalResult = this.evaluationService
+                .evalPolicy(createRequest(RESOURCE_IDENTIFIER, SUBJECT_IDENTIFIER, ACTION));
+        Assert.assertEquals(evalResult.getEffect(), Effect.INDETERMINATE);
+        Assert.assertEquals(evalResult.getMessage(), attributeRetrievalExceptionMessage);
+    }
+
+    private PolicyEvaluationRequestV1 createRequest(final String resource, final String subject, final String action) {
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        request.setAction(action);
+        request.setSubjectIdentifier(subject);
+        request.setResourceIdentifier(resource);
+        return request;
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationWithAttributeUriTemplateTest.java b/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationWithAttributeUriTemplateTest.java
new file mode 100644
index 0000000..af413df
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/PolicyEvaluationWithAttributeUriTemplateTest.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anySetOf;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.internal.util.reflection.Whitebox;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ge.predix.acs.attribute.readers.AttributeReaderFactory;
+import com.ge.predix.acs.attribute.readers.PrivilegeServiceResourceAttributeReader;
+import com.ge.predix.acs.attribute.readers.PrivilegeServiceSubjectAttributeReader;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.policy.evaluation.cache.PolicyEvaluationCache;
+import com.ge.predix.acs.policy.evaluation.cache.PolicyEvaluationRequestCacheKey;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+import com.ge.predix.acs.service.policy.admin.PolicyManagementService;
+import com.ge.predix.acs.service.policy.admin.PolicyManagementServiceImpl;
+import com.ge.predix.acs.service.policy.matcher.PolicyMatcherImpl;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.ge.predix.acs.zone.resolver.ZoneResolver;
+
+public class PolicyEvaluationWithAttributeUriTemplateTest {
+
+    @InjectMocks
+    private final PolicyEvaluationService evaluationService = new PolicyEvaluationServiceImpl();
+    @Mock
+    private final PolicyManagementService policyService = new PolicyManagementServiceImpl();
+    @Mock
+    private AttributeReaderFactory attributeReaderFactory;
+    @Mock
+    private PrivilegeServiceResourceAttributeReader defaultResourceAttributeReader;
+    @Mock
+    private PrivilegeServiceSubjectAttributeReader defaultSubjectAttributeReader;
+    @Mock
+    private ZoneResolver zoneResolver;
+    @Mock
+    private PolicyEvaluationCache cache;
+
+    private final PolicyMatcherImpl policyMatcher = new PolicyMatcherImpl();
+
+    @Test
+    public void testEvaluateWithURIAttributeTemplate() throws JsonParseException, JsonMappingException, IOException {
+        MockitoAnnotations.initMocks(this);
+        Whitebox.setInternalState(this.policyMatcher, "attributeReaderFactory", this.attributeReaderFactory);
+        Whitebox.setInternalState(this.evaluationService, "policyMatcher", this.policyMatcher);
+        when(this.zoneResolver.getZoneEntityOrFail()).thenReturn(new ZoneEntity(0L, "testzone"));
+        when(this.cache.get(any(PolicyEvaluationRequestCacheKey.class))).thenReturn(null);
+        when(this.attributeReaderFactory.getResourceAttributeReader()).thenReturn(this.defaultResourceAttributeReader);
+        when(this.attributeReaderFactory.getSubjectAttributeReader()).thenReturn(this.defaultSubjectAttributeReader);
+
+        // set policy
+        PolicySet policySet = new ObjectMapper()
+                .readValue(new File("src/test/resources/policy-set-with-attribute-uri-template.json"), PolicySet.class);
+        when(this.policyService.getAllPolicySets()).thenReturn(Arrays.asList(policySet));
+
+        // Create 'role' attribute in resource for URI /site/1234. Used in target match for policy 1.
+        BaseResource testResource = new BaseResource("/site/1234");
+        Set<Attribute> resourceAttributes = new HashSet<>();
+        resourceAttributes.add(new Attribute("https://acs.attributes.int", "role", "admin"));
+        testResource.setAttributes(resourceAttributes);
+
+        when(this.defaultResourceAttributeReader.getAttributes(testResource.getResourceIdentifier()))
+            .thenReturn(testResource.getAttributes());
+
+        BaseSubject testSubject = new BaseSubject("test-subject");
+        testSubject.setAttributes(Collections.emptySet());
+        when(this.defaultSubjectAttributeReader.getAttributesByScope(anyString(), anySetOf(Attribute.class)))
+                .thenReturn(testSubject.getAttributes());
+
+        // resourceURI matches attributeURITemplate
+        PolicyEvaluationResult evalResult = this.evaluationService
+                .evalPolicy(createRequest("/v1/site/1234/asset/456", "test-subject", "GET"));
+        Assert.assertEquals(evalResult.getEffect(), Effect.PERMIT);
+
+        // resourceURI does NOT match attributeURITemplate
+        evalResult = this.evaluationService
+                .evalPolicy(createRequest("/v1/no-match/asset/123", "test-subject", "GET"));
+        // second policy will match.
+        Assert.assertEquals(evalResult.getEffect(), Effect.DENY);
+
+    }
+
+    private PolicyEvaluationRequestV1 createRequest(final String resource, final String subject, final String action) {
+        PolicyEvaluationRequestV1 request = new PolicyEvaluationRequestV1();
+        request.setAction(action);
+        request.setSubjectIdentifier(subject);
+        request.setResourceIdentifier(resource);
+        return request;
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/ResourceAttributeResolverTest.java b/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/ResourceAttributeResolverTest.java
new file mode 100644
index 0000000..d15286d
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/ResourceAttributeResolverTest.java
@@ -0,0 +1,179 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.attribute.readers.PrivilegeServiceResourceAttributeReader;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Policy;
+import com.ge.predix.acs.model.ResourceType;
+import com.ge.predix.acs.model.Target;
+import com.ge.predix.acs.rest.BaseResource;
+
+@Test
+public class ResourceAttributeResolverTest {
+    @Mock
+    private PrivilegeServiceResourceAttributeReader defaultResourceAttributeReader;
+
+    private BaseResource testResource;
+
+    @BeforeMethod
+    public void beforeMethod() {
+        MockitoAnnotations.initMocks(this);
+
+        this.testResource = new BaseResource("/test/resource");
+        when(this.defaultResourceAttributeReader.getAttributes(eq(this.testResource.getResourceIdentifier())))
+                .thenReturn(Collections.emptySet());
+    }
+
+    /**
+     * @param resourceURI
+     *            in the evaluation request
+     * @param resolvedResourceURI
+     *            expected resource URI after attribute template is applied
+     */
+    @Test(dataProvider = "resourceUriProvider")
+    public void testResolveResourceUri(final String resourceURI, final String attributeUriTemplate,
+            final String resolvedResourceURI) throws Exception {
+        ResourceAttributeResolver resolver = new ResourceAttributeResolver(
+            this.defaultResourceAttributeReader, resourceURI, null);
+
+        Assert.assertEquals(resolver.resolveResourceURI(getPolicy(attributeUriTemplate)), resolvedResourceURI);
+    }
+
+    @DataProvider
+    public Object[][] resourceUriProvider() {
+        return new Object[][] { { "/v1/site/123/asset/456", "/v1{attribute_uri}/asset/{asset-id}", "/site/123" },
+                { "/v1/site/123/asset/456", "/v2/DoesNotExist/{attribute_uri}", null },
+                // attributeUriTemplate not defined
+                { "/v1/site/123/asset/456", null, null },
+                // attributeUriTemplate defined as " "
+                { "/v1/site/123/asset/456", " ", null } };
+    }
+
+    public void testResolveResourceUriNoPolicy() {
+        ResourceAttributeResolver resolver = new ResourceAttributeResolver(this.defaultResourceAttributeReader, "/a/b",
+                null);
+        Assert.assertEquals(resolver.resolveResourceURI(null), null);
+    }
+
+    public void testResolveResourceUriNoTarget() {
+        ResourceAttributeResolver resolver = new ResourceAttributeResolver(this.defaultResourceAttributeReader, "/a/b",
+                null);
+        Assert.assertEquals(resolver.resolveResourceURI(new Policy()), null);
+    }
+
+    public void testResolveResourceUriNoResource() {
+        ResourceAttributeResolver resolver = new ResourceAttributeResolver(this.defaultResourceAttributeReader, "/a/b",
+                null);
+        Assert.assertEquals(resolver.resolveResourceURI(new Policy()), null);
+    }
+
+    public void testGetResourceAttributes() {
+        Set<Attribute> resourceAttributes = new HashSet<>();
+        resourceAttributes.add(new Attribute("issuer1", "test-attr"));
+        this.testResource.setAttributes(resourceAttributes);
+
+        Set<Attribute> supplementalResourceAttributes = new HashSet<>();
+        supplementalResourceAttributes.add(new Attribute("https://acs.attributes.int", "site", "sanramon"));
+
+        // mock attribute service for the expected resource URI after attributeURITemplate is applied
+        when(this.defaultResourceAttributeReader.getAttributes(this.testResource.getResourceIdentifier()))
+                .thenReturn(resourceAttributes);
+        ResourceAttributeResolver resolver = new ResourceAttributeResolver(this.defaultResourceAttributeReader,
+                this.testResource.getResourceIdentifier(), supplementalResourceAttributes);
+
+        Set<Attribute> combinedResourceAttributes = resolver.getResult(getPolicy(null)).getResourceAttributes();
+        Assert.assertNotNull(combinedResourceAttributes);
+        Assert.assertTrue(combinedResourceAttributes.containsAll(resourceAttributes));
+        Assert.assertTrue(combinedResourceAttributes.containsAll(supplementalResourceAttributes));
+    }
+
+    public void testGetResourceAttributesNoResourceFoundAndNoSupplementalAttributes() {
+        ResourceAttributeResolver resolver = new ResourceAttributeResolver(this.defaultResourceAttributeReader,
+                "NonExistingURI", null);
+
+        Set<Attribute> combinedResourceAttributes = resolver.getResult(getPolicy(null)).getResourceAttributes();
+        Assert.assertTrue(combinedResourceAttributes.isEmpty());
+    }
+
+    @Test
+    public void testGetResourceAttributesResourceFoundWithNoAttributesAndNoSupplementalAttributes() {
+        ResourceAttributeResolver resolver = new ResourceAttributeResolver(this.defaultResourceAttributeReader,
+                this.testResource.getResourceIdentifier(), null);
+
+        Set<Attribute> combinedResourceAttributes = resolver.getResult(getPolicy(null)).getResourceAttributes();
+        Assert.assertTrue(combinedResourceAttributes.isEmpty());
+    }
+
+    @Test
+    public void testGetResourceAttributesResourceFoundWithAttributesAndNoSupplementalAttributes() {
+        Set<Attribute> resourceAttributes = new HashSet<>();
+        resourceAttributes.add(new Attribute("https://acs.attributes.int", "role", "administrator"));
+        this.testResource.setAttributes(resourceAttributes);
+
+        when(this.defaultResourceAttributeReader.getAttributes(eq(this.testResource.getResourceIdentifier())))
+                .thenReturn(resourceAttributes);
+        ResourceAttributeResolver resolver = new ResourceAttributeResolver(this.defaultResourceAttributeReader,
+                this.testResource.getResourceIdentifier(), null);
+
+        Set<Attribute> combinedResourceAttributes = resolver.getResult(getPolicy(null)).getResourceAttributes();
+        Assert.assertNotNull(combinedResourceAttributes);
+        Assert.assertTrue(combinedResourceAttributes.containsAll(resourceAttributes));
+    }
+
+    @Test
+    public void testGetResourceAttributesSupplementalAttributesOnly() {
+        Set<Attribute> supplementalResourceAttributes = new HashSet<>();
+        supplementalResourceAttributes.add(new Attribute("https://acs.attributes.int", "site", "sanramon"));
+
+        ResourceAttributeResolver resolver = new ResourceAttributeResolver(this.defaultResourceAttributeReader,
+                this.testResource.getResourceIdentifier(), supplementalResourceAttributes);
+
+        Set<Attribute> combinedResourceAttributes = resolver.getResult(getPolicy(null)).getResourceAttributes();
+        Assert.assertNotNull(combinedResourceAttributes);
+        Assert.assertTrue(combinedResourceAttributes.containsAll(supplementalResourceAttributes));
+    }
+
+    private Policy getPolicy(final String attributeTemplate) {
+        ResourceType r = new ResourceType();
+        r.setAttributeUriTemplate(attributeTemplate);
+        Target t = new Target();
+        t.setResource(r);
+        Policy p = new Policy();
+        p.setTarget(t);
+        return p;
+    }
+
+    // attrTemplate does not match uriTemplate
+    // no attrTemplate
+}
diff --git a/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/SubjectAttributeResolverTest.java b/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/SubjectAttributeResolverTest.java
new file mode 100644
index 0000000..3f3a72a
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/service/policy/evaluation/SubjectAttributeResolverTest.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.evaluation;
+
+import static org.mockito.Mockito.anySetOf;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.attribute.readers.PrivilegeServiceSubjectAttributeReader;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.BaseSubject;
+
+@Test
+public class SubjectAttributeResolverTest {
+
+    @Mock
+    private PrivilegeServiceSubjectAttributeReader defaultSubjectAttributeReader;
+
+    private BaseSubject testSubject;
+
+    @BeforeMethod
+    public void beforeMethod() {
+        MockitoAnnotations.initMocks(this);
+
+        this.testSubject = new BaseSubject("/test/subject");
+        when(this.defaultSubjectAttributeReader.getAttributesByScope(eq(this.testSubject.getSubjectIdentifier()),
+                anySetOf(Attribute.class))).thenReturn(Collections.emptySet());
+    }
+
+    @Test
+    public void testGetSubjectAttributes() {
+        Set<Attribute> subjectAttributes = new HashSet<>();
+        subjectAttributes.add(new Attribute("https://acs.attributes.int", "role", "administrator"));
+        this.testSubject.setAttributes(subjectAttributes);
+
+        Set<Attribute> supplementalSubjectAttributes = new HashSet<>();
+        supplementalSubjectAttributes.add(new Attribute("https://acs.attributes.int", "site", "sanramon"));
+
+        // mock attribute service for the expected resource URI after attributeURITemplate is applied
+        when(this.defaultSubjectAttributeReader.getAttributesByScope(eq(this.testSubject.getSubjectIdentifier()),
+                anySetOf(Attribute.class))).thenReturn(subjectAttributes);
+        SubjectAttributeResolver resolver = new SubjectAttributeResolver(this.defaultSubjectAttributeReader,
+                this.testSubject.getSubjectIdentifier(), supplementalSubjectAttributes);
+
+        Set<Attribute> combinedSubjectAttributes = resolver.getResult(null);
+        Assert.assertNotNull(combinedSubjectAttributes);
+        Assert.assertTrue(combinedSubjectAttributes.containsAll(subjectAttributes));
+        Assert.assertTrue(combinedSubjectAttributes.containsAll(supplementalSubjectAttributes));
+    }
+
+    @Test
+    public void testGetSubjectAttributesNoSubjectFoundAndNoSupplementalAttributes() {
+        SubjectAttributeResolver resolver = new SubjectAttributeResolver(this.defaultSubjectAttributeReader,
+                "NonExistingURI", null);
+
+        Set<Attribute> combinedSubjectAttributes = resolver.getResult(null);
+        Assert.assertTrue(combinedSubjectAttributes.isEmpty());
+    }
+
+    @Test
+    public void testGetSubjectAttributesSubjectFoundWithNoAttributesAndNoSupplementalAttributes() {
+        SubjectAttributeResolver resolver = new SubjectAttributeResolver(this.defaultSubjectAttributeReader,
+                this.testSubject.getSubjectIdentifier(), null);
+
+        Set<Attribute> combinedSubjectAttributes = resolver.getResult(null);
+        Assert.assertTrue(combinedSubjectAttributes.isEmpty());
+    }
+
+    @Test
+    public void testGetSubjectAttributesSubjectFoundWithAttributesAndNoSupplementalAttributes() {
+        Set<Attribute> subjectAttributes = new HashSet<>();
+        subjectAttributes.add(new Attribute("https://acs.attributes.int", "role", "administrator"));
+        this.testSubject.setAttributes(subjectAttributes);
+
+        when(this.defaultSubjectAttributeReader.getAttributesByScope(eq(this.testSubject.getSubjectIdentifier()),
+                anySetOf(Attribute.class))).thenReturn(subjectAttributes);
+        SubjectAttributeResolver resolver = new SubjectAttributeResolver(this.defaultSubjectAttributeReader,
+                this.testSubject.getSubjectIdentifier(), null);
+
+        Set<Attribute> combinedSubjectAttributes = resolver.getResult(null);
+        Assert.assertNotNull(combinedSubjectAttributes);
+        Assert.assertTrue(combinedSubjectAttributes.containsAll(subjectAttributes));
+    }
+
+    @Test
+    public void testGetSubjectAttributesSupplementalAttributesOnly() {
+        Set<Attribute> supplementalSubjectAttributes = new HashSet<>();
+        supplementalSubjectAttributes.add(new Attribute("https://acs.attributes.int", "site", "sanramon"));
+
+        SubjectAttributeResolver resolver = new SubjectAttributeResolver(this.defaultSubjectAttributeReader,
+                this.testSubject.getSubjectIdentifier(), supplementalSubjectAttributes);
+
+        Set<Attribute> combinedSubjectAttributes = resolver.getResult(null);
+        Assert.assertNotNull(combinedSubjectAttributes);
+        Assert.assertTrue(combinedSubjectAttributes.containsAll(supplementalSubjectAttributes));
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/service/policy/matcher/PolicyMatcherImplTest.java b/service/src/test/java/com/ge/predix/acs/service/policy/matcher/PolicyMatcherImplTest.java
new file mode 100644
index 0000000..f965c11
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/service/policy/matcher/PolicyMatcherImplTest.java
@@ -0,0 +1,594 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.matcher;
+
+import static org.mockito.Matchers.anySetOf;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.web.util.UriTemplate;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.attribute.readers.AttributeReaderFactory;
+import com.ge.predix.acs.attribute.readers.PrivilegeServiceResourceAttributeReader;
+import com.ge.predix.acs.attribute.readers.PrivilegeServiceSubjectAttributeReader;
+import com.ge.predix.acs.commons.policy.condition.groovy.GroovyConditionShell;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Condition;
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.model.Policy;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.service.policy.evaluation.MatchedPolicy;
+
+/**
+ * Unit tests for PolicyMatcher class.
+ *
+ * @author acs-engineers@ge.com
+ *
+ */
+@Test
+public class PolicyMatcherImplTest {
+    private static final String POLICY_DIR_PATH = "src/test/resources/policies";
+    @Mock
+    private AttributeReaderFactory attributeReaderFactory;
+    @Mock
+    private PrivilegeServiceResourceAttributeReader defaultResourceAttributeReader;
+    @Mock
+    private PrivilegeServiceSubjectAttributeReader defaultSubjectAttributeReader;
+    @InjectMocks
+    private PolicyMatcher policyMatcher;
+
+    @BeforeClass
+    public void setup() {
+        this.policyMatcher = new PolicyMatcherImpl();
+        MockitoAnnotations.initMocks(this);
+        when(this.attributeReaderFactory.getResourceAttributeReader()).thenReturn(this.defaultResourceAttributeReader);
+        when(this.attributeReaderFactory.getSubjectAttributeReader()).thenReturn(this.defaultSubjectAttributeReader);
+        when(this.defaultResourceAttributeReader.getAttributes(anyString())).thenReturn(Collections.emptySet());
+        BaseSubject subject = new BaseSubject("test-subject");
+        subject.setAttributes(new HashSet<>());
+        when(this.defaultSubjectAttributeReader.getAttributesByScope(anyString(), anySetOf(Attribute.class)))
+            .thenReturn(subject.getAttributes());
+    }
+
+    /**
+     * Tests matching a blanket policy to a request.
+     *
+     * @throws IOException
+     *             on failure to load policy required for test.
+     */
+    public void testMatchPolicyWithNoTarget() throws IOException {
+        File file = getPolicyFileForTest(getMethodName());
+        PolicySet policySet = PolicySets.loadFromFile(file);
+        List<Policy> policies = policySet.getPolicies();
+
+        PolicyMatchCandidate candidate = new PolicyMatchCandidate();
+        candidate.setAction("GET");
+        candidate.setResourceURI("/this/does/not/exist");
+        candidate.setSubjectIdentifier("Edward R. Murrow");
+        candidate.setSupplementalResourceAttributes(Collections.emptySet());
+        candidate.setSupplementalSubjectAttributes(Collections.emptySet());
+        List<MatchedPolicy> matchedPolicies = this.policyMatcher.match(candidate, policies);
+        Assert.assertEquals(matchedPolicies.size(), 1);
+        Assert.assertEquals(matchedPolicies.get(0).getPolicy(), policies.get(policies.size() - 1));
+        Assert.assertNull(matchedPolicies.get(0).getPolicy().getTarget());
+    }
+
+    /**
+     * Tests matching a blanket policy to a request.
+     *
+     * @throws IOException
+     *             on failure to load policy required for test.
+     */
+    public void testMatchPolicyWithNoTargetAction() throws IOException {
+        File file = getPolicyFileForTest(getMethodName());
+        PolicySet policySet = PolicySets.loadFromFile(file);
+        List<Policy> policies = policySet.getPolicies();
+
+        PolicyMatchCandidate candidate = new PolicyMatchCandidate();
+        candidate.setAction("GET");
+        candidate.setResourceURI("/this/does/not/exist");
+        candidate.setSubjectIdentifier("Edward R. Murrow");
+        candidate.setSupplementalResourceAttributes(Collections.emptySet());
+        candidate.setSupplementalSubjectAttributes(Collections.emptySet());
+        List<MatchedPolicy> matchedPolicies = this.policyMatcher.match(candidate, policies);
+        Assert.assertEquals(matchedPolicies.size(), 1);
+        Assert.assertEquals(matchedPolicies.get(0).getPolicy(), policies.get(policies.size() - 1));
+        Assert.assertNull(matchedPolicies.get(0).getPolicy().getTarget());
+    }
+
+    /**
+     * Tests matching a policy that does not specify a subject (i.e. applies to all subjects) to a request.
+     *
+     * @throws IOException
+     *             on failure to load policy required for test.
+     */
+    public void testMatchPolicyWithNoSubject() throws IOException {
+        File file = getPolicyFileForTest(getMethodName());
+        PolicySet policySet = PolicySets.loadFromFile(file);
+        List<Policy> policies = policySet.getPolicies();
+
+        PolicyMatchCandidate candidate = new PolicyMatchCandidate();
+        candidate.setAction("GET");
+        candidate.setResourceURI("/public");
+        candidate.setSubjectIdentifier("Edward R. Murrow");
+        candidate.setSupplementalResourceAttributes(Collections.emptySet());
+        candidate.setSupplementalSubjectAttributes(Collections.emptySet());
+        List<MatchedPolicy> matchedPolicies = this.policyMatcher.match(candidate, policies);
+        Assert.assertEquals(matchedPolicies.size(), 1);
+        Assert.assertEquals(matchedPolicies.get(0).getPolicy(), policies.get(policies.size() - 1));
+        Assert.assertNull(matchedPolicies.get(0).getPolicy().getTarget().getSubject());
+    }
+
+    /**
+     * Tests matching a policy that does not specify a resource (i.e. applies to all resources) to a request.
+     *
+     * @throws IOException
+     *             on failure to load policy required for test.
+     */
+    public void testMatchPolicyWithNoResource() throws IOException {
+        File file = getPolicyFileForTest(getMethodName());
+        PolicySet policySet = PolicySets.loadFromFile(file);
+        List<Policy> policies = policySet.getPolicies();
+
+        PolicyMatchCandidate candidate = new PolicyMatchCandidate();
+        candidate.setAction("GET");
+        candidate.setResourceURI("/public");
+        candidate.setSubjectIdentifier("Edward R. Murrow");
+        candidate.setSupplementalSubjectAttributes(new HashSet<>(
+                Arrays.asList(new Attribute[] { new Attribute("https://acs.attributes.int", "role", "admin") })));
+        List<MatchedPolicy> matchedPolicies = this.policyMatcher.match(candidate, policies);
+        Assert.assertEquals(matchedPolicies.size(), 1);
+        Assert.assertEquals(matchedPolicies.get(0).getPolicy(), policies.get(policies.size() - 1));
+        Assert.assertNull(matchedPolicies.get(0).getPolicy().getTarget().getResource());
+    }
+
+    /**
+     * Tests matching a policy that requires the subject to have a particular attribute and the resource to have a
+     * particular attribute.
+     *
+     * @throws IOException
+     *             on failure to load policy required for test.
+     */
+    public void testMatchPolicyWithSubjectAndResourceAttributeRequirements() throws IOException {
+        File file = getPolicyFileForTest(getMethodName());
+        PolicySet policySet = PolicySets.loadFromFile(file);
+
+        Attribute groupAttr = new Attribute("https://acs.attributes.int", "group", "gog");
+        Set<Attribute> resourceAttributes = new HashSet<>();
+        resourceAttributes.add(groupAttr);
+
+        BaseResource testResource = new BaseResource();
+        testResource.setAttributes(resourceAttributes);
+        testResource.setResourceIdentifier("/assets/1123");
+
+        when(this.defaultResourceAttributeReader.getAttributes(testResource.getResourceIdentifier()))
+            .thenReturn(testResource.getAttributes());
+
+        List<Policy> policies = policySet.getPolicies();
+        PolicyMatchCandidate candidate = new PolicyMatchCandidate();
+        candidate.setAction("GET");
+        candidate.setResourceURI("/assets/1123");
+        candidate.setSubjectIdentifier("Edward R. Murrow");
+        candidate.setSupplementalSubjectAttributes(new HashSet<>(Arrays.asList(new Attribute[] { groupAttr })));
+
+        List<MatchedPolicy> matchedPolicies = this.policyMatcher.match(candidate, policies);
+        Assert.assertEquals(matchedPolicies.size(), 1);
+        Assert.assertEquals(matchedPolicies.get(0).getPolicy(), policies.get(policies.size() - 1));
+        Assert.assertEquals(
+                matchedPolicies.get(0).getPolicy().getTarget().getResource().getAttributes().get(0).getName(), "group");
+        Assert.assertEquals(
+                matchedPolicies.get(0).getPolicy().getTarget().getSubject().getAttributes().get(0).getName(), "group");
+    }
+
+    /**
+     * Tests matching a policy that requires the subject to have a particular attribute and the resource to have a
+     * particular attribute.
+     *
+     * @throws IOException
+     *             on failure to load policy required for test.
+     */
+    public void testMatchPolicyFailureBecauseMissingResourceAttribute() throws IOException {
+        File file = getPolicyFileForTest(getMethodName());
+        PolicySet policySet = PolicySets.loadFromFile(file);
+        List<Policy> policies = policySet.getPolicies();
+
+        PolicyMatchCandidate candidate = new PolicyMatchCandidate();
+        candidate.setAction("GET");
+        candidate.setResourceURI("/assets/1123");
+
+        candidate.setSubjectIdentifier("Edward R. Murrow");
+        candidate.setSupplementalSubjectAttributes(new HashSet<>(
+                Arrays.asList(new Attribute[] { new Attribute("https://acs.attributes.int", "group", "gog") })));
+        List<MatchedPolicy> matchedPolicies = this.policyMatcher.match(candidate, policies);
+        Assert.assertEquals(matchedPolicies.size(), 0);
+    }
+
+    /**
+     * Tests matching a policy that requires a specific URI template and a specific subject attribute.
+     *
+     * @throws IOException
+     *             on failure to load policy required for test.
+     */
+    public void testMatchPolicyWithUriTemplateAndSubjectAttribute() throws IOException {
+        File file = getPolicyFileForTest(getMethodName());
+        PolicySet policySet = PolicySets.loadFromFile(file);
+        List<Policy> policies = policySet.getPolicies();
+
+        PolicyMatchCandidate candidate = new PolicyMatchCandidate();
+        candidate.setAction("GET");
+        candidate.setResourceURI("/sites/1123");
+
+        candidate.setSubjectIdentifier("Edward R. Murrow");
+        Attribute siteAttr = new Attribute("https://acs.attributes.int", "site", "1123");
+        candidate.setSupplementalSubjectAttributes(new HashSet<>(Arrays.asList(new Attribute[] { siteAttr })));
+        List<MatchedPolicy> matchedPolicies = this.policyMatcher.match(candidate, policies);
+        Assert.assertEquals(matchedPolicies.size(), 1);
+        Assert.assertEquals(matchedPolicies.get(0).getPolicy(), policies.get(policies.size() - 1));
+        Assert.assertEquals(matchedPolicies.get(0).getPolicy().getTarget().getResource().getUriTemplate(),
+                "/sites/{site_id}");
+        Assert.assertEquals(
+                matchedPolicies.get(0).getPolicy().getTarget().getSubject().getAttributes().get(0).getName(), "site");
+    }
+
+    /**
+     * Tests a failure to match any policy to the request.
+     *
+     * @throws IOException
+     */
+    public void testMultiplePoliciesNoMatch() throws IOException {
+        File file = getPolicyFileForTest(getMethodName());
+        PolicySet policySet = PolicySets.loadFromFile(file);
+        List<Policy> policies = policySet.getPolicies();
+
+        PolicyMatchCandidate candidate = new PolicyMatchCandidate();
+        candidate.setAction("GET");
+        candidate.setResourceURI("/this/does/not/exist");
+        candidate.setSubjectIdentifier("Edward R. Murrow");
+        candidate.setSupplementalSubjectAttributes(Collections.emptySet());
+        List<MatchedPolicy> matchedPolicies = this.policyMatcher.match(candidate, policies);
+        Assert.assertEquals(matchedPolicies.size(), 0);
+    }
+
+    public void testMultiplePoliciesMultipleMatches() throws IOException {
+        File file = getPolicyFileForTest("policySetWithOverlappingURIs");
+        PolicySet policySet = PolicySets.loadFromFile(file);
+        List<Policy> policies = policySet.getPolicies();
+
+        PolicyMatchCandidate candidate = new PolicyMatchCandidate();
+        candidate.setResourceURI("/alarm/site/site45/asset/asset46");
+        List<MatchedPolicy> matchedPolicies = this.policyMatcher.match(candidate, policies);
+        Assert.assertEquals(matchedPolicies.size(), 2);
+    }
+
+    public void testMultiplePoliciesOneMatch() throws IOException {
+        File file = getPolicyFileForTest("policySetWithOverlappingURIs");
+        PolicySet policySet = PolicySets.loadFromFile(file);
+        List<Policy> policies = policySet.getPolicies();
+
+        PolicyMatchCandidate candidate = new PolicyMatchCandidate();
+        candidate.setResourceURI("/alarm/site/site45");
+        List<MatchedPolicy> matchedPolicies = this.policyMatcher.match(candidate, policies);
+        Assert.assertEquals(matchedPolicies.size(), 1);
+    }
+
+    /**
+     * This is test to make sure the policy matcher has the same behavior as regular Spring URITemplate for the given
+     * data provider: policySetMatcherDataProvider.
+     *
+     * @param uriTemplate
+     *            The URI template that will be used in the policy set
+     * @param uri
+     *            the HTTP Request URI
+     * @param policyMatcherExpectedMatch
+     *            Expected behavior from the Policy Matcher
+     */
+    @Test(dataProvider = "policySetMatcherDataProvider")
+    public void testPolicySetMatcher(final String uriTemplate, final String uri,
+            final Boolean policyMatcherExpectedMatch) throws IOException {
+        doTestForPolicySetMatcher(uriTemplate, uri, policyMatcherExpectedMatch);
+    }
+
+    private void doTestForPolicySetMatcher(final String uriTemplate, final String uri,
+            final Boolean policyMatcherExpectedMatch) throws IOException {
+        PolicySet policySet = PolicySets.loadFromFile(getPolicyFileForTest("singlePolicyNoCondition"));
+
+        policySet.getPolicies().get(0).getTarget().getResource().setUriTemplate(uriTemplate);
+        List<Policy> policies = policySet.getPolicies();
+
+        PolicyMatchCandidate candidate = new PolicyMatchCandidate();
+        candidate.setAction("GET");
+        candidate.setResourceURI(uri);
+        List<MatchedPolicy> matchedPolicies = this.policyMatcher.match(candidate, policies);
+        int expectedSize = policyMatcherExpectedMatch ? 1 : 0;
+        Assert.assertEquals(matchedPolicies.size(), expectedSize, "Policy match count is incorrect");
+    }
+
+    /**
+     * Tests matching multiple actions to a request.
+     *
+     * @throws IOException
+     *             on failure to load policy required for test.
+     */
+    public void testMatchPolicyWithMultipleActions() throws IOException {
+        File file = getPolicyFileForTest(getMethodName());
+        PolicySet policySet = PolicySets.loadFromFile(file);
+        List<Policy> policies = policySet.getPolicies();
+
+        PolicyMatchCandidate candidate = new PolicyMatchCandidate();
+        candidate.setAction("GET");
+        candidate.setResourceURI("/assets/45");
+        List<MatchedPolicy> matchedPolicies = this.policyMatcher.match(candidate, policies);
+        Assert.assertEquals(matchedPolicies.size(), 1);
+    }
+
+    public void testMatchPolicyWithMultipleActionsNoMatch() throws IOException {
+        File file = getPolicyFileForTest(getMethodName());
+        PolicySet policySet = PolicySets.loadFromFile(file);
+        List<Policy> policies = policySet.getPolicies();
+
+        PolicyMatchCandidate candidate = new PolicyMatchCandidate();
+        candidate.setAction("GET");
+        candidate.setResourceURI("/assets/45");
+        List<MatchedPolicy> matchedPolicies = this.policyMatcher.match(candidate, policies);
+        Assert.assertEquals(matchedPolicies.size(), 0);
+    }
+
+    public void testApmPolicySetLoadsSuccessfully() throws Exception {
+        File file = getPolicyFileForTest(getMethodName());
+        PolicySet policySet = PolicySets.loadFromFile(file);
+        List<Policy> policies = policySet.getPolicies();
+
+        GroovyConditionShell shell = new GroovyConditionShell();
+        for (Policy policy : policies) {
+            for (Condition condition : policy.getConditions()) {
+                shell.parse(condition.getCondition());
+            }
+        }
+
+        PolicyMatchCandidate candidate = new PolicyMatchCandidate();
+        candidate.setAction("GET");
+        candidate.setResourceURI("/assets/45");
+        List<MatchedPolicy> matchedPolicies = this.policyMatcher.match(candidate, policies);
+        Assert.assertEquals(matchedPolicies.size(), 1);
+        Assert.assertEquals(matchedPolicies.get(0).getPolicy().getEffect(), Effect.DENY);
+    }
+
+    /**
+     * This test allows us to understand the behavior of the regular Spring URITemplate.
+     *
+     * @param uriTemplate
+     *            The URI template that will be used
+     * @param uri
+     *            the HTTP Request URI
+     * @param uriTemplateExpectedMatch
+     *            Expected behavior from the regular URITemplate
+     * @param varNames
+     *            Expanded variable names
+     * @param varValues
+     *            Expanded variable values
+     */
+    @Test(dataProvider = "uriTemplateMatchDataProvider")
+    public void testURITemplateMatch(final String uriTemplate, final String uri,
+
+            final Boolean uriTemplateExpectedMatch, final String[] varNames, final String[] varValues) {
+        doTestForURITemplateMatch(uriTemplate, uri, uriTemplateExpectedMatch, varNames, varValues);
+    }
+
+    private void doTestForURITemplateMatch(final String uriTemplate, final String uri,
+            final Boolean uriTemplateExpectedMatch, final String[] varNames, final String[] varValues) {
+        UriTemplate template = new UriTemplate(uriTemplate);
+        Assert.assertEquals(template.matches(uri), uriTemplateExpectedMatch.booleanValue());
+
+        Map<String, String> matchedVariables = template.match(uri);
+        for (int i = 0; i < varNames.length; i++) {
+            // skip variable match if name is "n/a"
+            if (varNames[i].equals("n/a")) {
+                continue;
+            }
+
+            Assert.assertEquals(matchedVariables.get(varNames[i]), varValues[i]);
+            Assert.assertEquals(matchedVariables.get(varNames[i]), varValues[i]);
+        }
+    }
+
+    @DataProvider(name = "uriTemplateMatchDataProvider")
+    private Object[][] uriTemplateMatchDataProvider() {
+        return new Object[][] {
+                /**
+                 * Each entry has the following data: uriTemplate, HTTP request URI, URITemplate expected match
+                 * result, Expected expanded URITemplate variable values
+                 */
+                // exact match
+                { "/one/{var1}", "/one/two", Boolean.TRUE, new String[] { "var1" }, new String[] { "two" } },
+                { "/one/{var1}", "/one", Boolean.FALSE, new String[] { "n/a" }, new String[] { "n/a" } },
+
+                // extra sub-path
+                { "/one/{var1}", "/one/two/three", Boolean.TRUE, new String[] { "var1" },
+                        new String[] { "two/three" } },
+
+                // var matches multiple sub-paths.
+                { "/one/{var1}/two", "/one/stuff/in/between/two", Boolean.TRUE, new String[] { "var1" },
+                        new String[] { "stuff/in/between" } },
+
+                // variable matches right-most sub-path
+                { "/one/{var1}/two/{var2}/four", "/one/two/two/two/three/three/four", Boolean.TRUE,
+                        new String[] { "var1", "var2" }, new String[] { "two/two", "three/three" } },
+
+                // no match
+                { "/one/{var1}/two", "/one/two/three", Boolean.FALSE, new String[] { "n/a" }, new String[] { "n/a" } },
+                { "/one", "/one/two", Boolean.FALSE, new String[] { "n/a" }, new String[] { "n/a" } },
+
+                // Eager match of sub-paths (default behavior)
+                { "/customer/{customer_id}", "/customer/c1", Boolean.TRUE, new String[] { "customer_id" },
+                        new String[] { "c1" } },
+                { "/customer/{customer_id}", "/customer/c1site/s1", Boolean.TRUE, new String[] { "customer_id" },
+                        new String[] { "c1site/s1" } },
+
+                // Exact match with sub-paths
+                { "/customer/{customer_id:\\w*}", "/customer/c1", Boolean.TRUE, new String[] { "customer_id" },
+                        new String[] { "c1" } },
+                { "/customer/{customer_id:\\w*}", "/customer/c1/site/s1", Boolean.FALSE, new String[] { "n/a" },
+                        new String[] { "n/a" } },
+
+                { "/customer/{customer_id:\\w*}/", "/customer/c1/", Boolean.TRUE, new String[] { "customer_id" },
+                        new String[] { "c1" } },
+                { "/customer/{customer_id:\\w*}/", "/customer/c1/site/s1/", Boolean.FALSE, new String[] { "n/a" },
+                        new String[] { "n/a" } },
+
+                { "/customer/{customer_id:\\w*}/site/{site_id:\\w*}", "/customer/c1/site/s1", Boolean.TRUE,
+                        new String[] { "customer_id", "site_id" }, new String[] { "c1", "s1" } },
+
+                { "/customer/{customer_id:\\w*}", "/customer/c1/site/s1", Boolean.FALSE, new String[] { "n/a" },
+                        new String[] { "n/a" } },
+
+                { "/asset/", "/asset/", Boolean.TRUE, new String[] { "n/a" }, new String[] { "n/a" } },
+                { "/asset", "/asset", Boolean.TRUE, new String[] { "n/a" }, new String[] { "n/a" } },
+                { "/asset", "/asset//", Boolean.FALSE, new String[] { "n/a" }, new String[] { "n/a" } },
+                { "/asset/", "/asset//", Boolean.FALSE, new String[] { "n/a" }, new String[] { "n/a" } },
+                { "/sites/{site_id}", "/sites/sanramon/", Boolean.TRUE, new String[] { "site_id" },
+                        new String[] { "sanramon/" } },
+                { "/sites/{site_id}", "/sites/sanramon", Boolean.TRUE, new String[] { "site_id" },
+                        new String[] { "sanramon" } },
+                { "/sites/{site_id}/", "/sites/sanramon/", Boolean.TRUE, new String[] { "site_id" },
+                        new String[] { "sanramon" } },
+                { "/myservice/{version}/stuff", "/myservice/v1/stuff", Boolean.TRUE, new String[] { "version" },
+                        new String[] { "v1" } }, };
+    }
+
+    @DataProvider(name = "policySetMatcherDataProvider")
+    private Object[][] policySetMatcherDataProvider() {
+        return new Object[][] {
+                /**
+                 * Each entry has the following data: uriTemplate, HTTP request URI, URITemplate expected match
+                 * result, Expected expanded URITemplate variable values
+                 */
+                // exact match
+                { "/one/{var1}", "/one/two", Boolean.TRUE }, { "/one/{var1}", "/one", Boolean.FALSE },
+
+                // extra sub-path
+                { "/one/{var1}", "/one/two/three", Boolean.TRUE },
+
+                // var matches multiple sub-paths.
+                { "/one/{var1}/two", "/one/stuff/in/between/two", Boolean.TRUE },
+
+                // variable matches right-most sub-path
+                { "/one/{var1}/two/{var2}/four", "/one/two/two/two/three/three/four", Boolean.TRUE },
+
+                // no match
+                { "/one/{var1}/two", "/one/two/three", Boolean.FALSE }, { "/one", "/one/two", Boolean.FALSE },
+
+                // Eager match of sub-paths (default behavior)
+                { "/customer/{customer_id}", "/customer/c1", Boolean.TRUE },
+                { "/customer/{customer_id}", "/customer/c1site/s1", Boolean.TRUE },
+
+                // Exact match with sub-paths
+                { "/customer/{customer_id:\\w*}", "/customer/c1", Boolean.TRUE },
+                { "/customer/{customer_id:\\w*}", "/customer/c1/site/s1", Boolean.FALSE },
+
+                { "/customer/{customer_id:\\w*}/", "/customer/c1/", Boolean.TRUE },
+                { "/customer/{customer_id:\\w*}/", "/customer/c1/site/s1/", Boolean.FALSE },
+
+                { "/customer/{customer_id:\\w*}/site/{site_id:\\w*}", "/customer/c1/site/s1", Boolean.TRUE },
+
+                { "/customer/{customer_id:\\w*}", "/customer/c1/site/s1", Boolean.FALSE },
+
+                { "/asset/", "/asset/", Boolean.TRUE }, { "/asset/", "/asset", Boolean.TRUE },
+                { "/asset", "/asset", Boolean.TRUE }, { "/asset", "/asset//", Boolean.TRUE },
+                { "/asset/", "/asset//", Boolean.TRUE }, { "/asset/", "/asset/../asset", Boolean.TRUE },
+                { "/asset/", "/asset/.", Boolean.TRUE }, { "/sites/{site_id}", "/sites/sanramon/", Boolean.TRUE },
+                { "/sites/{site_id}", "/sites/sanramon", Boolean.TRUE },
+                { "/sites/{site_id}/", "/sites/sanramon/", Boolean.TRUE },
+                { "/myservice/{version}/stuff", "/myservice/v1/stuff", Boolean.TRUE } };
+    }
+
+    /**
+     * This is the test to highlight the different behavior for the Spring URITemplate and the Policy Set Matcher with
+     * which introduces a trailing slash to create canonical URI.
+     */
+    @Test(dataProvider = "uriDataProviderDifferentMatchBehavior")
+    public void testURITemplateMatchVsPolicySetMatch(final String uriTemplate, final String uri,
+            final Boolean uriTemplateExpectedMatch, final Boolean policyMatcherExpectedMatch, final String[] varNames,
+            final String[] varValues) throws IOException {
+        // test the behavior for the Spring URITemplate matcher
+        doTestForURITemplateMatch(uriTemplate, uri, uriTemplateExpectedMatch, varNames, varValues);
+
+        // test the behavior for the policy set matcher
+        doTestForPolicySetMatcher(uriTemplate, uri, policyMatcherExpectedMatch);
+    }
+
+    public void testMatchPolicyUriCanonicalization() throws IOException {
+
+        File file = getPolicyFileForTest(getMethodName());
+        PolicySet policySet = PolicySets.loadFromFile(file);
+        List<Policy> policies = policySet.getPolicies();
+
+        PolicyMatchCandidate candidate = new PolicyMatchCandidate();
+        candidate.setAction("GET");
+        candidate.setResourceURI("/allowed/../not_allowed/gotcha");
+        candidate.setSubjectIdentifier("Edward R. Murrow");
+        candidate.setSupplementalSubjectAttributes(Collections.emptySet());
+        List<MatchedPolicy> matchedPolicies = this.policyMatcher.match(candidate, policies);
+        Assert.assertEquals(matchedPolicies.size(), 1);
+        Assert.assertEquals(matchedPolicies.get(0).getPolicy().getEffect(), Effect.DENY);
+    }
+
+    @DataProvider(name = "uriDataProviderDifferentMatchBehavior")
+    private Object[][] uriDataProviderWithSlash() {
+        return new Object[][] {
+                /**
+                 * Each entry has the following data: uriTemplate, HTTP request URI, URITemplate expected match
+                 * result, Policy Matcher expected match result, Expected expanded URITemplate variable values
+                 */
+
+                { "/asset/", "/asset", Boolean.FALSE, Boolean.TRUE, new String[] { "n/a" }, new String[] { "n/a" } },
+                { "/asset", "/asset/", Boolean.FALSE, Boolean.TRUE, new String[] { "n/a" }, new String[] { "n/a" } },
+                { "/sites/{site_id}/", "/sites/sanramon", Boolean.FALSE, Boolean.TRUE, new String[] { "n/a" },
+                        new String[] { "n/a" } },
+                { "/myservice/{version}/stuff", "/myservice/v1/stuff/", Boolean.FALSE, Boolean.TRUE,
+                        new String[] { "n/a" }, new String[] { "n/a" } }, };
+
+    }
+
+    public static String getMethodName() {
+        return Thread.currentThread().getStackTrace()[2].getMethodName();
+    }
+
+    public static File getPolicyFileForTest(final String methodName) {
+        File file = new File(POLICY_DIR_PATH, methodName + ".json");
+        return file;
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/service/policy/matcher/PolicySets.java b/service/src/test/java/com/ge/predix/acs/service/policy/matcher/PolicySets.java
new file mode 100644
index 0000000..2a32f9d
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/service/policy/matcher/PolicySets.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.matcher;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ge.predix.acs.model.PolicySet;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+public final class PolicySets {
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+    private PolicySets() {
+        // Prevents instantiation.
+    }
+
+    @SuppressWarnings("javadoc")
+    public static PolicySet loadFromFile(final File file) throws IOException {
+        return OBJECT_MAPPER.readValue(file, PolicySet.class);
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/service/policy/matcher/UriTemplateVariableResolverTest.java b/service/src/test/java/com/ge/predix/acs/service/policy/matcher/UriTemplateVariableResolverTest.java
new file mode 100644
index 0000000..4b15d60
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/service/policy/matcher/UriTemplateVariableResolverTest.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.matcher;
+
+import org.springframework.web.util.UriTemplate;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+@Test
+public class UriTemplateVariableResolverTest {
+
+    private final UriTemplateVariableResolver attributeUriResolver = new UriTemplateVariableResolver();
+
+    @Test(dataProvider = "uriDataProvider")
+    public void testMatch(final String uri, final UriTemplate attributeUriTemplate, final Object matchedURI) {
+        Assert.assertEquals(this.attributeUriResolver.resolve(uri, attributeUriTemplate, "attribute_uri"), matchedURI);
+    }
+
+    @DataProvider
+    Object[][] uriDataProvider() {
+        return new Object[][] {
+
+                { "/v1/site/123", new UriTemplate("/v1{attribute_uri}"), "/site/123" },
+                { "/v1/site/123/asset/345", new UriTemplate("/v1{attribute_uri}/asset/345"), "/site/123" },
+                { "/v1/site/123/asset/345", new UriTemplate("/v1{attribute_uri}/asset/{site_id}"), "/site/123" },
+                { "/v1/site/123/asset/345", new UriTemplate("/v1/site/123{attribute_uri}"), "/asset/345" },
+                { "/v1/site/123/asset/345", new UriTemplate("/v1{attribute_uri}"), "/site/123/asset/345" },
+
+                { "/v1/site/123/asset/345/report", new UriTemplate("/v1{attribute_uri}/report"),
+                        "/site/123/asset/345" },
+
+                // template doesnt match uri
+                { "/v1/site/123/asset/345/report", new UriTemplate("/v2{attribute_uri}"), null },
+                // no attribute_uri variable in template
+                { "/v1/site/123/asset/345/report", new UriTemplate("/v1{non_existent_variable}"), null },
+                // multiple attribute_uri variables in template
+                { "/v1/site/123/asset/345", new UriTemplate("/v1{attribute_uri}/{attribute_uri}"), "345" }, };
+    }
+
+}
diff --git a/service/src/test/java/com/ge/predix/acs/service/policy/validation/PolicySetValidatorTest.java b/service/src/test/java/com/ge/predix/acs/service/policy/validation/PolicySetValidatorTest.java
new file mode 100644
index 0000000..d545a13
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/service/policy/validation/PolicySetValidatorTest.java
@@ -0,0 +1,234 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.service.policy.validation;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.commons.policy.condition.ConditionScript;
+import com.ge.predix.acs.commons.policy.condition.ConditionShell;
+import com.ge.predix.acs.commons.policy.condition.groovy.GroovyConditionCache;
+import com.ge.predix.acs.commons.policy.condition.groovy.GroovyConditionShell;
+import com.ge.predix.acs.model.Condition;
+import com.ge.predix.acs.model.Policy;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.utils.JsonUtils;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@Test
+@ContextConfiguration(
+        classes = { GroovyConditionCache.class, GroovyConditionShell.class, PolicySetValidatorImpl.class })
+public class PolicySetValidatorTest extends AbstractTestNGSpringContextTests {
+
+    private final JsonUtils jsonUtils = new JsonUtils();
+    @Autowired
+    private PolicySetValidator policySetValidator;
+
+    @BeforeClass
+    public void setup() {
+        ((PolicySetValidatorImpl) this.policySetValidator)
+                .setValidAcsPolicyHttpActions("GET, POST, PUT, DELETE, PATCH, SUBSCRIBE, MESSAGE");
+        ((PolicySetValidatorImpl) this.policySetValidator).init();
+    }
+
+    @Test
+    public void testSuccesfulSchemaValidation() {
+
+        PolicySet policySet = this.jsonUtils.deserializeFromFile("set-with-2-policy.json", PolicySet.class);
+
+        this.policySetValidator.validatePolicySet(policySet);
+    }
+
+    @Test(dataProvider = "invalidPolicyProvider")
+    public void testUnsuccessfulSchemaValidation(final String fileName, final String causeSubstring) {
+
+        PolicySet policySet = this.jsonUtils.deserializeFromFile(fileName, PolicySet.class);
+        Assert.assertNotNull(policySet);
+        try {
+            this.policySetValidator.validatePolicySet(policySet);
+            Assert.fail("Negative test case should have failed for file " + fileName);
+        } catch (PolicySetValidationException e) {
+            Assert.assertTrue(e.getMessage().contains(causeSubstring),
+                    String.format("Expected %s vs Actual %s", causeSubstring, e.getMessage()));
+        }
+    }
+
+    @DataProvider(name = "invalidPolicyProvider")
+    public Object[][] getInvalidPolicies() {
+        Object[][] data = new Object[][] {
+                { "policyset/validator/test/missing-effect-policy.json", "missing: [\"effect\"]" },
+                { "policyset/validator/test/missing-name-policy-set.json", "missing: [\"name\"]" },
+                { "policyset/validator/test/empty-policies-policy-set.json", "/properties/policies" },
+                { "policyset/validator/test/no-policies-policy-set.json", "/properties/policies" },
+                { "policyset/validator/test/missing-resource-policy-target.json", "missing: [\"resource\"]" },
+                { "policyset/validator/test/missing-uritemplate-policy-resource.json", "missing: [\"uriTemplate\"]" },
+                { "policyset/validator/test/missing-condition-policy-condition.json", "missing: [\"condition\"]" },
+                { "policyset/validator/test/testMatchPolicyWithInvalidAction.json", "Policy Action validation failed" },
+                { "policyset/validator/test/testMatchPolicyWithMultipleActionsOneInvalid.json",
+                        "Policy Action validation failed" }, };
+        return data;
+    }
+
+    @Test
+    public void testSuccesfulEmptySubjectAttributesSchemaValidation() {
+
+        PolicySet policySet = this.jsonUtils
+                .deserializeFromFile("policyset/validator/test/empty-attributes-target-subject.json", PolicySet.class);
+        Assert.assertNotNull(policySet);
+        this.policySetValidator.validatePolicySet(policySet);
+    }
+
+    @Test
+    public void testSuccesfulEmptyConditionsPolicySchemaValidation() {
+
+        PolicySet policySet = this.jsonUtils
+                .deserializeFromFile("policyset/validator/test/empty-conditions-policy.json", PolicySet.class);
+        Assert.assertNotNull(policySet);
+        this.policySetValidator.validatePolicySet(policySet);
+    }
+
+    @Test
+    public void testSuccesfulMissingConditionNameSchemaValidation() {
+
+        PolicySet policySet = this.jsonUtils
+                .deserializeFromFile("policyset/validator/test/missing-condition-name-policy.json", PolicySet.class);
+        Assert.assertNotNull(policySet);
+        this.policySetValidator.validatePolicySet(policySet);
+    }
+
+    @Test
+    public void testSuccesfulMissingTargetNameSchemaValidation() {
+
+        PolicySet policySet = this.jsonUtils
+                .deserializeFromFile("policyset/validator/test/missing-target-name-policy.json", PolicySet.class);
+        Assert.assertNotNull(policySet);
+        this.policySetValidator.validatePolicySet(policySet);
+    }
+
+    @Test
+    public void testSuccesfulOnlyPolicySetNameAndEffectSchemaValidation() {
+
+        PolicySet policySet = this.jsonUtils
+                .deserializeFromFile("policyset/validator/test/policy-set-with-only-name-effect.json", PolicySet.class);
+        Assert.assertNotNull(policySet);
+        this.policySetValidator.validatePolicySet(policySet);
+    }
+
+    @Test
+    public void testEmptyPolicyConditions() {
+        this.policySetValidator.validatePolicyConditions(new ArrayList<Condition>());
+    }
+
+    @Test
+    public void testNullPolicyConditions() {
+        this.policySetValidator.validatePolicyConditions(null);
+    }
+
+    @Test
+    public void testPolicyConditionsWithOneValidCondition() {
+        Condition condition = new Condition("'a'.equals('b')");
+        List<ConditionScript> conditionScripts = this.policySetValidator
+                .validatePolicyConditions(Arrays.asList(condition));
+        Assert.assertEquals(conditionScripts.size(), 1);
+        Assert.assertNotNull(conditionScripts.get(0));
+    }
+
+    @Test
+    public void testPolicyConditionsWithMultipleValidCondition() {
+        Condition condition = new Condition("'a'.equals('b')");
+        Condition condition2 = new Condition("'a'.equals('c')");
+        List<ConditionScript> conditionScripts = this.policySetValidator
+                .validatePolicyConditions(Arrays.asList(condition, condition2));
+        Assert.assertEquals(conditionScripts.size(), 2);
+        Assert.assertNotNull(conditionScripts.get(0));
+        Assert.assertNotNull(conditionScripts.get(1));
+    }
+
+    @Test(expectedExceptions = { PolicySetValidationException.class })
+    public void testPolicyConditionsWithOneInvalidCondition() {
+        Condition condition = new Condition("System.exit(0)");
+        Condition condition2 = new Condition("'a'.equals('c')");
+        this.policySetValidator.validatePolicyConditions(Arrays.asList(condition, condition2));
+
+    }
+
+    @Test
+    public void testMatchPolicyWithMultipleActions() {
+        PolicySet policySet = this.jsonUtils.deserializeFromFile(
+                "policyset/validator/test/testMatchPolicyWithMultipleActions.json", PolicySet.class);
+        Assert.assertNotNull(policySet);
+        this.policySetValidator.validatePolicySet(policySet);
+
+    }
+
+    @Test
+    public void testpolicyWithNullTargetAction() {
+        PolicySet policySet = this.jsonUtils
+                .deserializeFromFile("policyset/validator/test/policyWithNullTargetAction.json", PolicySet.class);
+        Assert.assertNotNull(policySet);
+        this.policySetValidator.validatePolicySet(policySet);
+        Assert.assertNull(policySet.getPolicies().get(0).getTarget().getAction());
+    }
+
+    @Test
+    public void testpolicyWithEmptyTargetAction() {
+        PolicySet policySet = this.jsonUtils
+                .deserializeFromFile("policyset/validator/test/policyWithEmptyTargetAction.json", PolicySet.class);
+        Assert.assertNotNull(policySet);
+        this.policySetValidator.validatePolicySet(policySet);
+        Assert.assertNull(policySet.getPolicies().get(0).getTarget().getAction());
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testRemovalOfMultipleCachedConditions() {
+        PolicySet policySet = this.jsonUtils.deserializeFromFile(
+                "policyset/validator/test/multiple-policies-with-multiple-conditions.json", PolicySet.class);
+        Assert.assertNotNull(policySet);
+        this.policySetValidator.validatePolicySet(policySet);
+        GroovyConditionCache conditionCache =
+                (GroovyConditionCache) ReflectionTestUtils.getField(this.policySetValidator, "conditionCache");
+        Map<String, ConditionShell> cache =
+                (Map<String, ConditionShell>) ReflectionTestUtils.getField(conditionCache, "cache");
+        int cacheSize = cache.size();
+        Assert.assertTrue(cacheSize > 0);
+        this.policySetValidator.removeCachedConditions(policySet);
+        Assert.assertEquals(cache.size(), cacheSize - 3);
+        for (Policy policy : policySet.getPolicies()) {
+            for (Condition condition : policy.getConditions()) {
+                Assert.assertNull(conditionCache.get(condition.getCondition()));
+            }
+        }
+    }
+
+}
diff --git a/service/src/test/java/com/ge/predix/acs/testutils/MockAcsRequestContext.java b/service/src/test/java/com/ge/predix/acs/testutils/MockAcsRequestContext.java
new file mode 100644
index 0000000..e136dbd
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/testutils/MockAcsRequestContext.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.testutils;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import com.ge.predix.acs.request.context.AcsRequestContextHolder;
+
+public final class MockAcsRequestContext {
+    private MockAcsRequestContext() {
+        // Prevents instantiation.
+    }
+
+    public static void mockAcsRequestContext() {
+        try {
+            Method method = AcsRequestContextHolder.class.getDeclaredMethod("initialize");
+            method.setAccessible(true);
+            Object nullObj = null;
+            Object[] nullArgs = null;
+            method.invoke(nullObj, nullArgs);
+        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
+                | InvocationTargetException e) {
+            Throwable cause = e.getCause();
+            cause.printStackTrace();
+            // System.err.format("drinkMe() failed: %s%n", cause.getMessage());
+            e.printStackTrace();
+            throw new RuntimeException("Test Set up Failed.");
+        }
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/testutils/MockMvcContext.java b/service/src/test/java/com/ge/predix/acs/testutils/MockMvcContext.java
new file mode 100644
index 0000000..a37b8dc
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/testutils/MockMvcContext.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.testutils;
+
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+
+public class MockMvcContext {
+    private MockMvc mockMvc;
+    private MockHttpServletRequestBuilder builder;
+
+    public MockHttpServletRequestBuilder getBuilder() {
+        return this.builder;
+    }
+
+    public void setBuilder(final MockHttpServletRequestBuilder builder) {
+        this.builder = builder;
+    }
+
+    public MockMvc getMockMvc() {
+        return this.mockMvc;
+    }
+
+    public void setMockMvc(final MockMvc mockMvc) {
+        this.mockMvc = mockMvc;
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/testutils/MockSecurityContext.java b/service/src/test/java/com/ge/predix/acs/testutils/MockSecurityContext.java
new file mode 100644
index 0000000..4cc6236
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/testutils/MockSecurityContext.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.testutils;
+
+import static org.mockito.Mockito.when;
+
+import org.mockito.Mockito;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.uaa.token.lib.ZoneOAuth2Authentication;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+public final class MockSecurityContext {
+    private MockSecurityContext() {
+        // Prevents instantiation.
+    }
+
+    public static void mockSecurityContext(final Zone zone) {
+        ZoneOAuth2Authentication acsZoneOAuth = Mockito.mock(ZoneOAuth2Authentication.class);
+        if (zone != null) {
+            when(acsZoneOAuth.getZoneId()).thenReturn(zone.getSubdomain());
+        }
+        SecurityContextHolder.getContext().setAuthentication(acsZoneOAuth);
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/testutils/TestActiveProfilesResolver.java b/service/src/test/java/com/ge/predix/acs/testutils/TestActiveProfilesResolver.java
new file mode 100644
index 0000000..e9e802e
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/testutils/TestActiveProfilesResolver.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.testutils;
+
+import org.apache.commons.lang.StringUtils;
+import org.springframework.test.context.ActiveProfilesResolver;
+
+public class TestActiveProfilesResolver implements ActiveProfilesResolver {
+
+    @Override
+    public String[] resolve(final Class<?> arg0) {
+        String envSpringProfilesActive = System.getenv("SPRING_PROFILES_ACTIVE");
+        String[] profiles = new String[] { "h2", "public", "simple-cache", "titan" };
+        if (StringUtils.isNotEmpty(envSpringProfilesActive)) {
+            profiles = StringUtils.split(envSpringProfilesActive, ',');
+        }
+        System.out.println("SPRING_ACTIVE_PROFILES: " + StringUtils.join(profiles, ','));
+        return profiles;
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/testutils/TestUtils.java b/service/src/test/java/com/ge/predix/acs/testutils/TestUtils.java
new file mode 100644
index 0000000..5896bb2
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/testutils/TestUtils.java
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.testutils;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.springframework.aop.framework.Advised;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.http.MediaType;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+import org.testng.Assert;
+
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.zone.management.ZoneService;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+
+/**
+ * @author acs-engineers@ge.com
+ */
+public class TestUtils {
+
+    // to set the a field of object for testing
+    public void setField(final Object target, final String name, final Object value) {
+
+        // check if the object is a proxy object
+        if (AopUtils.isAopProxy(target) && target instanceof Advised) {
+            try {
+                ReflectionTestUtils.setField(((Advised) target).getTargetSource().getTarget(), name, value);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        } else {
+            ReflectionTestUtils.setField(target, name, value);
+        }
+    }
+
+    public Zone createZone(final String name, final String subdomain) {
+        Zone zone = new Zone();
+        zone.setName(name);
+        zone.setSubdomain(subdomain);
+        return zone;
+    }
+
+    /**
+     * @returns a zone entity for a name 'Default acs Zone' and subdomain 'default_subdomain'
+     */
+    public ZoneEntity createDefaultZoneEntity() {
+        ZoneEntity zoneEntity = new ZoneEntity();
+        zoneEntity.setDescription("default zoneEnity for test");
+        zoneEntity.setName("Default Acs Zone");
+        zoneEntity.setSubdomain("default-subdomain");
+        return zoneEntity;
+    }
+
+    public Zone createTestZone(final String testName) {
+        return new Zone(testName + ".zone", testName + "-subdomain", "");
+    }
+
+    public Zone setupTestZone(final String testName, final ZoneService zoneService) {
+        Zone testZone = createTestZone(testName);
+        zoneService.upsertZone(testZone);
+        MockSecurityContext.mockSecurityContext(testZone);
+        MockAcsRequestContext.mockAcsRequestContext();
+        return testZone;
+    }
+
+    public MockMvcContext createWACWithCustomGETRequestBuilder(final WebApplicationContext wac, final String subdomain,
+            final String resourceURI) throws URISyntaxException {
+        MockMvcContext result = new MockMvcContext();
+        result.setBuilder(MockMvcRequestBuilders.get(new URI("http://" + subdomain + ".localhost/" + resourceURI))
+                .accept(MediaType.APPLICATION_JSON));
+        result.setMockMvc(MockMvcBuilders.webAppContextSetup(wac).defaultRequest(result.getBuilder()).build());
+        return result;
+    }
+
+    public MockMvcContext createWACWithCustomDELETERequestBuilder(final WebApplicationContext wac,
+            final String subdomain, final String resourceURI) throws URISyntaxException {
+        MockMvcContext result = new MockMvcContext();
+        result.setBuilder(MockMvcRequestBuilders.delete(new URI("http://" + subdomain + ".localhost/" + resourceURI)));
+        result.setMockMvc(MockMvcBuilders.webAppContextSetup(wac).defaultRequest(result.getBuilder()).build());
+        return result;
+    }
+
+    public MockMvcContext createWACWithCustomPUTRequestBuilder(final WebApplicationContext wac, final String subdomain,
+            final String resourceURI) throws URISyntaxException {
+        MockMvcContext result = new MockMvcContext();
+        result.setBuilder(MockMvcRequestBuilders.put(new URI("http://" + subdomain + ".localhost/" + resourceURI)));
+        result.setMockMvc(MockMvcBuilders.webAppContextSetup(wac).defaultRequest(result.getBuilder()).build());
+        return result;
+    }
+
+    public MockMvcContext createWACWithCustomPOSTRequestBuilder(final WebApplicationContext wac, final String subdomain,
+            final String resourceURI) throws URISyntaxException {
+        MockMvcContext result = new MockMvcContext();
+        result.setBuilder(MockMvcRequestBuilders.post(new URI("http://" + subdomain + ".localhost/" + resourceURI)));
+        result.setMockMvc(MockMvcBuilders.webAppContextSetup(wac).defaultRequest(result.getBuilder()).build());
+        return result;
+    }
+
+    public static class TestAssertion {
+        public static void assertDoesNotThrow(final Class<?> unexpectedException, final Runnable executableCode) {
+
+            /*
+            TODO We should Start moving tests to use this assertDoesNotThrow method wherever we use the try { ...
+            } catch (Exception e) { Assert.fail(); }
+
+            */
+            try {
+                executableCode.run();
+            } catch (Exception e) {
+                if (e.getClass() == unexpectedException) {
+                    Assert.fail();
+                }
+                throw e;
+            }
+        }
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/testutils/XFiles.java b/service/src/test/java/com/ge/predix/acs/testutils/XFiles.java
new file mode 100644
index 0000000..ce10a71
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/testutils/XFiles.java
@@ -0,0 +1,188 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.testutils;
+
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.Parent;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public final class XFiles {
+    private XFiles() {
+        // Prevents instantiation.
+    }
+
+    public static final String ISSUER_URI = "acs.example.org";
+    public static final Attribute SITE_BASEMENT = new Attribute(ISSUER_URI, "site", "basement");
+    public static final Attribute SITE_PENTAGON = new Attribute(ISSUER_URI, "site", "pentagon");
+    public static final Attribute SITE_QUANTICO = new Attribute(ISSUER_URI, "site", "quantico");
+    public static final Attribute TOP_SECRET_CLASSIFICATION = new Attribute(ISSUER_URI, "classification", "top secret");
+    public static final Attribute SECRET_CLASSIFICATION = new Attribute(ISSUER_URI, "classification", "secret");
+    public static final Attribute SPECIAL_AGENTS_GROUP_ATTRIBUTE = new Attribute(ISSUER_URI, "group", "special-agents");
+    public static final Attribute TYPE_MYTHARC = new Attribute(ISSUER_URI, "type", "mytharc");
+    public static final Attribute TYPE_MONSTER_OF_THE_WEEK = new Attribute("acs.predix.ge.com", "type",
+            "monster-of-the-week");
+
+    public static final String FBI = "fbi";
+    public static final Set<Attribute> FBI_ATTRIBUTES =
+        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new Attribute[] {SITE_QUANTICO })));
+
+    public static final String SPECIAL_AGENTS_GROUP = "special-agents";
+    public static final Set<Attribute> SPECIAL_AGENTS_GROUP_ATTRIBUTES =
+        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new Attribute[] { SPECIAL_AGENTS_GROUP_ATTRIBUTE })));
+
+    public static final String TOP_SECRET_GROUP = "top-secret-clearance";
+    public static final Set<Attribute> TOP_SECRET_GROUP_ATTRIBUTES =
+        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new Attribute[] { TOP_SECRET_CLASSIFICATION })));
+
+    public static final String SECRET_GROUP = "secret-clearance";
+    public static final Set<Attribute> SECRET_GROUP_ATTRIBUTES =
+        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new Attribute[] { SECRET_CLASSIFICATION })));
+
+    public static final String AGENT_MULDER = "mulder";
+    public static final Set<Attribute> MULDERS_ATTRIBUTES =
+        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new Attribute[] { SITE_BASEMENT })));
+
+    public static final String AGENT_SCULLY = "scully";
+    public static final Set<Attribute> SCULLYS_ATTRIBUTES =
+        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new Attribute[] { SITE_BASEMENT })));
+
+    public static final String BASEMENT_SITE_ID = "/site/basement";
+    public static final Set<Attribute> BASEMENT_ATTRIBUTES =
+        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new Attribute[] { SITE_BASEMENT })));
+
+    public static final String PENTAGON_SITE_ID = "/site/pentagon";
+    public static final Set<Attribute> PENTAGON_ATTRIBUTES =
+        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new Attribute[] { SITE_PENTAGON })));
+
+    public static final String XFILES_ID = "/x-files";
+
+    public static final String ASCENSION_ID = "/x-files/ascension";
+    public static final Set<Attribute> ASCENSION_ATTRIBUTES =
+        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new Attribute[] { SPECIAL_AGENTS_GROUP_ATTRIBUTE,
+                                                                                  TYPE_MYTHARC })));
+
+    public static final String DRIVE_ID = "/x-files/drive";
+    public static final Set<Attribute> DRIVE_ATTRIBUTES =
+        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new Attribute[] { TYPE_MONSTER_OF_THE_WEEK })));
+
+    public static final String JOSECHUNG_ID = "/x-files/josechung";
+    public static final Set<Attribute> JOSECHUNG_ATTRIBUTES =
+        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new Attribute[] { TYPE_MONSTER_OF_THE_WEEK })));
+
+    public static final String EVIDENCE_SCULLYS_TESTIMONY_ID = "/evidence/scullys-testimony";
+    public static final Set<Attribute> SCULLYS_TESTIMONY_ATTRIBUTES =
+        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new Attribute[] { TOP_SECRET_CLASSIFICATION })));
+
+    public static final String EVIDENCE_IMPLANT_ID = "/evidence/implant";
+    public static final Set<Attribute> EVIDENCE_IMPLANT_ATTRIBUTES =
+        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new Attribute[] { TOP_SECRET_CLASSIFICATION })));
+
+    public static List<BaseSubject> createSubjectHierarchy() {
+        BaseSubject fbi = new BaseSubject(FBI);
+        fbi.setAttributes(FBI_ATTRIBUTES);
+
+        BaseSubject specialAgentsGroup = new BaseSubject(SPECIAL_AGENTS_GROUP);
+        specialAgentsGroup.setAttributes(SPECIAL_AGENTS_GROUP_ATTRIBUTES);
+        specialAgentsGroup
+                .setParents(new HashSet<>(Arrays.asList(new Parent[] { new Parent(fbi.getSubjectIdentifier()) })));
+
+        BaseSubject topSecretGroup = new BaseSubject(TOP_SECRET_GROUP);
+        topSecretGroup.setAttributes(TOP_SECRET_GROUP_ATTRIBUTES);
+
+        BaseSubject agentMulder = new BaseSubject(AGENT_MULDER);
+        agentMulder.setAttributes(MULDERS_ATTRIBUTES);
+        agentMulder.setParents(
+                new HashSet<>(Arrays.asList(new Parent[] { new Parent(specialAgentsGroup.getSubjectIdentifier()),
+                        new Parent(topSecretGroup.getSubjectIdentifier()) })));
+
+        return Arrays.asList(new BaseSubject[] { fbi, specialAgentsGroup, topSecretGroup, agentMulder });
+    }
+
+    public static List<BaseResource> createTwoParentResourceHierarchy() {
+        BaseResource pentagon = new BaseResource(PENTAGON_SITE_ID);
+        pentagon.setAttributes(PENTAGON_ATTRIBUTES);
+
+        BaseResource ascension = new BaseResource(ASCENSION_ID);
+        ascension.setAttributes(ASCENSION_ATTRIBUTES);
+
+        BaseResource evidenceImplant = new BaseResource(EVIDENCE_IMPLANT_ID);
+        evidenceImplant.setAttributes(EVIDENCE_IMPLANT_ATTRIBUTES);
+        evidenceImplant.setParents(new HashSet<>(Arrays.asList(new Parent[] {
+                new Parent(pentagon.getResourceIdentifier()), new Parent(ascension.getResourceIdentifier()) })));
+
+        return Arrays.asList(new BaseResource[] { pentagon, ascension, evidenceImplant });
+    }
+
+    public static List<BaseResource> createTwoLevelResourceHierarchy() {
+
+        BaseResource basement = new BaseResource(BASEMENT_SITE_ID);
+        basement.setAttributes(BASEMENT_ATTRIBUTES);
+
+        BaseResource scullysTestimony = new BaseResource(EVIDENCE_SCULLYS_TESTIMONY_ID);
+        scullysTestimony.setAttributes(SCULLYS_TESTIMONY_ATTRIBUTES);
+        scullysTestimony.setParents(new HashSet<>(Arrays.asList(new Parent[] {
+                new Parent(basement.getResourceIdentifier())})));
+
+        return Arrays.asList(new BaseResource[] { basement, scullysTestimony });
+    }
+
+    public static List<BaseResource> createThreeLevelResourceHierarchy() {
+        BaseResource basement = new BaseResource(BASEMENT_SITE_ID);
+        basement.setAttributes(BASEMENT_ATTRIBUTES);
+
+        BaseResource ascension = new BaseResource(ASCENSION_ID);
+        ascension.setAttributes(ASCENSION_ATTRIBUTES);
+        ascension.setParents(
+                new HashSet<>(Arrays.asList(new Parent[] { new Parent(basement.getResourceIdentifier()) })));
+
+        BaseResource scullysTestimony = new BaseResource(EVIDENCE_SCULLYS_TESTIMONY_ID);
+        scullysTestimony.setAttributes(SCULLYS_TESTIMONY_ATTRIBUTES);
+        scullysTestimony.setParents(
+                new HashSet<>(Arrays.asList(new Parent[] { new Parent(ascension.getResourceIdentifier()) })));
+
+        return Arrays.asList(new BaseResource[] { basement, ascension, scullysTestimony });
+    }
+
+    public static List<BaseSubject> createScopedSubjectHierarchy() {
+        BaseSubject fbi = new BaseSubject(FBI);
+        fbi.setAttributes(FBI_ATTRIBUTES);
+
+        BaseSubject specialAgentsGroup = new BaseSubject(SPECIAL_AGENTS_GROUP);
+        specialAgentsGroup.setAttributes(SPECIAL_AGENTS_GROUP_ATTRIBUTES);
+        specialAgentsGroup
+                .setParents(new HashSet<>(Arrays.asList(new Parent[] { new Parent(fbi.getSubjectIdentifier()) })));
+
+        BaseSubject topSecretGroup = new BaseSubject(TOP_SECRET_GROUP);
+        topSecretGroup.setAttributes(TOP_SECRET_GROUP_ATTRIBUTES);
+
+        BaseSubject agentMulder = new BaseSubject(AGENT_MULDER);
+        agentMulder.setAttributes(MULDERS_ATTRIBUTES);
+        agentMulder.setParents(new HashSet<>(Arrays.asList(new Parent[] {
+                new Parent(specialAgentsGroup.getSubjectIdentifier()), new Parent(topSecretGroup.getSubjectIdentifier(),
+                        new HashSet<>(Arrays.asList(new Attribute[] { SITE_BASEMENT }))) })));
+        return Arrays.asList(new BaseSubject[] { fbi, specialAgentsGroup, topSecretGroup, agentMulder });
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/zone/management/ZoneEntityTest.java b/service/src/test/java/com/ge/predix/acs/zone/management/ZoneEntityTest.java
new file mode 100644
index 0000000..9ebe95b
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/zone/management/ZoneEntityTest.java
@@ -0,0 +1,147 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.zone.management;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.SpringSecurityPolicyContextResolver;
+import com.ge.predix.acs.config.GraphBeanDefinitionRegistryPostProcessor;
+import com.ge.predix.acs.config.GraphConfig;
+import com.ge.predix.acs.config.InMemoryDataSourceConfig;
+import com.ge.predix.acs.privilege.management.dao.ResourceEntity;
+import com.ge.predix.acs.privilege.management.dao.ResourceRepository;
+import com.ge.predix.acs.privilege.management.dao.SubjectEntity;
+import com.ge.predix.acs.privilege.management.dao.SubjectRepository;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.service.policy.admin.dao.PolicySetEntity;
+import com.ge.predix.acs.service.policy.admin.dao.PolicySetRepository;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.ge.predix.acs.zone.management.dao.ZoneRepository;
+import com.ge.predix.acs.zone.resolver.SpringSecurityZoneResolver;
+
+@ContextConfiguration(
+        classes = { GraphBeanDefinitionRegistryPostProcessor.class, GraphConfig.class, 
+                InMemoryDataSourceConfig.class, SpringSecurityPolicyContextResolver.class,
+                SpringSecurityZoneResolver.class, ZoneServiceImpl.class })
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+@Test(singleThreaded = true)
+public class ZoneEntityTest extends AbstractTransactionalTestNGSpringContextTests {
+
+    @Autowired
+    private ZoneRepository zoneRepository;
+
+    @Autowired
+    private SubjectRepository subjectRepository;
+
+    @Autowired
+    private ResourceRepository resourceRepository;
+
+    @Autowired
+    private PolicySetRepository policySetRepository;
+
+    @Autowired
+    private ZoneService service;
+
+    // zone with 1 issuer - issuer1
+    private Zone zone1;
+
+    // Some tests modify this standard setup. Reset before every test.
+    @BeforeMethod
+    public void setup() {
+        this.zone1 = new Zone("zone1", "zone1", "description");
+    }
+
+    public void testCreateZoneWithConstraintViolationSubdomain() {
+        Zone testzone1 = new Zone("zone1", "subdomain1", "description");
+        Zone zone2SameSubdomain = new Zone("zone2", "subdomain1", "description");
+
+        try {
+            this.service.upsertZone(testzone1);
+            this.service.upsertZone(zone2SameSubdomain);
+
+        } catch (ZoneManagementException e) {
+
+            Assert.assertEquals(e.getMessage(), "Subdomain subdomain1 for zoneName = zone2 is already being used.");
+            this.service.deleteZone(testzone1.getName());
+            this.service.deleteZone(zone2SameSubdomain.getName());
+            return;
+        }
+        this.service.deleteZone(testzone1.getName());
+        this.service.deleteZone(zone2SameSubdomain.getName());
+        Assert.fail("Expected ZoneManagementException to be thrown.");
+    }
+
+    public void testUpdateZoneSubdomain() {
+        this.service.upsertZone(this.zone1);
+        Zone updatedZone1 = new Zone("zone1", "updated-subdomain", "description");
+        this.service.upsertZone(updatedZone1);
+        ZoneEntity actualZone = this.zoneRepository.getByName(this.zone1.getName());
+        Assert.assertEquals(actualZone.getSubdomain(), "updated-subdomain");
+    }
+
+    public void testCreateZone() {
+        this.service.upsertZone(this.zone1);
+        Zone zone1Actual = this.service.retrieveZone(this.zone1.getName());
+        Assert.assertEquals(zone1Actual, this.zone1);
+    }
+
+    public void testDeleteZoneWithCascade() {
+
+        // create test zone
+        Zone testZone = new Zone("test-zone", "test-zone-subdomain", "description");
+        this.service.upsertZone(testZone);
+        ZoneEntity testZoneEntity = this.zoneRepository.getByName(testZone.getName());
+
+        // put subject, resource, and policy
+        SubjectEntity subjectEntity = new SubjectEntity(testZoneEntity, "bob");
+        subjectEntity.setAttributesAsJson("{}");
+        this.subjectRepository.save(subjectEntity);
+
+        ResourceEntity resourceEntity = new ResourceEntity(testZoneEntity, "bob");
+        resourceEntity.setAttributesAsJson("[]");
+        this.resourceRepository.save(resourceEntity);
+
+        PolicySetEntity policySetEntity = new PolicySetEntity(testZoneEntity, "policy-set-2", "{}");
+        this.policySetRepository.save(policySetEntity);
+
+        // Check if in repo
+        Assert.assertEquals(this.subjectRepository.getByZoneAndSubjectIdentifier(testZoneEntity, "bob"), subjectEntity);
+        Assert.assertEquals(this.resourceRepository.getByZoneAndResourceIdentifier(testZoneEntity, "bob"),
+                resourceEntity);
+        Assert.assertEquals(this.policySetRepository.getByZoneAndPolicySetId(testZoneEntity, "policy-set-2"),
+                policySetEntity);
+        Assert.assertEquals(testZoneEntity.getName(), testZone.getName());
+
+        // delete zone and assert proper cascading
+        this.service.deleteZone(testZone.getName());
+        Assert.assertEquals(this.subjectRepository.getByZoneAndSubjectIdentifier(testZoneEntity, "bob"), null);
+        Assert.assertEquals(this.resourceRepository.getByZoneAndResourceIdentifier(testZoneEntity, "bob"), null);
+        Assert.assertEquals(this.policySetRepository.getByZoneAndPolicySetId(testZoneEntity, "policy-set-2"), null);
+        testZoneEntity = this.zoneRepository.getByName(testZone.getName());
+        Assert.assertEquals(testZoneEntity, null);
+
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/zone/management/ZoneHttpMethodsFilterTest.java b/service/src/test/java/com/ge/predix/acs/zone/management/ZoneHttpMethodsFilterTest.java
new file mode 100644
index 0000000..c673b51
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/zone/management/ZoneHttpMethodsFilterTest.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.zone.management;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mockito.InjectMocks;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpMethod;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public final class ZoneHttpMethodsFilterTest {
+
+    @InjectMocks
+    private ZoneController zoneController;
+
+    private static final Set<HttpMethod> ALL_HTTP_METHODS = new HashSet<>(Arrays.asList(HttpMethod.values()));
+
+    private MockMvc mockMvc;
+
+    @BeforeClass
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        this.mockMvc =
+                MockMvcBuilders.standaloneSetup(this.zoneController).addFilters(new ZoneHttpMethodsFilter()).build();
+    }
+
+    @Test(dataProvider = "urisAndTheirAllowedHttpMethods")
+    public void testUriPatternsAndTheirAllowedHttpMethods(final String uri, final Set<HttpMethod> allowedHttpMethods)
+            throws Exception {
+        Set<HttpMethod> disallowedHttpMethods = new HashSet<>(ALL_HTTP_METHODS);
+        disallowedHttpMethods.removeAll(allowedHttpMethods);
+        for (HttpMethod disallowedHttpMethod : disallowedHttpMethods) {
+            this.mockMvc.perform(MockMvcRequestBuilders.request(disallowedHttpMethod, URI.create(uri)))
+                    .andExpect(MockMvcResultMatchers.status().isMethodNotAllowed());
+        }
+    }
+
+    @DataProvider
+    public Object[][] urisAndTheirAllowedHttpMethods() {
+        return new Object[][] { new Object[] { "/v1/zone/foo", new HashSet<>(Arrays.asList(HttpMethod.PUT,
+                HttpMethod.GET, HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.OPTIONS)) } };
+    }
+}
\ No newline at end of file
diff --git a/service/src/test/java/com/ge/predix/acs/zone/management/ZoneRepositoryTest.java b/service/src/test/java/com/ge/predix/acs/zone/management/ZoneRepositoryTest.java
new file mode 100644
index 0000000..c06125d
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/zone/management/ZoneRepositoryTest.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.zone.management;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.config.InMemoryDataSourceConfig;
+import com.ge.predix.acs.encryption.Encryptor;
+import com.ge.predix.acs.rest.AttributeAdapterConnection;
+import com.ge.predix.acs.rest.AttributeConnector;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.ge.predix.acs.zone.management.dao.ZoneRepository;
+
+@EnableAutoConfiguration
+@ContextConfiguration(classes = { InMemoryDataSourceConfig.class, Encryptor.class })
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+@TestPropertySource("/application.properties")
+public class ZoneRepositoryTest extends AbstractTransactionalTestNGSpringContextTests {
+
+    @Autowired
+    private ZoneRepository zoneRepository;
+
+    @Test
+    public void testAddConnector() throws Exception {
+        createZoneWithConnectorAndAssert();
+    }
+
+    @Test
+    public void testUpdateConnector() throws Exception {
+        ZoneEntity zone = createZoneWithConnectorAndAssert();
+
+        AttributeConnector expectedConnector = new AttributeConnector();
+        expectedConnector.setIsActive(true);
+        expectedConnector.setAdapters(Collections.singleton(new AttributeAdapterConnection("http://some-adapter.com",
+                "http://some-uaa.com", "some-client", "some-secret")));
+        zone.setResourceAttributeConnector(expectedConnector);
+
+        this.zoneRepository.save(zone);
+
+        // Assert that zone connectors and adapters are updated
+        AttributeConnector actualConnector = this.zoneRepository.getByName(zone.getName())
+                .getResourceAttributeConnector();
+        Assert.assertEquals(actualConnector, expectedConnector);
+        Assert.assertEquals(actualConnector.getAdapters(), expectedConnector.getAdapters());
+    }
+
+    @Test
+    public void testDeleteConnector() throws Exception {
+        ZoneEntity zone = createZoneWithConnectorAndAssert();
+
+        zone.setResourceAttributeConnector(null);
+        this.zoneRepository.save(zone);
+        Assert.assertNull(this.zoneRepository.getByName(zone.getName()).getResourceAttributeConnector());
+    }
+
+    private ZoneEntity createZoneWithConnectorAndAssert() throws Exception {
+        AttributeConnector expectedConnector = new AttributeConnector();
+        Set<AttributeAdapterConnection> expectedAdapters = Collections.singleton(
+                new AttributeAdapterConnection("http://my-adapter.com", "http://my-uaa", "my-client", "my-secret"));
+        expectedConnector.setAdapters(expectedAdapters);
+        expectedConnector.setMaxCachedIntervalMinutes(24);
+        ZoneEntity zone = new ZoneEntity();
+        zone.setName("azone");
+        zone.setSubdomain("asubdomain");
+        zone.setDescription("adescription");
+        zone.setResourceAttributeConnector(expectedConnector);
+        this.zoneRepository.save(zone);
+        ZoneEntity acutalZone = this.zoneRepository.getByName("azone");
+        Assert.assertEquals(acutalZone.getSubjectAttributeConnector(), null);
+        Assert.assertEquals(acutalZone.getResourceAttributeConnector(), expectedConnector);
+        Assert.assertEquals(acutalZone.getResourceAttributeConnector().getAdapters(), expectedAdapters);
+        return zone;
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/acs/zone/management/ZoneServiceTest.java b/service/src/test/java/com/ge/predix/acs/zone/management/ZoneServiceTest.java
new file mode 100644
index 0000000..f8f5b35
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/acs/zone/management/ZoneServiceTest.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.acs.zone.management;
+
+import static org.mockito.Matchers.anyList;
+
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.provider.OAuth2Request;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.SpringSecurityPolicyContextResolver;
+import com.ge.predix.acs.config.GraphConfig;
+import com.ge.predix.acs.config.InMemoryDataSourceConfig;
+import com.ge.predix.acs.privilege.management.dao.ResourceRepository;
+import com.ge.predix.acs.privilege.management.dao.SubjectRepository;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.testutils.TestUtils;
+import com.ge.predix.acs.zone.management.dao.ZoneEntity;
+import com.ge.predix.acs.zone.management.dao.ZoneRepository;
+import com.ge.predix.acs.zone.resolver.SpringSecurityZoneResolver;
+import com.ge.predix.uaa.token.lib.ZoneOAuth2Authentication;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+
+@ContextConfiguration(
+        classes = { GraphConfig.class, InMemoryDataSourceConfig.class, SpringSecurityPolicyContextResolver.class,
+                SpringSecurityZoneResolver.class, ZoneServiceImpl.class })
+@TestPropertySource("/application.properties")
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+@Test
+@SuppressWarnings("nls")
+public class ZoneServiceTest extends AbstractTransactionalTestNGSpringContextTests {
+
+    @Autowired
+    private ZoneService zoneService;
+
+    private final TestUtils testUtils = new TestUtils();
+
+    @BeforeMethod
+    public void createSampleData() {
+        this.zoneService.upsertZone(this.testUtils.createZone("zone1", "subdomain1"));
+        ZoneOAuth2Authentication acsAuth = new ZoneOAuth2Authentication(Mockito.mock(OAuth2Request.class), null,
+                "subdomain1");
+        SecurityContextHolder.getContext().setAuthentication(acsAuth);
+    }
+
+    @Test(dataProvider = "badSubDomainDataProvider")
+    public void testZoneCreationWithIllegalZoneNames(final String zoneSubdomain) {
+        try {
+            this.zoneService.upsertZone(new Zone("illegal_zone", zoneSubdomain, "desc"));
+            Assert.fail("Expected an exception for invalid zone name");
+        } catch (ZoneManagementException e) {
+            Assert.assertTrue(e.getMessage().contains("Invalid Zone Subdomain"));
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Subject Deletion Failed!")
+    public void deleteZoneFailsWhenDeleteSubjectFails() {
+        ZoneRepository zoneRepository = Mockito.mock(ZoneRepository.class);
+        Mockito.when(zoneRepository.getByName("test-zone")).thenReturn(new ZoneEntity(1L, "test-zone"));
+        ResourceRepository resourceRepository = Mockito.mock(ResourceRepository.class);
+        Mockito.doNothing().when(resourceRepository).delete(anyList());
+        SubjectRepository subjectRepository = Mockito.mock(SubjectRepository.class);
+        Mockito.doThrow(new RuntimeException("Subject Deletion Failed!")).when(subjectRepository).delete(anyList());
+        this.testUtils.setField(this.zoneService, "subjectRepository", subjectRepository);
+        this.testUtils.setField(this.zoneService, "resourceRepository", resourceRepository);
+        this.testUtils.setField(this.zoneService, "zoneRepository", zoneRepository);
+        this.zoneService.deleteZone("test-zone");
+    }
+
+    @DataProvider(name = "badSubDomainDataProvider")
+    private Object[][] badSubDomainDataProvider() {
+        return new String[][] { { "-baddomain" }, { "baddomain-" }, { "bad.domain" }, { ".baddomain" },
+                { "baddomain." }, { "bad$#%#$" }, { "_baddomain" } };
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/controller/test/AcsMonitoringControllerIT.java b/service/src/test/java/com/ge/predix/controller/test/AcsMonitoringControllerIT.java
new file mode 100644
index 0000000..a0257d4
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/controller/test/AcsMonitoringControllerIT.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.controller.test;
+
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.HEARTBEAT_URL;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+
+/*
+ *
+ * @author acs-engineers@ge.com
+ */
+@WebAppConfiguration
+@ContextConfiguration("classpath:controller-tests-context.xml")
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+@Test
+public class AcsMonitoringControllerIT extends AbstractTestNGSpringContextTests {
+    @Autowired
+    private WebApplicationContext wac;
+
+    private MockMvc mockMvc;
+
+    @BeforeClass
+    public void setup() {
+        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
+    }
+
+    public void testMonitoringHeartBeat() throws Exception {
+        this.mockMvc.perform(get(HEARTBEAT_URL).accept(MediaType.TEXT_PLAIN_VALUE)).andExpect(status().isOk())
+                .andExpect(content().contentType(MediaType.TEXT_PLAIN_VALUE + ";charset=UTF-8"))
+                .andExpect(content().string("alive"));
+    }
+
+}
diff --git a/service/src/test/java/com/ge/predix/controller/test/AttributeConnectorControllerIT.java b/service/src/test/java/com/ge/predix/controller/test/AttributeConnectorControllerIT.java
new file mode 100644
index 0000000..5e949ce
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/controller/test/AttributeConnectorControllerIT.java
@@ -0,0 +1,209 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.controller.test;
+
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.RESOURCE_CONNECTOR_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.SUBJECT_CONNECTOR_URL;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.V1;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.ZONE_URL;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.ge.predix.acs.privilege.management.PrivilegeManagementUtility;
+import com.ge.predix.acs.request.context.AcsRequestContext;
+import com.ge.predix.acs.request.context.AcsRequestContextHolder;
+import com.ge.predix.acs.rest.AttributeConnector;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.testutils.MockAcsRequestContext;
+import com.ge.predix.acs.testutils.MockMvcContext;
+import com.ge.predix.acs.testutils.MockSecurityContext;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.testutils.TestUtils;
+import com.ge.predix.acs.utils.JsonUtils;
+
+@WebAppConfiguration
+@ContextConfiguration("classpath:controller-tests-context.xml")
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+public class AttributeConnectorControllerIT extends AbstractTestNGSpringContextTests {
+    private static final String V1_ZONE_URL = V1 + ZONE_URL;
+    private static final String V1_RESOURCE_CONNECTOR_URL = V1 + RESOURCE_CONNECTOR_URL;
+    private static final String V1_SUBJECT_CONNECTOR_URL = V1 + SUBJECT_CONNECTOR_URL;
+
+    @Autowired
+    private WebApplicationContext wac;
+
+    private final JsonUtils jsonUtils = new JsonUtils();
+    private final ObjectWriter objectWriter = new ObjectMapper().writer().withDefaultPrettyPrinter();
+
+    private MockMvc mockMvc;
+
+    static final TestUtils TEST_UTILS = new TestUtils();
+
+    @BeforeClass
+    public void setup() {
+        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
+    }
+
+    @Test(dataProvider = "requestUrlProvider")
+    public void testAttributeInvalidMediaTypeResponseStatusCheck(final String endpointUrl) throws Exception {
+        this.mockMvc.perform(
+                put(endpointUrl).contentType(MediaType.APPLICATION_PDF_VALUE).content("testString"))
+                .andExpect(status().isUnsupportedMediaType());
+    }
+
+    @Test(dataProvider = "requestUrlProvider")
+    public void testCreateAndGetAndDeleteConnector(final String endpointUrl) throws Exception {
+        createZone1AndAssert();
+
+        AttributeConnector resourceConfig = this.jsonUtils
+                .deserializeFromFile("controller-test/createAttributeConnector.json", AttributeConnector.class);
+        Assert.assertNotNull(resourceConfig, "createAttributeConnector.json file not found or invalid");
+        String resourceConfigContent = this.objectWriter.writeValueAsString(resourceConfig);
+        this.mockMvc.perform(
+                put(endpointUrl).contentType(MediaType.APPLICATION_JSON).content(resourceConfigContent))
+                .andExpect(status().isCreated());
+
+        this.mockMvc.perform(get(endpointUrl))
+                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
+                .andExpect(jsonPath("isActive", equalTo(true)))
+                .andExpect(jsonPath("maxCachedIntervalMinutes", equalTo(60)))
+                .andExpect(jsonPath("adapters[0].adapterEndpoint", equalTo("https://my-adapter.com")))
+                .andExpect(jsonPath("adapters[0].uaaTokenUrl", equalTo("https://my-uaa.com")))
+                .andExpect(jsonPath("adapters[0].uaaClientId", equalTo("adapter-client")))
+                .andExpect(jsonPath("adapters[0].uaaClientSecret", equalTo("**********")))
+                .andExpect(status().isOk());
+
+        this.mockMvc.perform(delete(endpointUrl)).andExpect(status().isNoContent());
+    }
+
+    @Test(dataProvider = "requestUrlProvider")
+    public void testGetResourceConnectorWhichDoesNotExists(final String endpointUrl) throws Exception {
+        createZone1AndAssert();
+        this.mockMvc.perform(get(endpointUrl)).andExpect(status().isNotFound());
+    }
+
+    @Test(dataProvider = "requestUrlProvider")
+    public void testDeleteResourceConnectorWhichDoesNotExist(final String endpointUrl) throws Exception {
+        createZone1AndAssert();
+        this.mockMvc.perform(delete(endpointUrl)).andExpect(status().isNotFound());
+    }
+
+    @Test(dataProvider = "requestUrlProvider")
+    public void testCreateResourceConnectorWithEmptyAdapters(final String endpointUrl) throws Exception {
+        createZone1AndAssert();
+        AttributeConnector connector = this.jsonUtils.deserializeFromFile(
+                "controller-test/createAttributeConnectorWithEmptyAdapters.json", AttributeConnector.class);
+        Assert.assertNotNull(connector, "createAttributeConnectorWithEmptyAdapters.json file not found or invalid");
+        String connectorContent = this.objectWriter.writeValueAsString(connector);
+
+        this.mockMvc.perform(
+                put(endpointUrl).contentType(MediaType.APPLICATION_JSON).content(connectorContent))
+                .andExpect(status().isUnprocessableEntity());
+    }
+
+    @Test(dataProvider = "requestUrlProvider")
+    public void testCreateResourceConnectorWithTwoAdapters(final String endpointUrl) throws Exception {
+        createZone1AndAssert();
+        AttributeConnector connector = this.jsonUtils.deserializeFromFile(
+                "controller-test/createAttributeConnectorWithTwoAdapters.json", AttributeConnector.class);
+        Assert.assertNotNull(connector, "createAttributeConnectorWithTwoAdapters.json file not found or invalid");
+        String connectorContent = this.objectWriter.writeValueAsString(connector);
+
+        this.mockMvc.perform(
+                put(endpointUrl).contentType(MediaType.APPLICATION_JSON).content(connectorContent))
+                .andExpect(status().isUnprocessableEntity());
+    }
+
+    @Test(dataProvider = "requestUrlProvider")
+    public void testCreateResourceConnectorWithCachedIntervalBelowThreshold(final String endpointUrl) throws Exception {
+        createZone1AndAssert();
+        AttributeConnector connector = this.jsonUtils.deserializeFromFile(
+                "controller-test/createAttributeConnectorWithLowValueForCache.json", AttributeConnector.class);
+        Assert.assertNotNull(connector, "createAttributeConnectorWithLowValueForCache.json file not found or invalid");
+        String connectorContent = this.objectWriter.writeValueAsString(connector);
+
+        this.mockMvc.perform(
+                put(endpointUrl).contentType(MediaType.APPLICATION_JSON).content(connectorContent))
+                .andExpect(status().isUnprocessableEntity());
+    }
+
+    @Test
+    public void testZoneDoesNotExist() throws Exception {
+        Zone testZone3 = new Zone("name", "subdomain", "description");
+        MockSecurityContext.mockSecurityContext(testZone3);
+
+        Map<AcsRequestContext.ACSRequestContextAttribute, Object> newMap = new HashMap<>();
+        newMap.put(AcsRequestContext.ACSRequestContextAttribute.ZONE_ENTITY, null);
+
+        ReflectionTestUtils.setField(AcsRequestContextHolder.getAcsRequestContext(),
+                "unModifiableRequestContextMap", newMap);
+
+        MockMvcContext getContext =
+                TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, testZone3.getSubdomain(),
+                        "/v1/connector/resource");
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isBadRequest())
+                .andExpect(jsonPath(PrivilegeManagementUtility.INCORRECT_PARAMETER_TYPE_ERROR, is("Bad Request")))
+                .andExpect(jsonPath(PrivilegeManagementUtility.INCORRECT_PARAMETER_TYPE_MESSAGE,
+                        is("Zone not found")));
+    }
+
+    private void createZone1AndAssert() throws JsonProcessingException, Exception {
+        Zone zone = this.jsonUtils.deserializeFromFile("controller-test/createZone.json", Zone.class);
+        Assert.assertNotNull(zone, "createZone.json file not found or invalid");
+        String zoneContent = this.objectWriter.writeValueAsString(zone);
+        this.mockMvc
+                .perform(put(V1_ZONE_URL, zone.getName()).contentType(MediaType.APPLICATION_JSON).content(zoneContent))
+                .andExpect(status().isCreated());
+
+        MockSecurityContext.mockSecurityContext(zone);
+        MockAcsRequestContext.mockAcsRequestContext();
+    }
+
+    @DataProvider
+    private Object[][] requestUrlProvider() {
+        return new String[][] { { V1_RESOURCE_CONNECTOR_URL }, { V1_SUBJECT_CONNECTOR_URL } };
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/controller/test/ConfigureEnvironment.java b/service/src/test/java/com/ge/predix/controller/test/ConfigureEnvironment.java
new file mode 100644
index 0000000..0106041
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/controller/test/ConfigureEnvironment.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.controller.test;
+
+import org.apache.commons.lang3.StringUtils;
+
+public class ConfigureEnvironment {
+    static {
+        setPropertyIfNotExist("UAA_LOCAL_PORT", "8080");
+        setPropertyIfNotExist("ACS_DEFAULT_ISSUER_ID",
+                              "http://acs.localhost:" + System.getenv("UAA_LOCAL_PORT") + "/uaa");
+        setPropertyIfNotExist("ACS_SERVICE_ID", "predix-acs");
+        setPropertyIfNotExist("ACS_UAA_URL", "http://localhost:" + System.getenv("UAA_LOCAL_PORT") + "/uaa");
+    }
+
+    private static void setPropertyIfNotExist(final String name, final String value) {
+        if (StringUtils.isEmpty(System.getProperty(name))) {
+            System.setProperty(name, value);
+        }
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/controller/test/HierarchicalResourcesIT.java b/service/src/test/java/com/ge/predix/controller/test/HierarchicalResourcesIT.java
new file mode 100644
index 0000000..a2935ca
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/controller/test/HierarchicalResourcesIT.java
@@ -0,0 +1,169 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.controller.test;
+
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.testutils.MockAcsRequestContext;
+import com.ge.predix.acs.testutils.MockMvcContext;
+import com.ge.predix.acs.testutils.MockSecurityContext;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.zone.management.ZoneService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.web.context.WebApplicationContext;
+import org.testng.SkipException;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.net.URLEncoder;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.ge.predix.acs.testutils.XFiles.ASCENSION_ID;
+import static com.ge.predix.acs.testutils.XFiles.BASEMENT_SITE_ID;
+import static com.ge.predix.acs.testutils.XFiles.EVIDENCE_SCULLYS_TESTIMONY_ID;
+import static com.ge.predix.acs.testutils.XFiles.SITE_BASEMENT;
+import static com.ge.predix.acs.testutils.XFiles.SPECIAL_AGENTS_GROUP_ATTRIBUTE;
+import static com.ge.predix.acs.testutils.XFiles.TOP_SECRET_CLASSIFICATION;
+import static com.ge.predix.acs.testutils.XFiles.TYPE_MYTHARC;
+import static com.ge.predix.acs.testutils.XFiles.createThreeLevelResourceHierarchy;
+import static org.hamcrest.Matchers.is;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebAppConfiguration
+@ContextConfiguration("classpath:controller-tests-context.xml")
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+public class HierarchicalResourcesIT extends AbstractTestNGSpringContextTests {
+    private static final Zone TEST_ZONE =
+        ResourcePrivilegeManagementControllerIT.TEST_UTILS.createTestZone("ResourceMgmtControllerIT");
+
+    @Autowired
+    private ZoneService zoneService;
+
+    @Autowired
+    private WebApplicationContext wac;
+
+    @Autowired
+    private ConfigurableEnvironment configurableEnvironment;
+
+    @BeforeClass
+    public void beforeClass() throws Exception {
+        if (!Arrays.asList(this.configurableEnvironment.getActiveProfiles()).contains("titan")) {
+            throw new SkipException("This test only applies when using the \"titan\" profile");
+        }
+
+        this.zoneService.upsertZone(TEST_ZONE);
+        MockSecurityContext.mockSecurityContext(TEST_ZONE);
+        MockAcsRequestContext.mockAcsRequestContext();
+    }
+
+    @Test
+    public void testPostAndGetHierarchicalResources() throws Exception {
+        List<BaseResource> resources = createThreeLevelResourceHierarchy();
+
+        // Append a list of resources
+        MockMvcContext postContext =
+            ResourcePrivilegeManagementControllerIT.TEST_UTILS.createWACWithCustomPOSTRequestBuilder(
+                this.wac,
+                TEST_ZONE.getSubdomain(),
+                ResourcePrivilegeManagementControllerIT.RESOURCE_BASE_URL);
+
+        postContext.getMockMvc().perform(postContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                                                    .content(ResourcePrivilegeManagementControllerIT.OBJECT_MAPPER
+                                                                 .writeValueAsString(resources)))
+                   .andExpect(status().isNoContent());
+
+        // Get the child resource without query string specifier.
+        MockMvcContext getContext0 =
+            ResourcePrivilegeManagementControllerIT.TEST_UTILS.createWACWithCustomGETRequestBuilder(
+                this.wac,
+                TEST_ZONE.getSubdomain(),
+                ResourcePrivilegeManagementControllerIT.RESOURCE_BASE_URL + "/"
+                + URLEncoder.encode(EVIDENCE_SCULLYS_TESTIMONY_ID, "UTF-8"));
+
+        getContext0.getMockMvc().perform(getContext0.getBuilder()).andExpect(status().isOk())
+                   .andExpect(jsonPath("resourceIdentifier", is(EVIDENCE_SCULLYS_TESTIMONY_ID)))
+                   .andExpect(jsonPath("attributes[0].name", is(TOP_SECRET_CLASSIFICATION.getName())))
+                   .andExpect(jsonPath("attributes[0].value", is(TOP_SECRET_CLASSIFICATION.getValue())))
+                   .andExpect(jsonPath("attributes[0].issuer", is(TOP_SECRET_CLASSIFICATION.getIssuer())))
+                   .andExpect(jsonPath("attributes[1]").doesNotExist());
+
+        // Get the child resource without inherited attributes.
+        MockMvcContext getContext1 =
+            ResourcePrivilegeManagementControllerIT.TEST_UTILS.createWACWithCustomGETRequestBuilder(
+                this.wac,
+                TEST_ZONE.getSubdomain(),
+                ResourcePrivilegeManagementControllerIT.RESOURCE_BASE_URL + "/"
+                + URLEncoder.encode(EVIDENCE_SCULLYS_TESTIMONY_ID, "UTF-8") + "?includeInheritedAttributes=false");
+
+        getContext1.getMockMvc().perform(getContext1.getBuilder()).andExpect(status().isOk())
+                   .andExpect(jsonPath("resourceIdentifier", is(EVIDENCE_SCULLYS_TESTIMONY_ID)))
+                   .andExpect(jsonPath("attributes[0].name", is(TOP_SECRET_CLASSIFICATION.getName())))
+                   .andExpect(jsonPath("attributes[0].value", is(TOP_SECRET_CLASSIFICATION.getValue())))
+                   .andExpect(jsonPath("attributes[0].issuer", is(TOP_SECRET_CLASSIFICATION.getIssuer())))
+                   .andExpect(jsonPath("attributes[1]").doesNotExist());
+
+        // Get the child resource with inherited attributes.
+        MockMvcContext getContext2 =
+            ResourcePrivilegeManagementControllerIT.TEST_UTILS.createWACWithCustomGETRequestBuilder(
+                this.wac,
+                TEST_ZONE.getSubdomain(),
+                ResourcePrivilegeManagementControllerIT.RESOURCE_BASE_URL + "/"
+                + URLEncoder.encode(EVIDENCE_SCULLYS_TESTIMONY_ID, "UTF-8") + "?includeInheritedAttributes=true");
+
+        getContext2.getMockMvc().perform(getContext2.getBuilder()).andExpect(status().isOk())
+                       .andExpect(jsonPath("resourceIdentifier", is(EVIDENCE_SCULLYS_TESTIMONY_ID)))
+                       .andExpect(jsonPath("attributes[0].name", is(TOP_SECRET_CLASSIFICATION.getName())))
+                       .andExpect(jsonPath("attributes[0].value", is(TOP_SECRET_CLASSIFICATION.getValue())))
+                       .andExpect(jsonPath("attributes[0].issuer", is(TOP_SECRET_CLASSIFICATION.getIssuer())))
+                       .andExpect(jsonPath("attributes[1].name", is(SPECIAL_AGENTS_GROUP_ATTRIBUTE.getName())))
+                       .andExpect(jsonPath("attributes[1].value", is(SPECIAL_AGENTS_GROUP_ATTRIBUTE.getValue())))
+                       .andExpect(jsonPath("attributes[1].issuer", is(SPECIAL_AGENTS_GROUP_ATTRIBUTE.getIssuer())))
+                       .andExpect(jsonPath("attributes[2].name", is(TYPE_MYTHARC.getName())))
+                       .andExpect(jsonPath("attributes[2].value", is(TYPE_MYTHARC.getValue())))
+                       .andExpect(jsonPath("attributes[2].issuer", is(TYPE_MYTHARC.getIssuer())))
+                       .andExpect(jsonPath("attributes[3].name", is(SITE_BASEMENT.getName())))
+                       .andExpect(jsonPath("attributes[3].value", is(SITE_BASEMENT.getValue())))
+                       .andExpect(jsonPath("attributes[3].issuer", is(SITE_BASEMENT.getIssuer())));
+
+        // Clean up after ourselves.
+        deleteResource(EVIDENCE_SCULLYS_TESTIMONY_ID);
+        deleteResource(ASCENSION_ID);
+        deleteResource(BASEMENT_SITE_ID);
+    }
+
+    private void deleteResource(final String resourceIdentifier) throws Exception {
+        String resourceToDeleteURI = ResourcePrivilegeManagementControllerIT.RESOURCE_BASE_URL + "/"
+                                     + URLEncoder.encode(resourceIdentifier, "UTF-8");
+
+        MockMvcContext deleteContext =
+            ResourcePrivilegeManagementControllerIT.TEST_UTILS.createWACWithCustomDELETERequestBuilder(
+                this.wac,
+                TEST_ZONE.getSubdomain(), resourceToDeleteURI);
+
+        deleteContext.getMockMvc().perform(deleteContext.getBuilder()).andExpect(status().isNoContent());
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/controller/test/HierarchicalSubjectsIT.java b/service/src/test/java/com/ge/predix/controller/test/HierarchicalSubjectsIT.java
new file mode 100644
index 0000000..fb12e76
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/controller/test/HierarchicalSubjectsIT.java
@@ -0,0 +1,171 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.controller.test;
+
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.testutils.MockAcsRequestContext;
+import com.ge.predix.acs.testutils.MockMvcContext;
+import com.ge.predix.acs.testutils.MockSecurityContext;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.zone.management.ZoneService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.web.context.WebApplicationContext;
+import org.testng.SkipException;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.net.URLEncoder;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.ge.predix.acs.testutils.XFiles.AGENT_MULDER;
+import static com.ge.predix.acs.testutils.XFiles.FBI;
+import static com.ge.predix.acs.testutils.XFiles.SITE_BASEMENT;
+import static com.ge.predix.acs.testutils.XFiles.SITE_QUANTICO;
+import static com.ge.predix.acs.testutils.XFiles.SPECIAL_AGENTS_GROUP;
+import static com.ge.predix.acs.testutils.XFiles.SPECIAL_AGENTS_GROUP_ATTRIBUTE;
+import static com.ge.predix.acs.testutils.XFiles.TOP_SECRET_CLASSIFICATION;
+import static com.ge.predix.acs.testutils.XFiles.TOP_SECRET_GROUP;
+import static com.ge.predix.acs.testutils.XFiles.createSubjectHierarchy;
+import static org.hamcrest.Matchers.is;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebAppConfiguration
+@ContextConfiguration("classpath:controller-tests-context.xml")
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+public class HierarchicalSubjectsIT extends AbstractTestNGSpringContextTests {
+    private static final Zone TEST_ZONE =
+        SubjectPrivilegeManagementControllerIT.TEST_UTILS.createTestZone("SubjectMgmtControllerIT");
+
+    @Autowired
+    private ZoneService zoneService;
+
+    @Autowired
+    private WebApplicationContext wac;
+
+    @Autowired
+    private ConfigurableEnvironment configurableEnvironment;
+
+    @BeforeClass
+    public void beforeClass() throws Exception {
+        if (!Arrays.asList(this.configurableEnvironment.getActiveProfiles()).contains("titan")) {
+            throw new SkipException("This test only applies when using the \"titan\" profile");
+        }
+
+        this.zoneService.upsertZone(TEST_ZONE);
+        MockSecurityContext.mockSecurityContext(TEST_ZONE);
+        MockAcsRequestContext.mockAcsRequestContext();
+    }
+
+    @Test
+    public void testPostAndGetHierarchicalSubjects() throws Exception {
+        List<BaseSubject> subjects = createSubjectHierarchy();
+
+        // Append a list of resources
+        MockMvcContext postContext =
+            SubjectPrivilegeManagementControllerIT.TEST_UTILS.createWACWithCustomPOSTRequestBuilder(
+                this.wac,
+                TEST_ZONE.getSubdomain(),
+                SubjectPrivilegeManagementControllerIT.SUBJECT_BASE_URL);
+
+        postContext.getMockMvc().perform(postContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                                                    .content(SubjectPrivilegeManagementControllerIT.OBJECT_MAPPER
+                                                                 .writeValueAsString(subjects)))
+                   .andExpect(status().isNoContent());
+
+        // Get the child subject without query string specifier.
+        MockMvcContext getContext0 =
+            SubjectPrivilegeManagementControllerIT.TEST_UTILS.createWACWithCustomGETRequestBuilder(
+                this.wac,
+                TEST_ZONE.getSubdomain(),
+                SubjectPrivilegeManagementControllerIT.SUBJECT_BASE_URL + "/"
+                + URLEncoder.encode(AGENT_MULDER, "UTF-8"));
+
+        getContext0.getMockMvc().perform(getContext0.getBuilder()).andExpect(status().isOk())
+                   .andExpect(jsonPath("subjectIdentifier", is(AGENT_MULDER)))
+                   .andExpect(jsonPath("attributes[0].name", is(SITE_BASEMENT.getName())))
+                   .andExpect(jsonPath("attributes[0].value", is(SITE_BASEMENT.getValue())))
+                   .andExpect(jsonPath("attributes[0].issuer", is(SITE_BASEMENT.getIssuer())))
+                   .andExpect(jsonPath("attributes[1]").doesNotExist());
+
+        // Get the child subject without inherited attributes.
+        MockMvcContext getContext1 =
+            SubjectPrivilegeManagementControllerIT.TEST_UTILS.createWACWithCustomGETRequestBuilder(
+                this.wac,
+                TEST_ZONE.getSubdomain(),
+                SubjectPrivilegeManagementControllerIT.SUBJECT_BASE_URL + "/"
+                + URLEncoder.encode(AGENT_MULDER, "UTF-8") + "?includeInheritedAttributes=false");
+
+        getContext1.getMockMvc().perform(getContext1.getBuilder()).andExpect(status().isOk())
+                   .andExpect(jsonPath("subjectIdentifier", is(AGENT_MULDER)))
+                   .andExpect(jsonPath("attributes[0].name", is(SITE_BASEMENT.getName())))
+                   .andExpect(jsonPath("attributes[0].value", is(SITE_BASEMENT.getValue())))
+                   .andExpect(jsonPath("attributes[0].issuer", is(SITE_BASEMENT.getIssuer())))
+                   .andExpect(jsonPath("attributes[1]").doesNotExist());
+
+        // Get the child subject with inherited attributes.
+        MockMvcContext getContext2 =
+            SubjectPrivilegeManagementControllerIT.TEST_UTILS.createWACWithCustomGETRequestBuilder(
+                this.wac,
+                TEST_ZONE.getSubdomain(),
+                SubjectPrivilegeManagementControllerIT.SUBJECT_BASE_URL + "/"
+                + URLEncoder.encode(AGENT_MULDER, "UTF-8") + "?includeInheritedAttributes=true");
+
+        getContext2.getMockMvc().perform(getContext2.getBuilder()).andExpect(status().isOk())
+                   .andExpect(jsonPath("subjectIdentifier", is(AGENT_MULDER)))
+                   .andExpect(jsonPath("attributes[0].name", is(TOP_SECRET_CLASSIFICATION.getName())))
+                   .andExpect(jsonPath("attributes[0].value", is(TOP_SECRET_CLASSIFICATION.getValue())))
+                   .andExpect(jsonPath("attributes[0].issuer", is(TOP_SECRET_CLASSIFICATION.getIssuer())))
+                   .andExpect(jsonPath("attributes[1].name", is(SITE_QUANTICO.getName())))
+                   .andExpect(jsonPath("attributes[1].value", is(SITE_QUANTICO.getValue())))
+                   .andExpect(jsonPath("attributes[1].issuer", is(SITE_QUANTICO.getIssuer())))
+                   .andExpect(jsonPath("attributes[2].name", is(SPECIAL_AGENTS_GROUP_ATTRIBUTE.getName())))
+                   .andExpect(jsonPath("attributes[2].value", is(SPECIAL_AGENTS_GROUP_ATTRIBUTE.getValue())))
+                   .andExpect(jsonPath("attributes[2].issuer", is(SPECIAL_AGENTS_GROUP_ATTRIBUTE.getIssuer())))
+                   .andExpect(jsonPath("attributes[3].name", is(SITE_BASEMENT.getName())))
+                   .andExpect(jsonPath("attributes[3].value", is(SITE_BASEMENT.getValue())))
+                   .andExpect(jsonPath("attributes[3].issuer", is(SITE_BASEMENT.getIssuer())));
+
+        // Clean up after ourselves.
+        deleteSubject(AGENT_MULDER);
+        deleteSubject(TOP_SECRET_GROUP);
+        deleteSubject(SPECIAL_AGENTS_GROUP);
+        deleteSubject(FBI);
+    }
+
+    private void deleteSubject(final String subjectIdentifier) throws Exception {
+        String subjectToDeleteURI = SubjectPrivilegeManagementControllerIT.SUBJECT_BASE_URL + "/"
+                                    + URLEncoder.encode(subjectIdentifier, "UTF-8");
+
+        MockMvcContext deleteContext =
+            SubjectPrivilegeManagementControllerIT.TEST_UTILS.createWACWithCustomDELETERequestBuilder(
+                this.wac,
+                TEST_ZONE.getSubdomain(), subjectToDeleteURI);
+
+        deleteContext.getMockMvc().perform(deleteContext.getBuilder()).andExpect(status().isNoContent());
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/controller/test/NonHierarchicalResourcesIT.java b/service/src/test/java/com/ge/predix/controller/test/NonHierarchicalResourcesIT.java
new file mode 100644
index 0000000..afe5a21
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/controller/test/NonHierarchicalResourcesIT.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.controller.test;
+
+import com.ge.predix.acs.commons.web.BaseRestApi;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.testutils.MockAcsRequestContext;
+import com.ge.predix.acs.testutils.MockMvcContext;
+import com.ge.predix.acs.testutils.MockSecurityContext;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.zone.management.ZoneService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.web.context.WebApplicationContext;
+import org.testng.Assert;
+import org.testng.SkipException;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebAppConfiguration
+@ContextConfiguration("classpath:controller-tests-context.xml")
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+public final class NonHierarchicalResourcesIT extends AbstractTestNGSpringContextTests {
+    private static final Zone TEST_ZONE =
+        ResourcePrivilegeManagementControllerIT.TEST_UTILS.createTestZone("ResourceMgmtControllerIT");
+
+    @Autowired
+    private ZoneService zoneService;
+
+    @Autowired
+    private WebApplicationContext wac;
+
+    @Autowired
+    private ConfigurableEnvironment configurableEnvironment;
+
+    @BeforeClass
+    public void beforeClass() throws Exception {
+        if (Arrays.asList(this.configurableEnvironment.getActiveProfiles()).contains("titan")) {
+            throw new SkipException("This test only applies when NOT using the \"titan\" profile");
+        }
+
+        this.zoneService.upsertZone(TEST_ZONE);
+        MockSecurityContext.mockSecurityContext(TEST_ZONE);
+        MockAcsRequestContext.mockAcsRequestContext();
+    }
+
+    @Test
+    public void testResourceWithParentsFailWhenNotUsingTitan() throws Exception {
+        BaseResource resource = ResourcePrivilegeManagementControllerIT.JSON_UTILS.deserializeFromFile(
+            "controller-test/a-resource-with-parents.json", BaseResource.class);
+        Assert.assertNotNull(resource);
+
+        MockMvcContext putContext =
+            ResourcePrivilegeManagementControllerIT.TEST_UTILS.createWACWithCustomPUTRequestBuilder(
+                this.wac, TEST_ZONE.getSubdomain(),
+                ResourcePrivilegeManagementControllerIT.RESOURCE_BASE_URL
+                + "/%2Fservices%2Fsecured-api-with-parents");
+        final MvcResult result = putContext.getMockMvc()
+                                           .perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                                                              .content(ResourcePrivilegeManagementControllerIT
+                                                                           .OBJECT_MAPPER.writeValueAsString(resource)))
+                                           .andExpect(status().isNotImplemented())
+                                           .andReturn();
+
+        Assert.assertEquals(result.getResponse().getContentAsString(),
+                            "{\"ErrorDetails\":{\"errorCode\":\"FAILURE\","
+                            + "\"errorMessage\":\"" + BaseRestApi.PARENTS_ATTR_NOT_SUPPORTED_MSG + "\"}}");
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/controller/test/NonHierarchicalSubjectsIT.java b/service/src/test/java/com/ge/predix/controller/test/NonHierarchicalSubjectsIT.java
new file mode 100644
index 0000000..038c4db
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/controller/test/NonHierarchicalSubjectsIT.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.controller.test;
+
+import com.ge.predix.acs.commons.web.BaseRestApi;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.testutils.MockAcsRequestContext;
+import com.ge.predix.acs.testutils.MockMvcContext;
+import com.ge.predix.acs.testutils.MockSecurityContext;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.zone.management.ZoneService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.web.context.WebApplicationContext;
+import org.testng.Assert;
+import org.testng.SkipException;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebAppConfiguration
+@ContextConfiguration("classpath:controller-tests-context.xml")
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+public final class NonHierarchicalSubjectsIT extends AbstractTestNGSpringContextTests {
+    private static final Zone TEST_ZONE =
+        SubjectPrivilegeManagementControllerIT.TEST_UTILS.createTestZone("SubjectMgmtControllerIT");
+
+    @Autowired
+    private ZoneService zoneService;
+
+    @Autowired
+    private WebApplicationContext wac;
+
+    @Autowired
+    private ConfigurableEnvironment configurableEnvironment;
+
+    @BeforeClass
+    public void beforeClass() throws Exception {
+        if (Arrays.asList(this.configurableEnvironment.getActiveProfiles()).contains("titan")) {
+            throw new SkipException("This test only applies when NOT using the \"titan\" profile");
+        }
+
+        this.zoneService.upsertZone(TEST_ZONE);
+        MockSecurityContext.mockSecurityContext(TEST_ZONE);
+        MockAcsRequestContext.mockAcsRequestContext();
+    }
+
+    @Test
+    public void testSubjectWithParentsFailWhenNotUsingTitan() throws Exception {
+        BaseSubject subject = SubjectPrivilegeManagementControllerIT.JSON_UTILS.deserializeFromFile(
+            "controller-test/a-subject-with-parents.json", BaseSubject.class);
+        Assert.assertNotNull(subject);
+
+        MockMvcContext putContext =
+            SubjectPrivilegeManagementControllerIT.TEST_UTILS.createWACWithCustomPUTRequestBuilder(
+                this.wac, TEST_ZONE.getSubdomain(),
+                SubjectPrivilegeManagementControllerIT.SUBJECT_BASE_URL + "/dave-with-parents");
+        final MvcResult result = putContext.getMockMvc()
+                                           .perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                                                              .content(ResourcePrivilegeManagementControllerIT
+                                                                           .OBJECT_MAPPER.writeValueAsString(subject)))
+                                           .andExpect(status().isNotImplemented())
+                                           .andReturn();
+
+        Assert.assertEquals(result.getResponse().getContentAsString(),
+                            "{\"ErrorDetails\":{\"errorCode\":\"FAILURE\","
+                            + "\"errorMessage\":\"" + BaseRestApi.PARENTS_ATTR_NOT_SUPPORTED_MSG + "\"}}");
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/controller/test/PolicyEvalWithGraphDbControllerIT.java b/service/src/test/java/com/ge/predix/controller/test/PolicyEvalWithGraphDbControllerIT.java
new file mode 100644
index 0000000..4e09c00
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/controller/test/PolicyEvalWithGraphDbControllerIT.java
@@ -0,0 +1,368 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.controller.test;
+
+import static com.ge.predix.acs.testutils.XFiles.AGENT_MULDER;
+import static com.ge.predix.acs.testutils.XFiles.EVIDENCE_IMPLANT_ID;
+import static com.ge.predix.acs.testutils.XFiles.EVIDENCE_SCULLYS_TESTIMONY_ID;
+import static com.ge.predix.acs.testutils.XFiles.SPECIAL_AGENTS_GROUP_ATTRIBUTE;
+import static com.ge.predix.acs.testutils.XFiles.TOP_SECRET_CLASSIFICATION;
+import static com.ge.predix.acs.testutils.XFiles.createScopedSubjectHierarchy;
+import static com.ge.predix.acs.testutils.XFiles.createSubjectHierarchy;
+import static com.ge.predix.acs.testutils.XFiles.createTwoLevelResourceHierarchy;
+import static com.ge.predix.acs.testutils.XFiles.createThreeLevelResourceHierarchy;
+import static com.ge.predix.acs.testutils.XFiles.createTwoParentResourceHierarchy;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.net.URLEncoder;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.web.context.WebApplicationContext;
+import org.testng.Assert;
+import org.testng.SkipException;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.ge.predix.acs.model.Attribute;
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.privilege.management.PrivilegeManagementService;
+import com.ge.predix.acs.privilege.management.dao.GraphSubjectRepository;
+import com.ge.predix.acs.privilege.management.dao.GraphResourceRepository;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.testutils.MockAcsRequestContext;
+import com.ge.predix.acs.testutils.MockMvcContext;
+import com.ge.predix.acs.testutils.MockSecurityContext;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.testutils.TestUtils;
+import com.ge.predix.acs.utils.JsonUtils;
+import com.ge.predix.acs.zone.management.ZoneService;
+
+@WebAppConfiguration
+@ContextConfiguration("classpath:controller-tests-context.xml")
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+public class PolicyEvalWithGraphDbControllerIT extends AbstractTestNGSpringContextTests {
+
+    public static final ConfigureEnvironment CONFIGURE_ENVIRONMENT = new ConfigureEnvironment();
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+    private static final ObjectWriter OBJECT_WRITER = new ObjectMapper().writer().withDefaultPrettyPrinter();
+    private static final String POLICY_EVAL_URL = "v1/policy-evaluation";
+    private static final String POLICY_SET_URL = "v1/policy-set";
+    private static final String SUBJECT_URL = "v1/subject";
+    private static final String RESOURCE_URL = "v1/resource";
+    private static final long TEST_TRAVERSAL_LIMIT = 2;
+
+    @Autowired
+    private PrivilegeManagementService privilegeManagementService;
+
+    @Qualifier("resourceHierarchicalRepository")
+    @Autowired
+    private GraphResourceRepository graphResourceRepository;
+
+    @Qualifier("subjectHierarchicalRepository")
+    @Autowired
+    private GraphSubjectRepository graphSubjectRepository;
+
+    @Autowired
+    private WebApplicationContext wac;
+
+    @Autowired
+    private ZoneService zoneService;
+
+    private PolicySet policySet;
+
+    private final JsonUtils jsonUtils = new JsonUtils();
+    private final TestUtils testUtils = new TestUtils();
+    private Zone testZone1;
+    private Zone testZone2;
+
+    @Autowired
+    private ConfigurableEnvironment env;
+
+    @BeforeClass
+    public void setup() throws Exception {
+        if (!Arrays.asList(this.env.getActiveProfiles()).contains("titan")) {
+            throw new SkipException("This test only applies when using titan profile.");
+        }
+
+        this.testZone1 = new TestUtils().createTestZone("PolicyEvalWithGraphDbControllerIT1");
+        this.testZone2 = new TestUtils().createTestZone("PolicyEvalWithGraphDbControllerIT2");
+        this.zoneService.upsertZone(this.testZone1);
+        this.zoneService.upsertZone(this.testZone2);
+        MockSecurityContext.mockSecurityContext(this.testZone1);
+        MockAcsRequestContext.mockAcsRequestContext();
+        this.policySet = this.jsonUtils.deserializeFromFile("complete-sample-policy-set-2.json", PolicySet.class);
+        Assert.assertNotNull(this.policySet, "complete-sample-policy-set-2.json file not found or invalid");
+    }
+
+    @AfterMethod
+    public void testCleanup() {
+        for (BaseSubject subject : this.privilegeManagementService.getSubjects()) {
+            this.privilegeManagementService.deleteSubject(subject.getSubjectIdentifier());
+        }
+        for (BaseResource resource : this.privilegeManagementService.getResources()) {
+            this.privilegeManagementService.deleteResource(resource.getResourceIdentifier());
+        }
+    }
+
+    @Test
+    public void testPolicyInvalidMediaTypeResponseStatusCheck()
+            throws Exception {
+
+        String uri = POLICY_SET_URL + "/" + "testString";
+        MockMvcContext putPolicySetContext = this.testUtils
+                .createWACWithCustomPUTRequestBuilder(this.wac, "testZone", uri);
+        putPolicySetContext.getMockMvc().perform(putPolicySetContext.getBuilder().contentType(MediaType.TEXT_HTML_VALUE)
+                .content("testString")).andExpect(status().isUnsupportedMediaType());
+
+    }
+
+    @Test(dataProvider = "policyEvalDataProvider")
+    public void testPolicyEvaluation(final Zone zone, final PolicySet testPolicySet,
+            final List<BaseResource> resourceHierarchy, final List<BaseSubject> subjectHierarchy,
+            final PolicyEvaluationRequestV1 policyEvalRequest, final Effect expectedEffect) throws Exception {
+        // Create policy set.
+
+        String uri = POLICY_SET_URL + "/" + URLEncoder.encode(testPolicySet.getName(), "UTF-8");
+        MockMvcContext putPolicySetContext = this.testUtils.createWACWithCustomPUTRequestBuilder(this.wac,
+                zone.getSubdomain(), uri);
+        putPolicySetContext.getMockMvc().perform(putPolicySetContext.getBuilder()
+                .contentType(MediaType.APPLICATION_JSON).content(OBJECT_WRITER.writeValueAsString(testPolicySet)))
+                .andExpect(status().isCreated());
+
+        // Create resource hierarchy.
+        if (null != resourceHierarchy) {
+            MockMvcContext postResourcesContext = this.testUtils.createWACWithCustomPOSTRequestBuilder(this.wac,
+                    zone.getSubdomain(), RESOURCE_URL);
+            postResourcesContext.getMockMvc()
+                    .perform(postResourcesContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                            .content(OBJECT_MAPPER.writeValueAsString(resourceHierarchy)))
+                    .andExpect(status().isNoContent());
+        }
+
+        // Create subject hierarchy.
+        if (null != subjectHierarchy) {
+            MockMvcContext postSubjectsContext = this.testUtils.createWACWithCustomPOSTRequestBuilder(this.wac,
+                    zone.getSubdomain(), SUBJECT_URL);
+            postSubjectsContext.getMockMvc()
+                    .perform(postSubjectsContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                            .content(OBJECT_MAPPER.writeValueAsString(subjectHierarchy)))
+                    .andExpect(status().isNoContent());
+        }
+
+        // Request policy evaluation.
+        MockMvcContext postPolicyEvalContext = this.testUtils.createWACWithCustomPOSTRequestBuilder(this.wac,
+                zone.getSubdomain(), POLICY_EVAL_URL);
+        MvcResult mvcResult = postPolicyEvalContext.getMockMvc()
+                .perform(postPolicyEvalContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                        .content(OBJECT_MAPPER.writeValueAsString(policyEvalRequest)))
+                .andExpect(status().isOk()).andReturn();
+        PolicyEvaluationResult policyEvalResult = OBJECT_MAPPER
+                .readValue(mvcResult.getResponse().getContentAsByteArray(), PolicyEvaluationResult.class);
+        assertThat(policyEvalResult.getEffect(), equalTo(expectedEffect));
+    }
+
+    @Test(dataProvider = "policyEvalExceedingAttributeLimitDataProvider")
+    public void testPolicyEvaluationForAttributesExceedingTraversalLimit(final Zone zone, final PolicySet testPolicySet,
+            final List<BaseResource> resourceHierarchy, final List<BaseSubject> subjectHierarchy,
+            final PolicyEvaluationRequestV1 policyEvalRequest, final Effect expectedEffect,
+            final String expectedMessage) throws Exception {
+        Long traversalLimit = graphResourceRepository.getTraversalLimit();
+
+        graphResourceRepository.setTraversalLimit(TEST_TRAVERSAL_LIMIT);
+        graphSubjectRepository.setTraversalLimit(TEST_TRAVERSAL_LIMIT);
+
+        // Create policy set.
+
+        try {
+            String uri = POLICY_SET_URL + "/" + URLEncoder.encode(testPolicySet.getName(), "UTF-8");
+            MockMvcContext putPolicySetContext = this.testUtils.createWACWithCustomPUTRequestBuilder(this.wac,
+                    zone.getSubdomain(), uri);
+            putPolicySetContext.getMockMvc().perform(putPolicySetContext.getBuilder()
+                    .contentType(MediaType.APPLICATION_JSON).content(OBJECT_WRITER.writeValueAsString(testPolicySet)))
+                    .andExpect(status().isCreated());
+
+            // Create resource hierarchy.
+            if (null != resourceHierarchy) {
+                MockMvcContext postResourcesContext = this.testUtils.createWACWithCustomPOSTRequestBuilder(this.wac,
+                        zone.getSubdomain(), RESOURCE_URL);
+                postResourcesContext.getMockMvc()
+                        .perform(postResourcesContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                                .content(OBJECT_MAPPER.writeValueAsString(resourceHierarchy)))
+                        .andExpect(status().isNoContent());
+            }
+
+            // Create subject hierarchy.
+            if (null != subjectHierarchy) {
+                MockMvcContext postSubjectsContext = this.testUtils.createWACWithCustomPOSTRequestBuilder(this.wac,
+                        zone.getSubdomain(), SUBJECT_URL);
+                postSubjectsContext.getMockMvc()
+                        .perform(postSubjectsContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                                .content(OBJECT_MAPPER.writeValueAsString(subjectHierarchy)))
+                        .andExpect(status().isNoContent());
+            }
+
+            // Request policy evaluation.
+            MockMvcContext postPolicyEvalContext = this.testUtils.createWACWithCustomPOSTRequestBuilder(this.wac,
+                    zone.getSubdomain(), POLICY_EVAL_URL);
+            MvcResult mvcResult = postPolicyEvalContext.getMockMvc()
+                    .perform(postPolicyEvalContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                            .content(OBJECT_MAPPER.writeValueAsString(policyEvalRequest)))
+                    .andExpect(status().isOk()).andReturn();
+            PolicyEvaluationResult policyEvalResult = OBJECT_MAPPER
+                    .readValue(mvcResult.getResponse().getContentAsByteArray(), PolicyEvaluationResult.class);
+            assertThat(policyEvalResult.getEffect(), equalTo(expectedEffect));
+            assertThat(policyEvalResult.getMessage(), equalTo(expectedMessage));
+        } finally {
+            graphResourceRepository.setTraversalLimit(traversalLimit);
+            graphSubjectRepository.setTraversalLimit(traversalLimit);
+        }
+    }
+
+    @DataProvider(name = "policyEvalDataProvider")
+    private Object[][] policyEvalDataProvider() {
+        return new Object[][] { attributeInheritanceData(), scopedAttributeInheritanceData(),
+                evaluationWithNoSubjectAndNoResourceData(), evaluationWithSupplementalAttributesData() };
+    }
+
+    @DataProvider(name = "policyEvalExceedingAttributeLimitDataProvider")
+    private Object[][] policyEvalExceedingAttributeLimitDataProvider() {
+        return new Object[][] { evaluationWithResourceAttributesExceedingTraversalLimitData(),
+                evaluationWithSubjectAttributesExceedingTraversalLimitData() };
+    }
+
+    /**
+     * Test that subjects and resources inherit attributes from their parents. The policy set will permit the request
+     * if the subject and resource successfully inherit the required attributes from their respective parents.
+     */
+    Object[] attributeInheritanceData() {
+        return new Object[] { this.testZone1, this.policySet, createThreeLevelResourceHierarchy(),
+                createSubjectHierarchy(), createPolicyEvalRequest("GET", EVIDENCE_SCULLYS_TESTIMONY_ID, AGENT_MULDER),
+                Effect.PERMIT };
+    }
+
+    /**
+     * Test that subjects inherit attributes only when accessing resources with the right attributes. The policy set
+     * will deny the request if the subject accesses a resource that does not allow it inherit the required
+     * attributes.
+     */
+    Object[] scopedAttributeInheritanceData() {
+        return new Object[] { this.testZone1, this.policySet, createTwoParentResourceHierarchy(),
+                createScopedSubjectHierarchy(), createPolicyEvalRequest("GET", EVIDENCE_IMPLANT_ID, AGENT_MULDER),
+                Effect.DENY };
+    }
+
+    /**
+     * Test that evaluation is successful even when the resource and subject do not exist. The policy set will deny
+     * the request but return a successful result.
+     */
+    Object[] evaluationWithNoSubjectAndNoResourceData() {
+        return new Object[] { this.testZone1, this.policySet, null, null,
+                createPolicyEvalRequest("GET", EVIDENCE_IMPLANT_ID, AGENT_MULDER), Effect.DENY };
+    }
+
+    /**
+     * Test that evaluation is successful even when the request provides the attributes. The policy set will return
+     * permit because the condition is satisfied by the user provided supplemental attributes.
+     */
+    Object[] evaluationWithSupplementalAttributesData() {
+        return new Object[] { this.testZone1, this.policySet, null, null,
+                createPolicyEvalRequest("GET", EVIDENCE_IMPLANT_ID, AGENT_MULDER,
+                        new HashSet<Attribute>(
+                                Arrays.asList(
+                                        new Attribute[] {
+                                                SPECIAL_AGENTS_GROUP_ATTRIBUTE,
+                                                TOP_SECRET_CLASSIFICATION
+                                        })),
+                        new HashSet<Attribute>(
+                                Arrays.asList(
+                                        new Attribute[] {
+                                                SPECIAL_AGENTS_GROUP_ATTRIBUTE,
+                                                TOP_SECRET_CLASSIFICATION
+                                        }))),
+                Effect.PERMIT };
+    }
+
+    /**
+     * Test that evaluation is successful when the resource and/or subject attributes exceed the length.
+     * The policy set will return indeterminate because the traversal limit is exceeded.
+     */
+    Object[] evaluationWithResourceAttributesExceedingTraversalLimitData() {
+        String errorMessage = "The number of attributes on this resource '"
+                + EVIDENCE_SCULLYS_TESTIMONY_ID + "' has exceeded the maximum limit of " + TEST_TRAVERSAL_LIMIT;
+        return new Object[] { this.testZone1, this.policySet, createThreeLevelResourceHierarchy(), null,
+                createPolicyEvalRequest("GET", EVIDENCE_SCULLYS_TESTIMONY_ID, AGENT_MULDER),
+                Effect.INDETERMINATE, errorMessage};
+    }
+
+    /**
+     * Test that subjects and resources inherit attributes from their parents. The policy set will permit the request
+     * if the subject and resource successfully inherit the required attributes from their respective parents.
+     */
+    Object[] evaluationWithSubjectAttributesExceedingTraversalLimitData() {
+        String errorMessage = "The number of attributes on this subject '"
+                + AGENT_MULDER + "' has exceeded the maximum limit of " + TEST_TRAVERSAL_LIMIT;
+        return new Object[] { this.testZone1, this.policySet, createTwoLevelResourceHierarchy(),
+                createSubjectHierarchy(), createPolicyEvalRequest("GET", EVIDENCE_SCULLYS_TESTIMONY_ID, AGENT_MULDER),
+                Effect.INDETERMINATE, errorMessage};
+    }
+
+    PolicyEvaluationRequestV1 createPolicyEvalRequest(final String action, final String resourceIdentifier,
+            final String subjectIdentifier) {
+        PolicyEvaluationRequestV1 policyEvalRequest = new PolicyEvaluationRequestV1();
+        policyEvalRequest.setAction("GET");
+        policyEvalRequest.setResourceIdentifier(resourceIdentifier);
+        policyEvalRequest.setSubjectIdentifier(subjectIdentifier);
+        return policyEvalRequest;
+    }
+
+    PolicyEvaluationRequestV1 createPolicyEvalRequest(final String action, final String resourceIdentifier,
+            final String subjectIdentifier, final Set<Attribute> supplementalResourceAttributes,
+            final Set<Attribute> supplementalSubjectAttributes) {
+        PolicyEvaluationRequestV1 policyEvalRequest = new PolicyEvaluationRequestV1();
+        policyEvalRequest.setAction("GET");
+        policyEvalRequest.setResourceIdentifier(resourceIdentifier);
+        policyEvalRequest.setSubjectIdentifier(subjectIdentifier);
+        policyEvalRequest.setResourceAttributes(supplementalResourceAttributes);
+        policyEvalRequest.setSubjectAttributes(supplementalSubjectAttributes);
+        return policyEvalRequest;
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/controller/test/PolicyEvaluationControllerIT.java b/service/src/test/java/com/ge/predix/controller/test/PolicyEvaluationControllerIT.java
new file mode 100644
index 0000000..c50beb0
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/controller/test/PolicyEvaluationControllerIT.java
@@ -0,0 +1,290 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.controller.test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.web.context.WebApplicationContext;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ge.predix.acs.model.Effect;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.privilege.management.PrivilegeManagementService;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.PolicyEvaluationResult;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.service.policy.admin.PolicyManagementService;
+import com.ge.predix.acs.testutils.MockAcsRequestContext;
+import com.ge.predix.acs.testutils.MockMvcContext;
+import com.ge.predix.acs.testutils.MockSecurityContext;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.testutils.TestUtils;
+import com.ge.predix.acs.utils.JsonUtils;
+import com.ge.predix.acs.zone.management.ZoneService;
+
+@WebAppConfiguration
+@ContextConfiguration("classpath:controller-tests-context.xml")
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+@Test
+public class PolicyEvaluationControllerIT extends AbstractTestNGSpringContextTests {
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+    private static final String POLICY_EVAL_URL = "v1/policy-evaluation";
+    private static final LinkedHashSet<String> EMPTY_POLICY_EVALUATION_ORDER = new LinkedHashSet<>();
+
+    private final JsonUtils jsonUtils = new JsonUtils();
+    private final TestUtils testUtils = new TestUtils();
+    private Zone testZone;
+    private BaseSubject testSubject;
+    private BaseResource testResource;
+    private List<PolicySet> denyPolicySet;
+    private List<PolicySet> notApplicableAndDenyPolicySets;
+
+    @Autowired
+    private WebApplicationContext wac;
+
+    @Autowired
+    private ZoneService zoneService;
+
+    @Autowired
+    private PrivilegeManagementService privilegeManagementService;
+
+    @Autowired
+    private PolicyManagementService policyManagementService;
+
+    @BeforeClass
+    public void setup() {
+
+        this.testZone =  new TestUtils().setupTestZone("PolicyEvaluationControllerITZone", zoneService);
+        this.testSubject = new BaseSubject("testSubject");
+        this.testResource = new BaseResource("testResource");
+        Assert.assertTrue(this.privilegeManagementService.upsertResource(this.testResource));
+        Assert.assertTrue(this.privilegeManagementService.upsertSubject(this.testSubject));
+
+        this.denyPolicySet = createDenyPolicySet();
+        this.notApplicableAndDenyPolicySets = createNotApplicableAndDenyPolicySets();
+    }
+
+    @AfterMethod
+    public void testCleanup() {
+        List<PolicySet> policySets = this.policyManagementService.getAllPolicySets();
+        policySets.forEach(policySet -> this.policyManagementService.deletePolicySet(policySet.getName()));
+    }
+
+    @Test
+    public void testPolicyZoneDoesNotExistException() throws Exception {
+        MockSecurityContext.mockSecurityContext(null);
+        MockAcsRequestContext.mockAcsRequestContext();
+        PolicyEvaluationRequestV1 policyEvalRequest = createPolicyEvalRequest(this.testResource.getResourceIdentifier(),
+                this.testSubject.getSubjectIdentifier(), EMPTY_POLICY_EVALUATION_ORDER);
+        MockMvcContext postPolicyEvalContext = this.testUtils
+                .createWACWithCustomPOSTRequestBuilder(this.wac, this.testZone.getSubdomain(), POLICY_EVAL_URL);
+        ResultActions resultActions = postPolicyEvalContext.getMockMvc().perform(
+                postPolicyEvalContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                        .content(OBJECT_MAPPER.writeValueAsString(policyEvalRequest)));
+        resultActions.andReturn().getResponse()
+                .getContentAsString().contentEquals("Zone not found");
+        resultActions.andExpect(status().isBadRequest());
+
+        MockSecurityContext.mockSecurityContext(this.testZone);
+        MockAcsRequestContext.mockAcsRequestContext();
+    }
+
+    @Test
+    public void testPolicyInvalidMediaTypeResponseStatusCheck()
+            throws Exception {
+
+        MockMvcContext postPolicyEvalContext = this.testUtils
+                .createWACWithCustomPOSTRequestBuilder(this.wac, this.testZone.getSubdomain(), POLICY_EVAL_URL);
+        postPolicyEvalContext.getMockMvc().perform(
+                postPolicyEvalContext.getBuilder().contentType(MediaType.IMAGE_GIF_VALUE)
+                        .content("testString"))
+                .andExpect(status().isUnsupportedMediaType());
+    }
+
+    @Test(dataProvider = "policyEvalDataProvider")
+    public void testPolicyEvaluation(final PolicyEvaluationRequestV1 policyEvalRequest,
+            final List<PolicySet> policySets, final Effect expectedEffect) throws Exception {
+
+        if (policySets != null) {
+            upsertMultiplePolicySets(policySets);
+        }
+
+        MockMvcContext postPolicyEvalContext = this.testUtils
+                .createWACWithCustomPOSTRequestBuilder(this.wac, this.testZone.getSubdomain(), POLICY_EVAL_URL);
+        MvcResult mvcResult = postPolicyEvalContext.getMockMvc().perform(
+                postPolicyEvalContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                        .content(OBJECT_MAPPER.writeValueAsString(policyEvalRequest))).andExpect(status().isOk())
+                .andReturn();
+        PolicyEvaluationResult policyEvalResult = OBJECT_MAPPER
+                .readValue(mvcResult.getResponse().getContentAsByteArray(), PolicyEvaluationResult.class);
+
+        assertThat(policyEvalResult.getEffect(), equalTo(expectedEffect));
+    }
+
+    @Test(dataProvider = "policyEvalBadRequestDataProvider")
+    public void testPolicyEvaluationBadRequest(final PolicyEvaluationRequestV1 policyEvalRequest,
+            final List<PolicySet> policySets) throws Exception {
+
+        upsertMultiplePolicySets(policySets);
+
+        MockMvcContext postPolicyEvalContext = this.testUtils
+                .createWACWithCustomPOSTRequestBuilder(this.wac, this.testZone.getSubdomain(), POLICY_EVAL_URL);
+        postPolicyEvalContext.getMockMvc().perform(
+                postPolicyEvalContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                        .content(OBJECT_MAPPER.writeValueAsString(policyEvalRequest)))
+                .andExpect(status().isBadRequest());
+    }
+
+    @DataProvider(name = "policyEvalDataProvider")
+    private Object[][] policyEvalDataProvider() {
+        return new Object[][] { requestEvaluationWithEmptyPolicySet(),
+                requestEvaluationWithOnePolicySetAndEmptyPriorityList(),
+                requestEvaluationWithOnePolicySetAndPriorityList(), requestEvaluationWithAllOfTwoPolicySets(),
+                requestEvaluationWithFirstOfTwoPolicySets(), requestEvaluationWithSecondOfTwoPolicySets() };
+    }
+
+    private Object[] requestEvaluationWithEmptyPolicySet() {
+        return new Object[] { createPolicyEvalRequest(this.testResource.getResourceIdentifier(),
+                this.testSubject.getSubjectIdentifier(), EMPTY_POLICY_EVALUATION_ORDER), Collections.emptyList(),
+                Effect.NOT_APPLICABLE };
+    }
+
+    private Object[] requestEvaluationWithSecondOfTwoPolicySets() {
+        return new Object[] { createPolicyEvalRequest(this.testResource.getResourceIdentifier(),
+                this.testSubject.getSubjectIdentifier(), Stream.of(this.notApplicableAndDenyPolicySets.get(1).getName())
+                        .collect(Collectors.toCollection(LinkedHashSet::new))), this.notApplicableAndDenyPolicySets,
+                Effect.DENY };
+    }
+
+    private Object[] requestEvaluationWithFirstOfTwoPolicySets() {
+        return new Object[] { createPolicyEvalRequest(this.testResource.getResourceIdentifier(),
+                this.testSubject.getSubjectIdentifier(), Stream.of(this.notApplicableAndDenyPolicySets.get(0).getName())
+                        .collect(Collectors.toCollection(LinkedHashSet::new))), this.notApplicableAndDenyPolicySets,
+                Effect.NOT_APPLICABLE };
+    }
+
+    private Object[] requestEvaluationWithOnePolicySetAndPriorityList() {
+        return new Object[] { createPolicyEvalRequest(this.testResource.getResourceIdentifier(),
+                this.testSubject.getSubjectIdentifier(),
+                Stream.of(this.denyPolicySet.get(0).getName()).collect(Collectors.toCollection(LinkedHashSet::new))),
+                this.denyPolicySet, Effect.DENY };
+    }
+
+    private Object[] requestEvaluationWithOnePolicySetAndEmptyPriorityList() {
+        return new Object[] { createPolicyEvalRequest(this.testResource.getResourceIdentifier(),
+                this.testSubject.getSubjectIdentifier(), EMPTY_POLICY_EVALUATION_ORDER),
+                this.denyPolicySet, Effect.DENY };
+    }
+
+    private Object[] requestEvaluationWithAllOfTwoPolicySets() {
+        return new Object[] { createPolicyEvalRequest(this.testResource.getResourceIdentifier(),
+                this.testSubject.getSubjectIdentifier(), Stream.of(this.notApplicableAndDenyPolicySets.get(0).getName(),
+                        this.notApplicableAndDenyPolicySets.get(1).getName())
+                        .collect(Collectors.toCollection(LinkedHashSet::new))), this.notApplicableAndDenyPolicySets,
+                Effect.DENY };
+
+    }
+
+    @DataProvider(name = "policyEvalBadRequestDataProvider")
+    private Object[][] policyEvalBadRequestDataProvider() {
+        return new Object[][] { requestEvaluationWithNonExistentPolicySet(),
+                requestEvaluationWithTwoPolicySetsAndNoPriorityList(),
+                requestEvaluationWithExistentAndNonExistentPolicySets() };
+    }
+
+    private Object[] requestEvaluationWithExistentAndNonExistentPolicySets() {
+        return new Object[] { createPolicyEvalRequest(this.testResource.getResourceIdentifier(),
+                this.testSubject.getSubjectIdentifier(),
+                Stream.of(this.notApplicableAndDenyPolicySets.get(0).getName(), "noexistent-policy-set")
+                        .collect(Collectors.toCollection(LinkedHashSet::new))), this.notApplicableAndDenyPolicySets };
+    }
+
+    private Object[] requestEvaluationWithTwoPolicySetsAndNoPriorityList() {
+        return new Object[] { createPolicyEvalRequest(this.testResource.getResourceIdentifier(),
+                this.testSubject.getSubjectIdentifier(), EMPTY_POLICY_EVALUATION_ORDER),
+                this.notApplicableAndDenyPolicySets };
+    }
+
+    private Object[] requestEvaluationWithNonExistentPolicySet() {
+        return new Object[] { createPolicyEvalRequest(this.testResource.getResourceIdentifier(),
+                this.testSubject.getSubjectIdentifier(),
+                Stream.of("nonexistent-policy-set").collect(Collectors.toCollection(LinkedHashSet::new))),
+                this.denyPolicySet };
+    }
+
+    private PolicyEvaluationRequestV1 createPolicyEvalRequest(final String resourceIdentifier,
+            final String subjectIdentifier, final LinkedHashSet<String> policySetsPriority) {
+        PolicyEvaluationRequestV1 policyEvalRequest = new PolicyEvaluationRequestV1();
+        policyEvalRequest.setAction("GET");
+        policyEvalRequest.setResourceIdentifier(resourceIdentifier);
+        policyEvalRequest.setSubjectIdentifier(subjectIdentifier);
+        policyEvalRequest.setPolicySetsEvaluationOrder(policySetsPriority);
+        return policyEvalRequest;
+    }
+
+    private List<PolicySet> createDenyPolicySet() {
+        List<PolicySet> policySets = new ArrayList<>();
+        policySets.add(this.jsonUtils.deserializeFromFile("policies/testPolicyEvalDeny.json", PolicySet.class));
+        Assert.assertNotNull(policySets, "Policy set file is not found or invalid");
+        return policySets;
+    }
+
+    private List<PolicySet> createNotApplicableAndDenyPolicySets() {
+        List<PolicySet> policySets = new ArrayList<>();
+        policySets
+                .add(this.jsonUtils.deserializeFromFile("policies/testPolicyEvalNotApplicable.json", PolicySet.class));
+        policySets.add(this.jsonUtils.deserializeFromFile("policies/testPolicyEvalDeny.json", PolicySet.class));
+        Assert.assertNotNull(policySets, "Policy set files are not found or invalid");
+        Assert.assertTrue(policySets.size() == 2, "One or more policy set files are not found or invalid");
+        return policySets;
+    }
+
+    private void upsertPolicySet(final PolicySet policySet) {
+        this.policyManagementService.upsertPolicySet(policySet);
+        Assert.assertNotNull(this.policyManagementService.getPolicySet(policySet.getName()));
+    }
+
+    private void upsertMultiplePolicySets(final List<PolicySet> policySets) {
+        policySets.forEach(this::upsertPolicySet);
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/controller/test/PolicyManagementControllerIT.java b/service/src/test/java/com/ge/predix/controller/test/PolicyManagementControllerIT.java
new file mode 100644
index 0000000..de7aabf
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/controller/test/PolicyManagementControllerIT.java
@@ -0,0 +1,289 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.controller.test;
+
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.POLICY_SET_URL;
+import static org.hamcrest.Matchers.is;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.net.URI;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.web.context.WebApplicationContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.ge.predix.acs.commons.web.UriTemplateUtils;
+import com.ge.predix.acs.model.PolicySet;
+import com.ge.predix.acs.rest.PolicyEvaluationRequestV1;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.testutils.MockAcsRequestContext;
+import com.ge.predix.acs.testutils.MockMvcContext;
+import com.ge.predix.acs.testutils.MockSecurityContext;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.testutils.TestUtils;
+import com.ge.predix.acs.utils.JsonUtils;
+import com.ge.predix.acs.zone.management.ZoneService;
+
+@WebAppConfiguration
+@ContextConfiguration("classpath:controller-tests-context.xml")
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+@Test
+public class PolicyManagementControllerIT extends AbstractTestNGSpringContextTests {
+
+    public static final ConfigureEnvironment CONFIGURE_ENVIRONMENT = new ConfigureEnvironment();
+
+    private final ObjectWriter objectWriter = new ObjectMapper().writer().withDefaultPrettyPrinter();
+
+    @Autowired
+    private ZoneService zoneService;
+
+    @Autowired
+    private WebApplicationContext wac;
+
+    private PolicySet policySet;
+
+    private final JsonUtils jsonUtils = new JsonUtils();
+    private final TestUtils testUtils = new TestUtils();
+    private Zone testZone;
+    private Zone testZone2;
+
+    private static final String VERSION = "v1/";
+
+    @BeforeClass
+    public void setup() throws Exception {
+        this.testZone = new TestUtils().setupTestZone("PolicyMgmtControllerIT", zoneService);
+        this.policySet = this.jsonUtils.deserializeFromFile("controller-test/complete-sample-policy-set.json",
+                PolicySet.class);
+        Assert.assertNotNull(this.policySet, "complete-sample-policy-set.json file not found or invalid");
+    }
+
+    public void testCreatePolicyInvalidMediaTypeResponseStatusCheck() throws Exception {
+
+        String thisUri = VERSION + "/policy-set/" + this.policySet.getName();
+        // create policy-set in first zone
+        MockMvcContext putContext = this.testUtils
+                .createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        putContext.getMockMvc().perform(putContext.getBuilder().contentType(MediaType.APPLICATION_XML_VALUE)
+                .content("testString"))
+                .andExpect(status().isUnsupportedMediaType());
+    }
+
+    public void policyZoneDoesNotExistException() throws Exception {
+        // NOTE: To throw a ZoneDoesNotExistException, we must ensure that the AcsRequestContext in the
+        //       SpringSecurityZoneResolver class returns a null ZoneEntity
+        MockSecurityContext.mockSecurityContext(null);
+        MockAcsRequestContext.mockAcsRequestContext();
+        String thisUri = VERSION + "/policy-set/" + this.policySet.getName();
+        // create policy-set in first zone
+        MockMvcContext putContext = this.testUtils.createWACWithCustomPUTRequestBuilder(this.wac,
+                this.testZone.getSubdomain(), thisUri);
+        ResultActions resultActions = putContext.getMockMvc().perform(
+                putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                        .content(this.objectWriter.writeValueAsString(this.policySet)));
+        resultActions.andExpect(status().isUnprocessableEntity());
+        resultActions.andReturn().getResponse().getContentAsString().contains("zone 'null' does not exist");
+
+        MockSecurityContext.mockSecurityContext(this.testZone);
+        MockAcsRequestContext.mockAcsRequestContext();
+    }
+
+    public void testCreateSamePolicyDifferentZones() throws Exception {
+        String thisUri = VERSION + "/policy-set/" + this.policySet.getName();
+        // create policy-set in first zone
+        MockMvcContext putContext = this.testUtils.createWACWithCustomPUTRequestBuilder(this.wac,
+                this.testZone.getSubdomain(), thisUri);
+        putContext.getMockMvc().perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(this.objectWriter.writeValueAsString(this.policySet))).andExpect(status().isCreated());
+
+        // create policy set in second zone
+        this.testZone2 = new TestUtils().createTestZone("PolicyMgmtControllerIT2");
+        this.zoneService.upsertZone(this.testZone2);
+        MockSecurityContext.mockSecurityContext(this.testZone2);
+        putContext = this.testUtils.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone2.getSubdomain(),
+                thisUri);
+        putContext.getMockMvc().perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(this.objectWriter.writeValueAsString(this.policySet))).andExpect(status().isCreated());
+        // we expect both policy sets to be create in each zone
+        // set security context back to first test zone
+        MockSecurityContext.mockSecurityContext(this.testZone);
+        MockAcsRequestContext.mockAcsRequestContext();
+    }
+
+    public void testCreatePolicy() throws Exception {
+        String policySetName = upsertPolicySet(this.policySet);
+        MockMvcContext mockMvcContext = this.testUtils.createWACWithCustomGETRequestBuilder(this.wac,
+                this.testZone.getSubdomain(), VERSION + "policy-set/" + policySetName);
+        mockMvcContext.getMockMvc().perform(mockMvcContext.getBuilder()).andExpect(status().isOk())
+                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
+                .andExpect(jsonPath("name").value(policySetName)).andExpect(jsonPath("policies").isArray())
+                .andExpect(jsonPath("policies[1].target.resource.attributes[0].name").value("group"));
+    }
+
+    @Test
+    public void testCreateMultiplePolicySets() throws Exception {
+        //create first policy set
+        String policySetName = upsertPolicySet(this.policySet);
+
+        MockMvcContext mockMvcContext = this.testUtils.createWACWithCustomGETRequestBuilder(this.wac,
+                this.testZone.getSubdomain(), VERSION + "policy-set/" + policySetName);
+
+        //assert first policy set
+        mockMvcContext.getMockMvc().perform(mockMvcContext.getBuilder()).andExpect(status().isOk())
+                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
+                .andExpect(jsonPath("name").value(policySetName)).andExpect(jsonPath("policies").isArray())
+                .andExpect(jsonPath("policies[1].target.resource.attributes[0].name").value("group"));
+
+        String policySet2Name = "";
+        try {
+            //create second policy set
+            PolicySet policySet2 = this.jsonUtils.deserializeFromFile("controller-test/multiple-policy-set-test.json",
+                    PolicySet.class);
+            Assert.assertNotNull(policySet2, "multiple-policy-set-test.json file not found or invalid");
+            policySet2Name = upsertPolicySet(policySet2);
+
+            mockMvcContext = this.testUtils.createWACWithCustomGETRequestBuilder(this.wac,
+                    this.testZone.getSubdomain(), VERSION + "policy-set/" + policySet2Name);
+
+            //assert second policy set
+            mockMvcContext.getMockMvc().perform(mockMvcContext.getBuilder()).andExpect(status().isOk())
+            .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
+            .andExpect(jsonPath("name").value(policySet2Name));
+
+            //assert that policy evaluation fails
+            PolicyEvaluationRequestV1 evalRequest = new PolicyEvaluationRequestV1();
+            evalRequest.setAction("GET");
+            evalRequest.setSubjectIdentifier("test-user");
+            evalRequest.setResourceIdentifier("/app/testuri");
+            String evalRequestJson = this.objectWriter.writeValueAsString(evalRequest);
+            mockMvcContext = this.testUtils.createWACWithCustomPOSTRequestBuilder(this.wac,
+                    this.testZone.getSubdomain(), VERSION + "policy-evaluation");
+            mockMvcContext.getMockMvc()
+                    .perform(mockMvcContext.getBuilder().content(evalRequestJson)
+                            .contentType(MediaType.APPLICATION_JSON))
+                    .andExpect(status().isBadRequest())
+                    .andExpect(jsonPath("ErrorDetails.errorMessage")
+                          .value("More than one policy set exists for this zone. Please provide an ordered list "
+                                  + "of policy set names to consider for this evaluation and resubmit the request."));
+        } finally {
+            mockMvcContext = this.testUtils.createWACWithCustomDELETERequestBuilder(this.wac,
+                    this.testZone.getSubdomain(), VERSION + "policy-set/" + policySet2Name);
+            mockMvcContext.getMockMvc().perform(mockMvcContext.getBuilder()).andExpect(status().is2xxSuccessful());
+        }
+    }
+
+    public void testGetNonExistentPolicySet() throws Exception {
+        MockMvcContext mockMvcContext = this.testUtils.createWACWithCustomGETRequestBuilder(this.wac,
+                this.testZone.getSubdomain(), VERSION + "/policy-set/non-existent");
+        mockMvcContext.getMockMvc().perform(mockMvcContext.getBuilder().accept(MediaType.APPLICATION_JSON))
+                .andExpect(status().isNotFound());
+    }
+
+    public void testCreatePolicyWithNoPolicySet() throws Exception {
+        MockMvcContext ctxt = this.testUtils.createWACWithCustomPUTRequestBuilder(this.wac,
+                this.testZone.getSubdomain(), VERSION + "policy-set/policyNoBody");
+        ctxt.getMockMvc().perform(ctxt.getBuilder().contentType(MediaType.APPLICATION_JSON_UTF8))
+                .andExpect(status().isBadRequest());
+    }
+
+    public void testDeletePolicySet() throws Exception {
+        String policySetName = upsertPolicySet(this.policySet);
+        MockMvcContext ctxt = this.testUtils.createWACWithCustomDELETERequestBuilder(this.wac,
+                this.testZone.getSubdomain(), VERSION + "/policy-set/" + policySetName);
+        ctxt.getMockMvc().perform(ctxt.getBuilder()).andExpect(status().isNoContent());
+
+        // assert policy is gone
+        MockMvcContext getContext = this.testUtils.createWACWithCustomGETRequestBuilder(this.wac,
+                this.testZone.getSubdomain(), VERSION + "/policy-set/" + policySetName);
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isNotFound());
+    }
+
+    public void testGetAllPolicySets() throws Exception {
+        String firstPolicySetName = upsertPolicySet(this.policySet);
+        MockMvcContext mockMvcContext = this.testUtils.createWACWithCustomGETRequestBuilder(this.wac,
+                this.testZone.getSubdomain(), VERSION + "policy-set");
+        mockMvcContext.getMockMvc().perform(mockMvcContext.getBuilder().accept(MediaType.APPLICATION_JSON))
+                .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
+                .andExpect(jsonPath("$[0].name", is(firstPolicySetName)));
+
+    }
+
+    private String upsertPolicySet(final PolicySet myPolicySet) throws JsonProcessingException, Exception {
+
+        String policySetContent = this.objectWriter.writeValueAsString(myPolicySet);
+        String policySetName = myPolicySet.getName();
+        MockMvcContext ctxt = this.testUtils.createWACWithCustomPUTRequestBuilder(this.wac,
+                this.testZone.getSubdomain(), VERSION + "policy-set/" + myPolicySet.getName());
+        URI policySetUri = UriTemplateUtils.expand(POLICY_SET_URL, "policySetId:" + policySetName);
+        String policySetPath = policySetUri.getPath();
+
+        ctxt.getMockMvc().perform(ctxt.getBuilder().content(policySetContent).contentType(MediaType.APPLICATION_JSON))
+                .andExpect(status().isCreated()).andExpect(header().string("Location", policySetPath));
+
+        return policySetName;
+    }
+
+    public void testCreatePolicyEmptyPolicySetName() throws Exception {
+        PolicySet simplePolicyEmptyName = this.jsonUtils
+                .deserializeFromFile("controller-test/simple-policy-set-empty-name.json", PolicySet.class);
+        Assert.assertNotNull(simplePolicyEmptyName, "simple-policy-set-empty-name.json file not found or invalid");
+
+        MockMvcContext ctxt = this.testUtils.createWACWithCustomPUTRequestBuilder(this.wac,
+                this.testZone.getSubdomain(), VERSION + "policy-set/policyWithEmptyName");
+
+        String policySetPayload = this.jsonUtils.serialize(simplePolicyEmptyName);
+        ctxt.getMockMvc().perform(ctxt.getBuilder().contentType(MediaType.APPLICATION_JSON).content(policySetPayload))
+                .andExpect(status().isUnprocessableEntity());
+    }
+
+    public void testCreatePolicyNoPolicySetName() throws Exception {
+        PolicySet simplePolicyNoName = this.jsonUtils
+                .deserializeFromFile("controller-test/simple-policy-set-no-name.json", PolicySet.class);
+        Assert.assertNotNull(simplePolicyNoName, "simple-policy-set-no-name.json file not found or invalid");
+
+        MockMvcContext ctxt = this.testUtils.createWACWithCustomPUTRequestBuilder(this.wac,
+                this.testZone.getSubdomain(), VERSION + "policy-set/policyWithNoName");
+        String policySetPayload = this.jsonUtils.serialize(simplePolicyNoName);
+        ctxt.getMockMvc().perform(ctxt.getBuilder().contentType(MediaType.APPLICATION_JSON).content(policySetPayload))
+                .andExpect(status().isUnprocessableEntity());
+
+    }
+
+    public void testCreatePolicyUriPolicySetIdMismatch() throws Exception {
+
+        String policySetPayload = this.jsonUtils.serialize(this.policySet);
+        MockMvcContext ctxt = this.testUtils.createWACWithCustomPUTRequestBuilder(this.wac,
+                this.testZone.getSubdomain(), VERSION + "policy-set/mismatchWithPolicy");
+        ctxt.getMockMvc().perform(ctxt.getBuilder().contentType(MediaType.APPLICATION_JSON).content(policySetPayload))
+                .andExpect(status().isUnprocessableEntity());
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/controller/test/ResourcePrivilegeManagementControllerIT.java b/service/src/test/java/com/ge/predix/controller/test/ResourcePrivilegeManagementControllerIT.java
new file mode 100644
index 0000000..b657b7b
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/controller/test/ResourcePrivilegeManagementControllerIT.java
@@ -0,0 +1,435 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+// @formatter:off
+package com.ge.predix.controller.test;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.isIn;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.web.context.WebApplicationContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ge.predix.acs.privilege.management.PrivilegeManagementUtility;
+import com.ge.predix.acs.request.context.AcsRequestContext;
+import com.ge.predix.acs.request.context.AcsRequestContextHolder;
+import com.ge.predix.acs.rest.BaseResource;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.testutils.MockAcsRequestContext;
+import com.ge.predix.acs.testutils.MockMvcContext;
+import com.ge.predix.acs.testutils.MockSecurityContext;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.testutils.TestUtils;
+import com.ge.predix.acs.utils.JsonUtils;
+import com.ge.predix.acs.zone.management.ZoneService;
+
+@WebAppConfiguration
+@ContextConfiguration("classpath:controller-tests-context.xml")
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+public class ResourcePrivilegeManagementControllerIT extends AbstractTestNGSpringContextTests {
+    static final String RESOURCE_BASE_URL = "/v1/resource";
+    static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+    static final JsonUtils JSON_UTILS = new JsonUtils();
+    static final TestUtils TEST_UTILS = new TestUtils();
+
+    @Autowired
+    private WebApplicationContext wac;
+
+    @Autowired
+    private ZoneService zoneService;
+
+    private Zone testZone;
+
+    private Zone testZone2;
+
+    @Autowired
+    private ConfigurableEnvironment env;
+
+    @BeforeClass
+    public void setup() throws Exception {
+        this.testZone = TEST_UTILS.setupTestZone("ResourceMgmtControllerIT", zoneService);
+
+    }
+
+
+  @Test
+    public void testResourceInvalidMediaTypeResponseStatusCheck() throws Exception {
+
+        String thisUri = RESOURCE_BASE_URL + "/%2Fservices%2Fsecured-api";
+        // create resource in first zone
+        MockMvcContext putContext =
+            TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        putContext.getMockMvc().perform(putContext.getBuilder().contentType(MediaType.APPLICATION_OCTET_STREAM_VALUE)
+                .content("testString")).andExpect(status().isUnsupportedMediaType());
+
+    }
+
+    @Test
+     public void resourceZoneDoesNotExistException() throws Exception {
+         // NOTE: To throw a ZoneDoesNotExistException, we must ensure that the AcsRequestContext in the
+        //       SpringSecurityZoneResolver class returns a null ZoneEntity
+         MockSecurityContext.mockSecurityContext(null);
+         MockAcsRequestContext.mockAcsRequestContext();
+         BaseResource resource = JSON_UTILS.deserializeFromFile("controller-test/a-resource.json",
+                 BaseResource.class);
+         String thisUri = RESOURCE_BASE_URL + "/%2Fservices%2Fsecured-api";
+         // create resource in first zone
+         MockMvcContext putContext =
+             TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+         ResultActions resultActions = putContext.getMockMvc().perform(putContext.getBuilder().
+                 contentType(MediaType.APPLICATION_JSON).content(OBJECT_MAPPER.writeValueAsString(resource)));
+         resultActions.andExpect(status().isBadRequest());
+         resultActions.andReturn().getResponse().getContentAsString().contentEquals("Zone not found");
+         MockSecurityContext.mockSecurityContext(this.testZone);
+         MockAcsRequestContext.mockAcsRequestContext();
+     }
+
+
+    @Test
+    public void testSameResourceDifferentZones() throws Exception {
+        BaseResource resource = JSON_UTILS.deserializeFromFile("controller-test/a-resource.json",
+                                                                    BaseResource.class);
+        String thisUri = RESOURCE_BASE_URL + "/%2Fservices%2Fsecured-api";
+        // create resource in first zone
+        MockMvcContext putContext =
+            TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        putContext.getMockMvc().perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(resource))).andExpect(status().isCreated());
+
+        // create resource in second zone
+        this.testZone2 = TEST_UTILS.setupTestZone("ResourceMgmtControllerIT2", zoneService);
+        putContext = TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone2.getSubdomain(),
+                                                                          thisUri);
+        putContext.getMockMvc().perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(resource))).andExpect(status().isCreated());
+        // we expect both resources to be create in each zone
+        // set security context back to first test zone
+        MockSecurityContext.mockSecurityContext(this.testZone);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testPOSTResources() throws Exception {
+
+        List<BaseResource> resources = JSON_UTILS.deserializeFromFile("controller-test/resources-collection.json",
+                                                                           List.class);
+        Assert.assertNotNull(resources);
+
+        // Append a list of resources
+        MockMvcContext postContext =
+            TEST_UTILS.createWACWithCustomPOSTRequestBuilder(this.wac, this.testZone.getSubdomain(), RESOURCE_BASE_URL);
+        postContext.getMockMvc().perform(postContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(resources))).andExpect(status().isNoContent());
+
+        // Get the list of resources
+        MockMvcContext getContext =
+            TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(), RESOURCE_BASE_URL);
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isOk())
+                .andExpect(
+                        jsonPath("$[0].resourceIdentifier",
+                                isIn(new String[] { "/services/secured-api",
+                                        "/services/reports/01928374102398741235123" })))
+                .andExpect(
+                        jsonPath("$[1].resourceIdentifier",
+                                isIn(new String[] { "/services/secured-api",
+                                        "/services/reports/01928374102398741235123" })))
+                .andExpect(jsonPath("$[0].attributes[0].value", isIn(new String[] { "sales", "admin" })))
+                .andExpect(jsonPath("$[0].attributes[0].issuer", is("https://acs.attributes.int")))
+                .andExpect(jsonPath("$[1].attributes[0].value", isIn(new String[] { "sales", "admin" })))
+                .andExpect(jsonPath("$[1].attributes[0].issuer", is("https://acs.attributes.int")));
+
+        BaseResource resource = JSON_UTILS.deserializeFromFile("controller-test/a-resource.json",
+                                                                    BaseResource.class);
+        Assert.assertNotNull(resource);
+
+        String aResourceURI = RESOURCE_BASE_URL + "/%2Fservices%2Fsecured-api";
+
+        // Update a given resource
+        MockMvcContext updateContext =
+            TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(), aResourceURI);
+        updateContext.getMockMvc().perform(updateContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(resource))).andExpect(status().isNoContent());
+
+        // Get a given resource
+        getContext = TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(),
+                                                                          aResourceURI);
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isOk())
+                .andExpect(jsonPath("resourceIdentifier", is("/services/secured-api")))
+                .andExpect(jsonPath("attributes[0].value", isIn(new String[] { "supervisor", "it" })))
+                .andExpect(jsonPath("attributes[0].issuer", is("https://acs.attributes.int")));
+
+        MockMvcContext deleteContext =
+            TEST_UTILS.createWACWithCustomDELETERequestBuilder(this.wac, this.testZone.getSubdomain(), aResourceURI);
+
+        // Delete a given resource
+        deleteContext.getMockMvc().perform(deleteContext.getBuilder()).andExpect(status().isNoContent());
+
+        // Delete a given resource
+        deleteContext = TEST_UTILS.createWACWithCustomDELETERequestBuilder(this.wac, this.testZone.getSubdomain(),
+                RESOURCE_BASE_URL + "/01928374102398741235123");
+        deleteContext.getMockMvc().perform(deleteContext.getBuilder()).andExpect(status().isNoContent());
+
+        // Make sure resource does not exist anymore
+        getContext = TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(),
+                                                                          "/tenant/ge/resource/0192837410239874");
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isNotFound());
+
+        // Make sure resource does not exist anymore
+        getContext = TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(),
+                                                                     "/tenant/ge/resource/01928374102398741235123");
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isNotFound());
+    }
+
+    @Test
+    public void testZoneDoesNotExist() throws Exception {
+
+        Zone testZone3 = new Zone("name", "subdomain", "description");
+        MockSecurityContext.mockSecurityContext(testZone3);
+
+        Map<AcsRequestContext.ACSRequestContextAttribute, Object> newMap = new HashMap<>();
+        newMap.put(AcsRequestContext.ACSRequestContextAttribute.ZONE_ENTITY, null);
+
+        ReflectionTestUtils.setField(AcsRequestContextHolder.getAcsRequestContext(),
+                "unModifiableRequestContextMap", newMap);
+
+        MockMvcContext getContext =
+                TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, testZone3.getSubdomain(),
+                        RESOURCE_BASE_URL + "/test-resource");
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isBadRequest())
+                .andExpect(jsonPath(PrivilegeManagementUtility.INCORRECT_PARAMETER_TYPE_ERROR, is("Bad Request")))
+                .andExpect(jsonPath(PrivilegeManagementUtility.INCORRECT_PARAMETER_TYPE_MESSAGE,
+                        is("Zone not found")));
+        MockAcsRequestContext.mockAcsRequestContext();
+    }
+
+    @Test
+    public void testPOSTResourcesMissingResourceId() throws Exception {
+        List<BaseResource> resources = JSON_UTILS.deserializeFromFile(
+                "controller-test/missing-resourceIdentifier-resources-collection.json", List.class);
+
+        Assert.assertNotNull(resources);
+
+        // Append a list of resources
+        MockMvcContext postContext =
+            TEST_UTILS.createWACWithCustomPOSTRequestBuilder(this.wac, this.testZone.getSubdomain(), RESOURCE_BASE_URL);
+        postContext.getMockMvc()
+                .perform(postContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                        .content(OBJECT_MAPPER.writeValueAsString(resources)))
+                .andExpect(status().isUnprocessableEntity());
+
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testPOSTResourcesMissingIdentifier() throws Exception {
+        List<BaseResource> resources = JSON_UTILS
+                .deserializeFromFile("controller-test/missing-identifier-resources-collection.json", List.class);
+
+        Assert.assertNotNull(resources);
+
+        // Append a list of resources
+        MockMvcContext postContext =
+            TEST_UTILS.createWACWithCustomPOSTRequestBuilder(this.wac, this.testZone.getSubdomain(), RESOURCE_BASE_URL);
+        postContext.getMockMvc()
+                .perform(postContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                        .content(OBJECT_MAPPER.writeValueAsString(resources)))
+                .andExpect(status().isUnprocessableEntity());
+
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testPOSTResourcesEmptyIdentifier() throws Exception {
+        List<BaseResource> resources = JSON_UTILS
+                .deserializeFromFile("controller-test/empty-identifier-resources-collection.json", List.class);
+
+        Assert.assertNotNull(resources);
+
+        // Append a list of resources
+        MockMvcContext postContext =
+            TEST_UTILS.createWACWithCustomPOSTRequestBuilder(this.wac, this.testZone.getSubdomain(), RESOURCE_BASE_URL);
+        postContext.getMockMvc()
+                .perform(postContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                        .content(OBJECT_MAPPER.writeValueAsString(resources)))
+                .andExpect(status().isUnprocessableEntity());
+    }
+
+    @Test
+    public void testResourceIdentifierMismatch() throws Exception {
+
+        BaseResource resource = JSON_UTILS.deserializeFromFile("controller-test/a-mismatched-resourceid.json",
+                                                                    BaseResource.class);
+        Assert.assertNotNull(resource);
+
+        String thisUri = RESOURCE_BASE_URL + "/%2Fservices%2Fsecured-api";
+
+        // Update a given resource
+        MockMvcContext putContext =
+            TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        putContext.getMockMvc()
+                .perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                        .content(OBJECT_MAPPER.writeValueAsString(resource)))
+                .andExpect(status().isUnprocessableEntity());
+
+    }
+
+    @Test
+    public void testTypeMismatchForQueryParameter() throws Exception {
+
+        // GET a given resource
+        String thisUri = RESOURCE_BASE_URL + "/%2Fservices%2Fsecured-api?includeInheritedAttributes=true)";
+        MockMvcContext getContext =
+            TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        getContext.getMockMvc()
+                .perform(getContext.getBuilder().contentType(MediaType.APPLICATION_JSON))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath(PrivilegeManagementUtility.INCORRECT_PARAMETER_TYPE_ERROR, is(HttpStatus
+                .BAD_REQUEST.getReasonPhrase())))
+                .andExpect(jsonPath(PrivilegeManagementUtility.INCORRECT_PARAMETER_TYPE_MESSAGE,
+                is("Request Parameter " + PrivilegeManagementUtility.INHERITED_ATTRIBUTES_REQUEST_PARAMETER
+                                    + " must be a boolean value")));
+
+    }
+
+    @Test
+    public void testPUTResourceNoResourceId() throws Exception {
+
+        BaseResource resource = JSON_UTILS
+                .deserializeFromFile("controller-test/no-resourceIdentifier-resource.json", BaseResource.class);
+        Assert.assertNotNull(resource);
+
+        String thisUri = RESOURCE_BASE_URL + "/%2Fservices%2Fsecured-api%2Fsubresource";
+        // Update a given resource
+        MockMvcContext putContext =
+            TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        putContext.getMockMvc().perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(resource))).andExpect(status().is2xxSuccessful());
+
+        // Get a given resource
+        MockMvcContext getContext =
+            TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isOk())
+                .andExpect(jsonPath("resourceIdentifier", is("/services/secured-api/subresource")))
+                .andExpect(jsonPath("attributes[0].value", isIn(new String[] { "supervisor", "it" })))
+                .andExpect(jsonPath("attributes[0].issuer", is("https://acs.attributes.int")));
+
+        // Delete a given resource
+        MockMvcContext deleteContext =
+            TEST_UTILS.createWACWithCustomDELETERequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        deleteContext.getMockMvc().perform(deleteContext.getBuilder()).andExpect(status().isNoContent());
+    }
+
+    @Test
+    public void testPUTCreateResourceThenUpdateNoResourceIdentifier() throws Exception {
+
+        BaseResource resource = JSON_UTILS
+                .deserializeFromFile("controller-test/with-resourceIdentifier-resource.json", BaseResource.class);
+        Assert.assertNotNull(resource);
+
+        String thisUri = RESOURCE_BASE_URL + "/%2Fservices%2Fsecured-api%2Fsubresource";
+
+        MockMvcContext putContext =
+            TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        putContext.getMockMvc().perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(resource))).andExpect(status().isCreated());
+
+        // Get a given resource
+        MockMvcContext getContext =
+            TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isOk())
+                .andExpect(jsonPath("resourceIdentifier", is(resource.getResourceIdentifier())))
+                .andExpect(jsonPath("attributes[0].value", isIn(new String[] { "supervisor", "it" })))
+                .andExpect(jsonPath("attributes[0].issuer", is("https://acs.attributes.int")));
+
+        // Ensure we can update resource without a resource identifier in json
+        // payload
+        // In this case, the resource identifier must be part of the URI.
+        BaseResource resourceNoResourceIdentifier = JSON_UTILS
+                .deserializeFromFile("controller-test/no-resourceIdentifier-resource.json", BaseResource.class);
+        Assert.assertNotNull(resourceNoResourceIdentifier);
+
+        // Update a given resource
+        putContext = TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(),
+                                                                          thisUri);
+        putContext.getMockMvc()
+                .perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                        .content(OBJECT_MAPPER.writeValueAsString(resourceNoResourceIdentifier)))
+                .andExpect(status().isNoContent());
+
+        // Get a given resource
+        getContext = TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(),
+                                                                          thisUri);
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isOk())
+                .andExpect(jsonPath("resourceIdentifier", is(resource.getResourceIdentifier())))
+                .andExpect(jsonPath("attributes[0].value", isIn(new String[] { "supervisor", "it" })))
+                .andExpect(jsonPath("attributes[0].issuer", is("https://acs.attributes.int")));
+
+        // Delete a given resource
+        MockMvcContext deleteContext =
+            TEST_UTILS.createWACWithCustomDELETERequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        deleteContext.getMockMvc().perform(deleteContext.getBuilder()).andExpect(status().isNoContent());
+
+    }
+
+    @Test
+    public void testPathVariablesWithSpecialCharacters() throws Exception {
+
+        // If the given string violates RFC&nbsp;2396 it will throw http 422
+        // error
+        // The following characters are valid: @#!$*&()_-+=[]:;{}'~`,
+        String decoded = "/services/special/@#!$*&()_-+=[]:;{}'~`,";
+        String encoded = URLEncoder.encode(decoded, "UTF-8");
+        String thisUri = RESOURCE_BASE_URL + "/" + encoded;
+
+        BaseResource resource = JSON_UTILS
+                .deserializeFromFile("controller-test/special-character-resource-identifier.json", BaseResource.class);
+        Assert.assertNotNull(resource);
+
+        MockMvcContext putContext =
+            TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        putContext.getMockMvc().perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(resource))).andExpect(status().isCreated());
+
+        MockMvcContext getContext =
+            TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isOk())
+                .andExpect(jsonPath("resourceIdentifier", is(decoded)));
+    }
+
+}
+// @formatter:on
diff --git a/service/src/test/java/com/ge/predix/controller/test/SubjectPrivilegeManagementControllerIT.java b/service/src/test/java/com/ge/predix/controller/test/SubjectPrivilegeManagementControllerIT.java
new file mode 100644
index 0000000..ca71ac9
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/controller/test/SubjectPrivilegeManagementControllerIT.java
@@ -0,0 +1,490 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+// @formatter:off
+package com.ge.predix.controller.test;
+
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.isIn;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.web.context.WebApplicationContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ge.predix.acs.privilege.management.PrivilegeManagementUtility;
+import com.ge.predix.acs.request.context.AcsRequestContext;
+import com.ge.predix.acs.request.context.AcsRequestContextHolder;
+import com.ge.predix.acs.rest.BaseSubject;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.testutils.MockAcsRequestContext;
+import com.ge.predix.acs.testutils.MockMvcContext;
+import com.ge.predix.acs.testutils.MockSecurityContext;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.testutils.TestUtils;
+import com.ge.predix.acs.utils.JsonUtils;
+import com.ge.predix.acs.zone.management.ZoneService;
+
+/**
+ *
+ * @author acs-engineers@ge.com
+ */
+@WebAppConfiguration
+@ContextConfiguration("classpath:controller-tests-context.xml")
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+public class SubjectPrivilegeManagementControllerIT extends AbstractTestNGSpringContextTests {
+    static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+    static final String SUBJECT_BASE_URL = "/v1/subject";
+    static final JsonUtils JSON_UTILS = new JsonUtils();
+    static final TestUtils TEST_UTILS = new TestUtils();
+
+    @Autowired
+    private ZoneService zoneService;
+
+    @Autowired
+    private WebApplicationContext wac;
+
+    private Zone testZone;
+    private Zone testZone2;
+
+    @Autowired
+    private ConfigurableEnvironment env;
+
+    @BeforeClass
+    public void setup() {
+        this.testZone = TEST_UTILS.setupTestZone("SubjectMgmtControllerIT", zoneService);
+    }
+
+
+    @Test
+    public void subjectZoneDoesNotExistException() throws Exception {
+        // NOTE: To throw a ZoneDoesNotExistException, we must ensure that the AcsRequestContext in the
+        //       SpringSecurityZoneResolver class returns a null ZoneEntity
+        MockSecurityContext.mockSecurityContext(null);
+        MockAcsRequestContext.mockAcsRequestContext();
+
+        BaseSubject subject = JSON_UTILS.deserializeFromFile("controller-test/a-subject.json", BaseSubject.class);
+        MockMvcContext putContext = TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac,
+                "zoneDoesNotExist", SUBJECT_BASE_URL + '/' + subject.getSubjectIdentifier());
+        ResultActions resultActions = putContext.getMockMvc().perform(putContext.getBuilder()
+                .contentType(MediaType.APPLICATION_JSON).content(OBJECT_MAPPER.writeValueAsString(subject)));
+        resultActions.andExpect(status().isBadRequest());
+        resultActions.andReturn().getResponse().getContentAsString().contentEquals("Zone not found");
+        MockSecurityContext.mockSecurityContext(this.testZone);
+        MockAcsRequestContext.mockAcsRequestContext();
+    }
+
+    @Test
+    public void testSameSubjectDifferentZones() throws Exception {
+        BaseSubject subject = JSON_UTILS.deserializeFromFile("controller-test/a-subject.json", BaseSubject.class);
+        String thisUri = SUBJECT_BASE_URL + "/dave";
+        // create subject in first zone
+        MockMvcContext putContext =
+            TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        putContext.getMockMvc().perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(subject))).andExpect(status().isCreated());
+
+        // create subject in second zone
+        this.testZone2 = TEST_UTILS.setupTestZone("SubjectMgmtControllerIT2", zoneService);
+
+        putContext = TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone2.getSubdomain(),
+                                                                          thisUri);
+        putContext.getMockMvc().perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(subject))).andExpect(status().isCreated());
+        // we expect both subjects to be create in each zone
+        // set security context back to first test zone
+        MockSecurityContext.mockSecurityContext(this.testZone);
+    }
+
+    @Test
+    public void testSubjectInvalidMediaTypeResponseStatusCheck() throws Exception {
+
+        MockMvcContext postContext =
+            TEST_UTILS.createWACWithCustomPOSTRequestBuilder(this.wac, this.testZone.getSubdomain(), SUBJECT_BASE_URL);
+        postContext.getMockMvc().perform(postContext.getBuilder().contentType(MediaType.TEXT_PLAIN)
+                .content("testString")).andExpect(status().isUnsupportedMediaType());
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testSubjects() throws Exception {
+        List<Subject> subjects = JSON_UTILS.deserializeFromFile("controller-test/subjects-collection.json",
+                                                                     List.class);
+        Assert.assertNotNull(subjects);
+
+        // Append a list of subjects
+        MockMvcContext postContext =
+            TEST_UTILS.createWACWithCustomPOSTRequestBuilder(this.wac, this.testZone.getSubdomain(), SUBJECT_BASE_URL);
+        postContext.getMockMvc().perform(postContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(subjects))).andExpect(status().isNoContent());
+
+        // Get the list of subjects
+        MockMvcContext getContext =
+            TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(), SUBJECT_BASE_URL);
+
+        ResultActions resultActions = getContext.getMockMvc().perform(getContext.getBuilder());
+        assertSubjects(resultActions, 2, new String[] { "dave", "vineet" });
+        assertSubjectsAttributes(resultActions, 2, 2, new String[] { "group", "department" },
+                new String[] { "sales", "admin" }, new String[] { "https://acs.attributes.int" });
+
+        BaseSubject subject = JSON_UTILS.deserializeFromFile("controller-test/a-subject.json", BaseSubject.class);
+        Assert.assertNotNull(subject);
+
+        // Update a given subject
+
+        String thisUri = SUBJECT_BASE_URL + "/dave";
+
+        MockMvcContext putContext =
+            TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        putContext.getMockMvc().perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(subject))).andExpect(status().isNoContent());
+
+        // Get a given subject
+        getContext = TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(),
+                                                                          thisUri);
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isOk())
+                .andExpect(jsonPath("subjectIdentifier", is("dave")))
+                .andExpect(jsonPath("attributes[0].value", isIn(new String[] { "supervisor", "it" })))
+                .andExpect(jsonPath("attributes[0].issuer", is("https://acs.attributes.int")));
+
+        // Delete resources from created collection
+        thisUri = SUBJECT_BASE_URL + "/vineet";
+        MockMvcContext deleteContext =
+            TEST_UTILS.createWACWithCustomDELETERequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        deleteContext.getMockMvc().perform(deleteContext.getBuilder()).andExpect(status().isNoContent());
+        // Make sure subject does not exist anymore
+        getContext = TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(),
+                                                                          thisUri);
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isNotFound());
+        thisUri = SUBJECT_BASE_URL + "/dave";
+        deleteContext = TEST_UTILS.createWACWithCustomDELETERequestBuilder(this.wac, this.testZone.getSubdomain(),
+                                                                                thisUri);
+        deleteContext.getMockMvc().perform(deleteContext.getBuilder()).andExpect(status().isNoContent());
+
+        // Make sure subject does not exist anymore
+        getContext = TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(),
+                                                                          thisUri);
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isNotFound());
+    }
+
+    /*
+     * This test posts a collection of subjects where one is missing an subject identifier
+     */
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testPOSTSubjectsMissingSubjectIdentifier() throws Exception {
+
+        List<Subject> subjects = JSON_UTILS
+                .deserializeFromFile("controller-test/missing-subjectidentifier-collection.json", List.class);
+
+        Assert.assertNotNull(subjects);
+
+        // Append a list of subjects
+        MockMvcContext postContext =
+            TEST_UTILS.createWACWithCustomPOSTRequestBuilder(this.wac, this.testZone.getSubdomain(), SUBJECT_BASE_URL);
+        postContext.getMockMvc()
+                .perform(postContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                        .content(OBJECT_MAPPER.writeValueAsString(subjects)))
+                .andExpect(status().isUnprocessableEntity());
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testPOSTSubjectsEmptyIdentifier() throws Exception {
+
+        List<Subject> subjects = JSON_UTILS
+                .deserializeFromFile("controller-test/empty-identifier-subjects-collection.json", List.class);
+        Assert.assertNotNull(subjects);
+
+        // Append a list of subjects
+        MockMvcContext postContext =
+            TEST_UTILS.createWACWithCustomPOSTRequestBuilder(this.wac, this.testZone.getSubdomain(), SUBJECT_BASE_URL);
+        postContext.getMockMvc()
+                .perform(postContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                        .content(OBJECT_MAPPER.writeValueAsString(subjects)))
+                .andExpect(status().isUnprocessableEntity());
+
+    }
+
+    @Test
+    public void testPUTSubjectIdentifierMismatch() throws Exception {
+        BaseSubject subject = JSON_UTILS.deserializeFromFile("controller-test/a-mismatched-subjectidentifier.json",
+                                                                  BaseSubject.class);
+        Assert.assertNotNull(subject);
+
+        // Update a given resource
+        String thisUri = SUBJECT_BASE_URL + "/dave";
+        MockMvcContext putContext =
+            TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        putContext.getMockMvc()
+                .perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                        .content(OBJECT_MAPPER.writeValueAsString(subject)))
+                .andExpect(status().isUnprocessableEntity());
+
+    }
+
+    @Test
+    public void testTypeMismatchForQueryParameter() throws Exception {
+
+        // GET a given resource
+        String thisUri = SUBJECT_BASE_URL + "/dave?includeInheritedAttributes=true)";
+        MockMvcContext getContext =
+            TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        getContext.getMockMvc()
+                .perform(getContext.getBuilder().contentType(MediaType.APPLICATION_JSON))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath(PrivilegeManagementUtility.INCORRECT_PARAMETER_TYPE_ERROR, is(HttpStatus
+                .BAD_REQUEST.getReasonPhrase())))
+                .andExpect(jsonPath(PrivilegeManagementUtility.INCORRECT_PARAMETER_TYPE_MESSAGE,
+                is("Request Parameter " + PrivilegeManagementUtility.INHERITED_ATTRIBUTES_REQUEST_PARAMETER
+                                    + " must be a boolean value")));
+
+    }
+
+    /*
+     * This tests putting a single subject that is does not have a subject identifier
+     */
+    @Test
+    public void testPUTSubjectNoSubjectIdentifier() throws Exception {
+
+        BaseSubject subject = JSON_UTILS.deserializeFromFile("controller-test/no-subjectidentifier-subject.json",
+                                                                  BaseSubject.class);
+        Assert.assertNotNull(subject);
+
+        // Update a given resource
+        String thisUri = SUBJECT_BASE_URL + "/fermin";
+        MockMvcContext putContext =
+            TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        putContext.getMockMvc().perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(subject))).andExpect(status().is2xxSuccessful());
+
+        // Get a given resource
+        MockMvcContext getContext =
+            TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        ResultActions resultActions = getContext.getMockMvc().perform(getContext.getBuilder());
+
+        resultActions.andExpect(status().isOk()).andExpect(jsonPath("subjectIdentifier", is("fermin")))
+                .andExpect(jsonPath("attributes[0].value", isIn(new String[] { "sales", "admin" })))
+                .andExpect(jsonPath("attributes[0].issuer", is("https://acs.attributes.int")));
+
+        // Delete a given resource
+        MockMvcContext deleteContext =
+            TEST_UTILS.createWACWithCustomDELETERequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        deleteContext.getMockMvc().perform(deleteContext.getBuilder()).andExpect(status().isNoContent());
+
+        // Make sure subject does not exist anymore
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isNotFound());
+    }
+
+    @Test
+    public void testPUTCreateSubjectThenUpdateNoSubjectIdentifier() throws Exception {
+
+        BaseSubject subject = JSON_UTILS.deserializeFromFile("controller-test/with-subjectidentifier-subject.json",
+                                                                  BaseSubject.class);
+        Assert.assertNotNull(subject);
+
+        String subjectIdentifier = "fermin";
+        String thisUri = SUBJECT_BASE_URL + "/" + subjectIdentifier;
+
+        MockMvcContext putContext =
+            TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        putContext.getMockMvc().perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(subject))).andExpect(status().isCreated());
+
+        MockMvcContext getContext =
+            TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        ResultActions resultActions = getContext.getMockMvc().perform(getContext.getBuilder());
+
+        resultActions.andExpect(status().isOk()).andExpect(jsonPath("subjectIdentifier", is(subjectIdentifier)))
+                .andExpect(jsonPath("attributes[0].value", isIn(new String[] { "admin", "sales" })))
+                .andExpect(jsonPath("attributes[0].issuer", is("https://acs.attributes.int")));
+
+        BaseSubject subjectNoSubjectIdentifier = JSON_UTILS
+                .deserializeFromFile("controller-test/no-subjectidentifier-subject.json", BaseSubject.class);
+        Assert.assertNotNull(subjectNoSubjectIdentifier);
+
+        putContext = TEST_UTILS.createWACWithCustomPUTRequestBuilder(this.wac, this.testZone.getSubdomain(),
+                                                                          thisUri);
+        putContext.getMockMvc()
+                .perform(putContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                        .content(OBJECT_MAPPER.writeValueAsString(subjectNoSubjectIdentifier)))
+                .andExpect(status().isNoContent());
+
+        getContext = TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(),
+                                                                          thisUri);
+        resultActions = getContext.getMockMvc().perform(getContext.getBuilder());
+
+        resultActions.andExpect(status().isOk()).andExpect(jsonPath("subjectIdentifier",
+                is(subjectIdentifier)))
+                .andExpect(jsonPath("attributes[0].value", isIn(new String[] { "admin", "sales" })))
+                .andExpect(jsonPath("attributes[0].issuer", is("https://acs.attributes.int")));
+
+        // Delete a given resource
+        MockMvcContext deleteContext =
+            TEST_UTILS.createWACWithCustomDELETERequestBuilder(this.wac, this.testZone.getSubdomain(), thisUri);
+        deleteContext.getMockMvc().perform(deleteContext.getBuilder()).andExpect(status().isNoContent());
+
+    }
+
+    @Test
+    public void testZoneDoesNotExist() throws Exception {
+        Zone testZone3 = new Zone("name", "subdomain", "description");
+        MockSecurityContext.mockSecurityContext(testZone3);
+
+        Map<AcsRequestContext.ACSRequestContextAttribute, Object> newMap = new HashMap<>();
+        newMap.put(AcsRequestContext.ACSRequestContextAttribute.ZONE_ENTITY, null);
+
+        ReflectionTestUtils.setField(AcsRequestContextHolder.getAcsRequestContext(),
+                "unModifiableRequestContextMap", newMap);
+
+        MockMvcContext getContext =
+                TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, testZone3.getSubdomain(),
+                        SUBJECT_BASE_URL + "/test-subject");
+        getContext.getMockMvc().perform(getContext.getBuilder()).andExpect(status().isBadRequest())
+                .andExpect(jsonPath(PrivilegeManagementUtility.INCORRECT_PARAMETER_TYPE_ERROR, is("Bad Request")))
+                .andExpect(jsonPath(PrivilegeManagementUtility.INCORRECT_PARAMETER_TYPE_MESSAGE,
+                        is("Zone not found")));
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testPOSTCreateSubjectThenUpdateAttributes() throws Exception {
+        // appending two subjects, the key one for this test is dave.
+        List<BaseSubject> subjects = JSON_UTILS.deserializeFromFile("controller-test/subjects-collection.json",
+                                                                         List.class);
+        Assert.assertNotNull(subjects);
+
+        // Append a list of subjects
+        MockMvcContext postContext =
+            TEST_UTILS.createWACWithCustomPOSTRequestBuilder(this.wac, this.testZone.getSubdomain(), SUBJECT_BASE_URL);
+        postContext.getMockMvc().perform(postContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(subjects))).andExpect(status().isNoContent());
+
+        MockMvcContext getContext =
+            TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(), SUBJECT_BASE_URL);
+        ResultActions resultActions = getContext.getMockMvc().perform(getContext.getBuilder());
+
+        assertSubjects(resultActions, 2, new String[] { "dave", "vineet" });
+        assertSubjectsAttributes(resultActions, 2, 2, new String[] { "group", "department" },
+                new String[] { "admin", "sales" }, new String[] { "https://acs.attributes.int" });
+
+        subjects = JSON_UTILS
+                .deserializeFromFile("controller-test/subjects-collection-with-different-attributes.json", List.class);
+        Assert.assertNotNull(subjects);
+
+        postContext = TEST_UTILS.createWACWithCustomPOSTRequestBuilder(this.wac, this.testZone.getSubdomain(),
+                                                                            SUBJECT_BASE_URL);
+        postContext.getMockMvc().perform(postContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(subjects))).andExpect(status().isNoContent());
+
+        getContext = TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(),
+                                                                          SUBJECT_BASE_URL);
+        resultActions = getContext.getMockMvc().perform(getContext.getBuilder());
+        assertSubjects(resultActions, 2, new String[] { "dave", "vineet" });
+        assertSubjectsAttributes(resultActions, 2, 2, new String[] { "group", "department" },
+                new String[] { "different", "sales" }, new String[] { "https://acs.attributes.int" });
+
+        // delete dave
+        MockMvcContext deleteContext =
+            TEST_UTILS.createWACWithCustomDELETERequestBuilder(this.wac, this.testZone.getSubdomain(),
+                                                               SUBJECT_BASE_URL + "/dave");
+        deleteContext.getMockMvc().perform(deleteContext.getBuilder()).andExpect(status().isNoContent());
+
+        // delete vineet
+        deleteContext = TEST_UTILS.createWACWithCustomDELETERequestBuilder(this.wac, this.testZone.getSubdomain(),
+                SUBJECT_BASE_URL + "/vineet");
+        deleteContext.getMockMvc().perform(deleteContext.getBuilder()).andExpect(status().isNoContent());
+
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testPOSTCreateSubjectNoDuplicates() throws Exception {
+        // appending two subjects, the key one for this test is dave.
+
+        List<BaseSubject> subjects = JSON_UTILS
+                .deserializeFromFile("controller-test/a-single-subject-collection.json", List.class);
+        Assert.assertNotNull(subjects);
+
+        // Append a list of subjects
+        MockMvcContext postContext =
+            TEST_UTILS.createWACWithCustomPOSTRequestBuilder(this.wac, this.testZone.getSubdomain(), SUBJECT_BASE_URL);
+        postContext.getMockMvc().perform(postContext.getBuilder().contentType(MediaType.APPLICATION_JSON)
+                .content(OBJECT_MAPPER.writeValueAsString(subjects))).andExpect(status().isNoContent());
+
+        MockMvcContext getContext =
+            TEST_UTILS.createWACWithCustomGETRequestBuilder(this.wac, this.testZone.getSubdomain(), SUBJECT_BASE_URL);
+        ResultActions resultActions = getContext.getMockMvc().perform(getContext.getBuilder());
+        assertSubjects(resultActions, 1, new String[] { "dave" });
+
+        MockMvcContext deleteContext =
+            TEST_UTILS.createWACWithCustomDELETERequestBuilder(this.wac, this.testZone.getSubdomain(),
+                                                               SUBJECT_BASE_URL + "/dave");
+        deleteContext.getMockMvc().perform(deleteContext.getBuilder()).andExpect(status().isNoContent());
+    }
+
+    private void assertSubjects(final ResultActions resultActions, final int size, final String[] identifiers)
+            throws Exception {
+        resultActions.andExpect(status().isOk()).andExpect(jsonPath("$", hasSize(size)));
+
+        for (int i = 0; i < size; i++) {
+            String subjectIdentifierPath = String.format("$[%s].subjectIdentifier", i);
+            resultActions.andExpect(jsonPath(subjectIdentifierPath, isIn(identifiers)));
+        }
+    }
+
+    private void assertSubjectsAttributes(final ResultActions resultActions, final int numOfSubjects,
+            final int numOfAttrs, final String[] attrNames, final String[] attrValues, final String[] attrIssuers)
+                    throws Exception {
+        resultActions.andExpect(status().isOk()).andExpect(jsonPath("$", hasSize(numOfSubjects)));
+
+        for (int i = 0; i < numOfSubjects; i++) {
+            for (int j = 0; j < numOfAttrs; j++) {
+                String attrValuePath = String.format("$[%s].attributes[%s].value", i, j);
+                String attrNamePath = String.format("$[%s].attributes[%s].name", i, j);
+                String attrIssuerPath = String.format("$[%s].attributes[%s].issuer", i, j);
+
+                resultActions.andExpect(jsonPath(attrValuePath, isIn(attrValues)))
+                        .andExpect(jsonPath(attrNamePath, isIn(attrNames)))
+                        .andExpect(jsonPath(attrIssuerPath, isIn(attrIssuers)));
+            }
+        }
+    }
+
+}
+// @formatter:on
diff --git a/service/src/test/java/com/ge/predix/controller/test/URLPathMatchingIT.java b/service/src/test/java/com/ge/predix/controller/test/URLPathMatchingIT.java
new file mode 100644
index 0000000..0004e2d
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/controller/test/URLPathMatchingIT.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.controller.test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.utils.JsonUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.RequestBuilder;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebAppConfiguration
+@ContextConfiguration("classpath:controller-tests-context.xml")
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+@Test
+public class URLPathMatchingIT extends AbstractTestNGSpringContextTests {
+    @Autowired
+    private WebApplicationContext wac;
+
+    private MockMvc mockMvc;
+    private final JsonUtils jsonUtils = new JsonUtils();
+    private final ObjectWriter objectWriter = new ObjectMapper().writer().withDefaultPrettyPrinter();
+    private Zone zone;
+
+    @BeforeClass
+    public void setup() {
+        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
+    }
+
+    @Test(dataProvider = "nonMatchedUrlPatternDp")
+    public void testReturnNotFoundForNotMatchedURLs(final RequestBuilder request) throws Exception {
+        this.mockMvc.perform(request).andExpect(status().isNotFound());
+    }
+
+    @DataProvider
+    public Object[][] nonMatchedUrlPatternDp() throws JsonProcessingException {
+        this.zone = this.jsonUtils.deserializeFromFile("controller-test/createZone.json", Zone.class);
+        Assert.assertNotNull(this.zone, "createZone.json file not found or invalid");
+        String zoneContent = this.objectWriter.writeValueAsString(this.zone);
+
+        return new Object[][] {
+                { put("/ /v1/zone/zone-1", "zone-1").contentType(MediaType.APPLICATION_JSON).content(zoneContent) },
+                { put("/v1/ /zone/zone-1", "zone-1").contentType(MediaType.APPLICATION_JSON).content(zoneContent) },
+                { get("/ /v1/zone/zone-2").contentType(MediaType.APPLICATION_JSON) },
+                { get("/v1/ /zone/zone-2").contentType(MediaType.APPLICATION_JSON) }
+        };
+    }
+}
diff --git a/service/src/test/java/com/ge/predix/controller/test/ZoneControllerIT.java b/service/src/test/java/com/ge/predix/controller/test/ZoneControllerIT.java
new file mode 100644
index 0000000..38852a8
--- /dev/null
+++ b/service/src/test/java/com/ge/predix/controller/test/ZoneControllerIT.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright 2017 General Electric Company
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package com.ge.predix.controller.test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.ge.predix.acs.rest.Zone;
+import com.ge.predix.acs.testutils.TestActiveProfilesResolver;
+import com.ge.predix.acs.utils.JsonUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.V1;
+import static com.ge.predix.acs.commons.web.AcsApiUriTemplates.ZONE_URL;
+import static org.hamcrest.Matchers.is;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebAppConfiguration
+@ContextConfiguration("classpath:controller-tests-context.xml")
+@ActiveProfiles(resolver = TestActiveProfilesResolver.class)
+@Test
+public class ZoneControllerIT extends AbstractTestNGSpringContextTests {
+
+    private static final String V1_ZONE_URL = V1 + ZONE_URL;
+
+    private final ObjectWriter objectWriter = new ObjectMapper().writer().withDefaultPrettyPrinter();
+
+    @Autowired
+    private WebApplicationContext wac;
+
+    private MockMvc mockMvc;
+
+    private final JsonUtils jsonUtils = new JsonUtils();
+
+    private Zone zone;
+
+    @BeforeClass
+    public void setup() {
+        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
+    }
+
+    public void testCreateAndGetAndDeleteZone() throws Exception {
+        this.zone = this.jsonUtils.deserializeFromFile("controller-test/createZone.json", Zone.class);
+        Assert.assertNotNull(this.zone, "createZone.json file not found or invalid");
+        String zoneContent = this.objectWriter.writeValueAsString(this.zone);
+        this.mockMvc.perform(put(V1_ZONE_URL, "zone-1").contentType(MediaType.APPLICATION_JSON).content(zoneContent))
+                .andExpect(status().isCreated());
+
+        this.mockMvc.perform(get(V1_ZONE_URL, "zone-1"))
+                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isOk())
+                .andExpect(jsonPath("name", is("zone-1"))).andExpect(jsonPath("subdomain", is("subdomain-1")));
+
+        this.mockMvc.perform(delete(V1_ZONE_URL, "zone-1")).andExpect(status().isNoContent());
+    }
+
+    public void testZoneInvalidMediaTypeResponseStatusCheck() throws Exception {
+
+        this.mockMvc.perform(put(V1_ZONE_URL, "zone-1").contentType(MediaType.TEXT_XML_VALUE).content("testString"))
+                .andExpect(status().isUnsupportedMediaType());
+    }
+
+    public void testUpdateZone() throws Exception {
+        this.zone = this.jsonUtils.deserializeFromFile("controller-test/createZone.json", Zone.class);
+        Assert.assertNotNull(this.zone, "createZone.json file not found or invalid");
+        String zoneContent = this.objectWriter.writeValueAsString(this.zone);
+        this.mockMvc.perform(put(V1_ZONE_URL, "zone-1").contentType(MediaType.APPLICATION_JSON).content(zoneContent))
+                .andExpect(status().isCreated());
+
+        this.zone = this.jsonUtils.deserializeFromFile("controller-test/updateZone.json", Zone.class);
+        Assert.assertNotNull(this.zone, "updateZone.json file not found or invalid");
+        String updatedZoneContent = this.objectWriter.writeValueAsString(this.zone);
+        this.mockMvc
+                .perform(
+                        put(V1_ZONE_URL, "zone-1").contentType(MediaType.APPLICATION_JSON).content(updatedZoneContent))
+                .andExpect(status().isCreated());
+        this.mockMvc.perform(get(V1_ZONE_URL, "zone-1"))
+                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isOk())
+                .andExpect(jsonPath("name", is("zone-1"))).andExpect(jsonPath("subdomain", is("subdomain-2")));
+
+        this.mockMvc.perform(delete(V1_ZONE_URL, "zone-1")).andExpect(status().isNoContent());
+    }
+
+    public void testCreateZoneWithExistingSubdomain() throws Exception {
+
+        Zone zone1 = this.jsonUtils.deserializeFromFile("controller-test/createZone.json", Zone.class);
+        Assert.assertNotNull(zone1, "createZone.json file not found or invalid");
+        String zoneContent1 = this.objectWriter.writeValueAsString(zone1);
+
+        this.mockMvc.perform(put(V1_ZONE_URL, "zone-1").contentType(MediaType.APPLICATION_JSON).content(zoneContent1))
+                .andExpect(status().isCreated());
+
+        Zone zone2 = this.jsonUtils.deserializeFromFile("controller-test/createZoneTwo.json", Zone.class);
+        Assert.assertNotNull(zone2, "createZoneTwo.json file not found or invalid");
+        String zoneContent2 = this.objectWriter.writeValueAsString(zone2);
+        this.mockMvc.perform(put(V1_ZONE_URL, "zone-2").contentType(MediaType.APPLICATION_JSON).content(zoneContent2))
+                .andExpect(status().isUnprocessableEntity());
+
+        this.mockMvc.perform(delete(V1_ZONE_URL, "zone-1")).andExpect(status().isNoContent());
+    }
+
+    public void testCreateZonewithNoSubdomain() throws Exception {
+        this.zone = this.jsonUtils.deserializeFromFile("controller-test/zone-with-no-subdomain.json", Zone.class);
+        Assert.assertNotNull(this.zone, "zone-with-no-subdomain.json file not found or invalid");
+        String zoneContent = this.objectWriter.writeValueAsString(this.zone);
+        this.mockMvc.perform(put(V1_ZONE_URL, "zone-3").contentType(MediaType.APPLICATION_JSON).content(zoneContent))
+                .andExpect(status().isUnprocessableEntity());
+    }
+
+    public void testGetZoneWhichDoesNotExists() throws Exception {
+        this.mockMvc.perform(get(V1_ZONE_URL, "zone-2"))
+                .andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpect(status().isNotFound());
+    }
+
+    public void testDeleteZoneWhichDoesNotExist() throws Exception {
+        this.mockMvc.perform(delete(V1_ZONE_URL, "zone-2")).andExpect(status().isNotFound());
+    }
+
+}
diff --git a/service/src/test/resources/application.properties b/service/src/test/resources/application.properties
new file mode 100644
index 0000000..584f1c0
--- /dev/null
+++ b/service/src/test/resources/application.properties
@@ -0,0 +1,55 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+#Set default profile
+spring.profiles.default=h2,public
+
+server.port = ${ACS_LOCAL_PORT}
+
+#This property is not being recognized by flyway in testng tests. migrations must live in default db/migrations
+#flyway.locations=filesystem:db/h2
+
+bootstrapUaaIssuerId=
+uaaCheckHealthUrl=
+zacCheckHealthUrl=
+
+clientId=fake-client
+clientSecret=fake-client
+
+ACS_BASE_DOMAIN=localhost
+ACS_UAA_URL=http://localhost:8080/uaa
+METER_BASE_DOMAIN=https://acsUrl.com
+
+zac.client.id=fake-client
+zac.client.secret=fake-client
+
+//fake values to load the properties during controller tests
+NUREGO_USERNAME=nuregoUsername
+NUREGO_PASSWORD=nuregoPassword
+NUREGO_INSTANCE_ID=nuregoInstanceId
+NUREGO_API_URL=https://fakeUrl.com
+NUREGO_BATCH_INTERVAL_SECONDS=3600
+NUREGO_BATCH_MAX_MAP_SIZE=1024
+
+cors.xhr.allowed.headers = Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method, Access-Control-Request-Headers
+cors.xhr.allowed.origins = ${CORS_XHR_ALLOWED_ORIGINS:}
+cors.xhr.allowed.uris = ^/v2/api-docs$
+cors.xhr.controlmaxage = 1728000
+cors.xhr.allowed.methods = GET
+
+ENCRYPTION_KEY=vskIfNpBoePioXtP
diff --git a/service/src/test/resources/complete-sample-policy-set-2.json b/service/src/test/resources/complete-sample-policy-set-2.json
new file mode 100644
index 0000000..b9e6060
--- /dev/null
+++ b/service/src/test/resources/complete-sample-policy-set-2.json
@@ -0,0 +1,61 @@
+{
+    "name" : "test-policy-set",
+    "policies" : [
+        {
+            "name" : "Agents can access a site if they are stationed at the site.",
+            "target" : {
+                "name" : "When an agent accesses a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Agent",
+                    "attributes" : [
+                        { "issuer" : "acs.example.org",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('acs.example.org', 'site'), resource.uriVariable('site_id'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Agents can access evidence if they are a member of the evidence group and have the right clearance.",
+            "target" : {
+                "name" : "When an agent accesses evidence",
+                "resource" : {
+                    "name" : "Evidence",
+                    "uriTemplate" : "/evidence/{evidence_id}",
+                    "attributes" : [
+                        { "issuer" : "acs.example.org",
+                          "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Agent",
+                    "attributes" : [
+                        { "issuer" : "acs.example.org",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "resource.and(subject).haveSame('acs.example.org', 'group').result()" },
+                { "name" : "has clearance",
+                  "condition" : "resource.and(subject).haveSame('acs.example.org', 'classification').result()" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Deny all other operations by default",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/complete-sample-policy-set.json b/service/src/test/resources/complete-sample-policy-set.json
new file mode 100644
index 0000000..bbc1180
--- /dev/null
+++ b/service/src/test/resources/complete-sample-policy-set.json
@@ -0,0 +1,158 @@
+{
+    "name" : "test-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), 'sanramon')" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read an asset if they are a member of the asset group.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can modify a site if they are assigned to the site and they are a manager.",
+            "target" : {
+                "name" : "When an operator modifies a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "PUT",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "role" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uriVariable('site_id'))" },
+                { "name" : "is a manager",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'role'), 'manager')" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can create a case if they own the alarm and they are members of the asset group.",
+            "target" : {
+                "name" : "When an operator creates an case for an asset alarm",
+                "resource" : {
+                    "name" : "Asset alarm cases",
+                    "uriTemplate" : "/assets/{asset_id}/alarms/{alarm_id}/cases",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "owner" }
+                    ]
+                },
+                "action" : "POST",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "name_id" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" },
+                { "name" : "is owner of",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'name_id'), resource.attributes('https://acs.attributes.int', 'owner'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can see all sites that they are assigned to",
+            "target" : {
+                "name" : "When an operator reads all sites",
+                "resource" : {
+                    "name" : "Sites",
+                    "uriTemplate" : "/sites"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can see all assets that are assigned to their asset groups",
+            "target" : {
+                "name" : "When an operator reads all assets",
+                "resource" : {
+                    "name" : "Assets",
+                    "uriTemplate" : "/assets"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Deny all other operations by default",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/controller-test/a-mismatched-resourceid.json b/service/src/test/resources/controller-test/a-mismatched-resourceid.json
new file mode 100644
index 0000000..fc4f834
--- /dev/null
+++ b/service/src/test/resources/controller-test/a-mismatched-resourceid.json
@@ -0,0 +1,14 @@
+{
+	"resourceIdentifier" : "/services/secured-api_EXTRA_INVALID_STUFF",
+	"attributes" : [
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "group",
+	      "value": "supervisor" 
+	    }
+	    ,
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "department",
+	       "value": "it" 
+	    }
+	]
+}
diff --git a/service/src/test/resources/controller-test/a-mismatched-subjectidentifier.json b/service/src/test/resources/controller-test/a-mismatched-subjectidentifier.json
new file mode 100644
index 0000000..e49a30b
--- /dev/null
+++ b/service/src/test/resources/controller-test/a-mismatched-subjectidentifier.json
@@ -0,0 +1,14 @@
+{
+	"subjectIdentifier" : "davewithinvalidsutff",
+	"attributes" : [
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "group",
+	      "value": "supervisor" 
+	    }
+	    ,
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "department",
+	       "value": "it" 
+	    }
+	]
+}
diff --git a/service/src/test/resources/controller-test/a-resource-with-parents.json b/service/src/test/resources/controller-test/a-resource-with-parents.json
new file mode 100644
index 0000000..72fc0f6
--- /dev/null
+++ b/service/src/test/resources/controller-test/a-resource-with-parents.json
@@ -0,0 +1,26 @@
+{
+	"resourceIdentifier" : "/services/secured-api-with-parents",
+	"attributes" : [
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "group",
+	      "value": "supervisor"
+	    }
+	    ,
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "department",
+	       "value": "it"
+	    }
+	],
+	"parents" : [
+		{
+			"identifier" : "role-analyst",
+			"scopes" : [
+				{
+					"issuer" : "https://acs.predix.io",
+					"name"   : "site",
+					"value"  : "san-ramon"
+				}
+			]
+		}
+	]
+}
diff --git a/service/src/test/resources/controller-test/a-resource.json b/service/src/test/resources/controller-test/a-resource.json
new file mode 100644
index 0000000..416159f
--- /dev/null
+++ b/service/src/test/resources/controller-test/a-resource.json
@@ -0,0 +1,14 @@
+{
+	"resourceIdentifier" : "/services/secured-api",
+	"attributes" : [
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "group",
+	      "value": "supervisor" 
+	    }
+	    ,
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "department",
+	       "value": "it" 
+	    }
+	]
+}
diff --git a/service/src/test/resources/controller-test/a-single-subject-collection.json b/service/src/test/resources/controller-test/a-single-subject-collection.json
new file mode 100644
index 0000000..c8bba24
--- /dev/null
+++ b/service/src/test/resources/controller-test/a-single-subject-collection.json
@@ -0,0 +1,16 @@
+[
+	{
+		"subjectIdentifier" : "dave",
+		"attributes" : [
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "group",
+		      "value": "supervisor" 
+		    }
+		    ,
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "department",
+		       "value": "it" 
+		    }
+		]
+	}
+]
\ No newline at end of file
diff --git a/service/src/test/resources/controller-test/a-subject-with-identical-deprecated-id.json b/service/src/test/resources/controller-test/a-subject-with-identical-deprecated-id.json
new file mode 100644
index 0000000..fe70886
--- /dev/null
+++ b/service/src/test/resources/controller-test/a-subject-with-identical-deprecated-id.json
@@ -0,0 +1,15 @@
+{
+	"subjectIdentifier" : "dave",
+	"subjectId" : "dave",
+	"attributes" : [
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "group",
+	      "value": "supervisor" 
+	    }
+	    ,
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "department",
+	       "value": "it" 
+	    }
+	]
+}
diff --git a/service/src/test/resources/controller-test/a-subject-with-invalid-deprecated-id.json b/service/src/test/resources/controller-test/a-subject-with-invalid-deprecated-id.json
new file mode 100644
index 0000000..7a21064
--- /dev/null
+++ b/service/src/test/resources/controller-test/a-subject-with-invalid-deprecated-id.json
@@ -0,0 +1,15 @@
+{
+	"subjectIdentifier" : "dave",
+	"subjectId" : "changed-dave",
+	"attributes" : [
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "group",
+	      "value": "supervisor" 
+	    }
+	    ,
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "department",
+	       "value": "it" 
+	    }
+	]
+}
diff --git a/service/src/test/resources/controller-test/a-subject-with-parents.json b/service/src/test/resources/controller-test/a-subject-with-parents.json
new file mode 100644
index 0000000..ada3f20
--- /dev/null
+++ b/service/src/test/resources/controller-test/a-subject-with-parents.json
@@ -0,0 +1,26 @@
+{
+	"subjectIdentifier" : "dave-with-parents",
+	"attributes" : [
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "group",
+	      "value": "supervisor"
+	    }
+	    ,
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "department",
+	       "value": "it"
+	    }
+	],
+	"parents" : [
+		{
+			"identifier" : "role-analyst",
+			"scopes" : [
+				{
+					"issuer" : "https://acs.predix.io",
+					"name"   : "site",
+					"value"  : "san-ramon"
+				}
+			]
+		}
+	]
+}
diff --git a/service/src/test/resources/controller-test/a-subject.json b/service/src/test/resources/controller-test/a-subject.json
new file mode 100644
index 0000000..15223f2
--- /dev/null
+++ b/service/src/test/resources/controller-test/a-subject.json
@@ -0,0 +1,14 @@
+{
+	"subjectIdentifier" : "dave",
+	"attributes" : [
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "group",
+	      "value": "supervisor" 
+	    }
+	    ,
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "department",
+	       "value": "it" 
+	    }
+	]
+}
diff --git a/service/src/test/resources/controller-test/complete-sample-policy-set-second.json b/service/src/test/resources/controller-test/complete-sample-policy-set-second.json
new file mode 100644
index 0000000..b2d43c3
--- /dev/null
+++ b/service/src/test/resources/controller-test/complete-sample-policy-set-second.json
@@ -0,0 +1,158 @@
+{
+    "name" : "test-policy-set-second",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), 'sanramon')" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read an asset if they are a member of the asset group.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can modify a site if they are assigned to the site and they are a manager.",
+            "target" : {
+                "name" : "When an operator modifies a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "PUT",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "role" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uriVariable('site_id'))" },
+                { "name" : "is a manager",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'role'), 'manager')" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can create a case if they own the alarm and they are members of the asset group.",
+            "target" : {
+                "name" : "When an operator creates an case for an asset alarm",
+                "resource" : {
+                    "name" : "Asset alarm cases",
+                    "uriTemplate" : "/assets/{asset_id}/alarms/{alarm_id}/cases",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "owner" }
+                    ]
+                },
+                "action" : "POST",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "name_id" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" },
+                { "name" : "is owner of",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'name_id'), resource.attributes('https://acs.attributes.int', 'owner'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can see all sites that they are assigned to",
+            "target" : {
+                "name" : "When an operator reads all sites",
+                "resource" : {
+                    "name" : "Sites",
+                    "uriTemplate" : "/sites"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can see all assets that are assigned to their asset groups",
+            "target" : {
+                "name" : "When an operator reads all assets",
+                "resource" : {
+                    "name" : "Assets",
+                    "uriTemplate" : "/assets"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Deny all other operations by default",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/controller-test/complete-sample-policy-set.json b/service/src/test/resources/controller-test/complete-sample-policy-set.json
new file mode 100644
index 0000000..bbc1180
--- /dev/null
+++ b/service/src/test/resources/controller-test/complete-sample-policy-set.json
@@ -0,0 +1,158 @@
+{
+    "name" : "test-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), 'sanramon')" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read an asset if they are a member of the asset group.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can modify a site if they are assigned to the site and they are a manager.",
+            "target" : {
+                "name" : "When an operator modifies a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "PUT",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "role" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uriVariable('site_id'))" },
+                { "name" : "is a manager",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'role'), 'manager')" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can create a case if they own the alarm and they are members of the asset group.",
+            "target" : {
+                "name" : "When an operator creates an case for an asset alarm",
+                "resource" : {
+                    "name" : "Asset alarm cases",
+                    "uriTemplate" : "/assets/{asset_id}/alarms/{alarm_id}/cases",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "owner" }
+                    ]
+                },
+                "action" : "POST",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "name_id" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" },
+                { "name" : "is owner of",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'name_id'), resource.attributes('https://acs.attributes.int', 'owner'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can see all sites that they are assigned to",
+            "target" : {
+                "name" : "When an operator reads all sites",
+                "resource" : {
+                    "name" : "Sites",
+                    "uriTemplate" : "/sites"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can see all assets that are assigned to their asset groups",
+            "target" : {
+                "name" : "When an operator reads all assets",
+                "resource" : {
+                    "name" : "Assets",
+                    "uriTemplate" : "/assets"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Deny all other operations by default",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/controller-test/createAttributeConnector.json b/service/src/test/resources/controller-test/createAttributeConnector.json
new file mode 100644
index 0000000..66faf73
--- /dev/null
+++ b/service/src/test/resources/controller-test/createAttributeConnector.json
@@ -0,0 +1,12 @@
+{
+     "isActive":"true",
+     "maxCachedIntervalMinutes":"60",
+     "adapters": [
+        {
+        "adapterEndpoint":"https://my-adapter.com",
+        "uaaTokenUrl":"https://my-uaa.com",
+        "uaaClientId":"adapter-client",
+        "uaaClientSecret":"adapter-secret"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/controller-test/createAttributeConnectorWithEmptyAdapters.json b/service/src/test/resources/controller-test/createAttributeConnectorWithEmptyAdapters.json
new file mode 100644
index 0000000..0f4bbb8
--- /dev/null
+++ b/service/src/test/resources/controller-test/createAttributeConnectorWithEmptyAdapters.json
@@ -0,0 +1,5 @@
+{
+     "isActive":"true",
+     "maxCachedIntervalMinutes":"60",
+     "adapters": []
+}
\ No newline at end of file
diff --git a/service/src/test/resources/controller-test/createAttributeConnectorWithLowValueForCache.json b/service/src/test/resources/controller-test/createAttributeConnectorWithLowValueForCache.json
new file mode 100644
index 0000000..6f83cfa
--- /dev/null
+++ b/service/src/test/resources/controller-test/createAttributeConnectorWithLowValueForCache.json
@@ -0,0 +1,12 @@
+{
+     "isActive":"true",
+     "maxCachedIntervalMinutes":"10",
+     "adapters": [
+        {
+        "adapterEndpoint":"https://my-adapter.com",
+        "uaaTokenUrl":"https://my-uaa.com",
+        "uaaClientId":"adapter-client",
+        "uaaClientSecret":"adapter-secret"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/controller-test/createAttributeConnectorWithTwoAdapters.json b/service/src/test/resources/controller-test/createAttributeConnectorWithTwoAdapters.json
new file mode 100644
index 0000000..24319cd
--- /dev/null
+++ b/service/src/test/resources/controller-test/createAttributeConnectorWithTwoAdapters.json
@@ -0,0 +1,18 @@
+{
+     "isActive":"true",
+     "maxCachedIntervalMinutes":"60",
+     "adapters": [
+        {
+        "adapterEndpoint":"https://my-adapter.com",
+        "uaaTokenUrl":"https://my-uaa.com",
+        "uaaClientId":"adapter-client",
+        "uaaClientSecret":"adapter-secret"
+        },
+        {
+        "adapterEndpoint":"https://my-adapter2.com",
+        "uaaTokenUrl":"https://my-uaa2.com",
+        "uaaClientId":"adapter-client2",
+        "uaaClientSecret":"adapter-secret2"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/controller-test/createZone.json b/service/src/test/resources/controller-test/createZone.json
new file mode 100644
index 0000000..5823d05
--- /dev/null
+++ b/service/src/test/resources/controller-test/createZone.json
@@ -0,0 +1,4 @@
+{
+    "name" : "zone-1",
+    "subdomain" : "subdomain-1"
+}
\ No newline at end of file
diff --git a/service/src/test/resources/controller-test/createZoneTwo.json b/service/src/test/resources/controller-test/createZoneTwo.json
new file mode 100644
index 0000000..2c59ef2
--- /dev/null
+++ b/service/src/test/resources/controller-test/createZoneTwo.json
@@ -0,0 +1,4 @@
+{
+    "name" : "zone-2",
+    "subdomain" : "subdomain-1"
+}
\ No newline at end of file
diff --git a/service/src/test/resources/controller-test/empty-identifier-resources-collection.json b/service/src/test/resources/controller-test/empty-identifier-resources-collection.json
new file mode 100644
index 0000000..484589e
--- /dev/null
+++ b/service/src/test/resources/controller-test/empty-identifier-resources-collection.json
@@ -0,0 +1,17 @@
+[
+	{
+		"resourceIdentifier" : " ",
+		"resourceId" : "R5",
+		"attributes" : [
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "group",
+		      "value": "admin" 
+		    }
+		    ,
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "department",
+		       "value": "sales" 
+		    }
+		]
+	}
+]
diff --git a/service/src/test/resources/controller-test/empty-identifier-subjects-collection.json b/service/src/test/resources/controller-test/empty-identifier-subjects-collection.json
new file mode 100644
index 0000000..86fc146
--- /dev/null
+++ b/service/src/test/resources/controller-test/empty-identifier-subjects-collection.json
@@ -0,0 +1,16 @@
+[
+	{
+		"subjectIdentifier" : " ",
+		"attributes" : [
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "group",
+		      "value": "admin" 
+		    }
+		    ,
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "department",
+		       "value": "sales" 
+		    }
+		]
+	}
+]
diff --git a/service/src/test/resources/controller-test/missing-identifier-resources-collection.json b/service/src/test/resources/controller-test/missing-identifier-resources-collection.json
new file mode 100644
index 0000000..f7a26bf
--- /dev/null
+++ b/service/src/test/resources/controller-test/missing-identifier-resources-collection.json
@@ -0,0 +1,16 @@
+[
+	{
+		"resourceId" : "secured-api",
+		"attributes" : [
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "group",
+		      "value": "admin" 
+		    }
+		    ,
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "department",
+		       "value": "sales" 
+		    }
+		]
+	}
+]
diff --git a/service/src/test/resources/controller-test/missing-identifier-subjects-collection.json b/service/src/test/resources/controller-test/missing-identifier-subjects-collection.json
new file mode 100644
index 0000000..9307148
--- /dev/null
+++ b/service/src/test/resources/controller-test/missing-identifier-subjects-collection.json
@@ -0,0 +1,16 @@
+[
+	{
+		"uri" : "/tenant/default/subject/S5",
+		"attributes" : [
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "group",
+		      "value": "admin" 
+		    }
+		    ,
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "department",
+		       "value": "sales" 
+		    }
+		]
+	}
+]
diff --git a/service/src/test/resources/controller-test/missing-resourceIdentifier-resources-collection.json b/service/src/test/resources/controller-test/missing-resourceIdentifier-resources-collection.json
new file mode 100644
index 0000000..ba7aa0f
--- /dev/null
+++ b/service/src/test/resources/controller-test/missing-resourceIdentifier-resources-collection.json
@@ -0,0 +1,15 @@
+[
+	{
+		"attributes" : [
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "group",
+		      "value": "admin" 
+		    }
+		    ,
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "department",
+		       "value": "sales" 
+		    }
+		]
+	}
+]
diff --git a/service/src/test/resources/controller-test/missing-subjectidentifier-collection.json b/service/src/test/resources/controller-test/missing-subjectidentifier-collection.json
new file mode 100644
index 0000000..ba7aa0f
--- /dev/null
+++ b/service/src/test/resources/controller-test/missing-subjectidentifier-collection.json
@@ -0,0 +1,15 @@
+[
+	{
+		"attributes" : [
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "group",
+		      "value": "admin" 
+		    }
+		    ,
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "department",
+		       "value": "sales" 
+		    }
+		]
+	}
+]
diff --git a/service/src/test/resources/controller-test/multiple-policy-set-test.json b/service/src/test/resources/controller-test/multiple-policy-set-test.json
new file mode 100644
index 0000000..6da4104
--- /dev/null
+++ b/service/src/test/resources/controller-test/multiple-policy-set-test.json
@@ -0,0 +1,8 @@
+{
+    "name" : "multiple-policy-set-test",
+    "policies" : [
+        {
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/controller-test/no-resourceIdentifier-resource.json b/service/src/test/resources/controller-test/no-resourceIdentifier-resource.json
new file mode 100644
index 0000000..9595a59
--- /dev/null
+++ b/service/src/test/resources/controller-test/no-resourceIdentifier-resource.json
@@ -0,0 +1,13 @@
+{
+	"attributes" : [
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "group",
+	      "value": "supervisor" 
+	    }
+	    ,
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "department",
+	       "value": "it" 
+	    }
+	]
+}
diff --git a/service/src/test/resources/controller-test/no-subjectidentifier-subject.json b/service/src/test/resources/controller-test/no-subjectidentifier-subject.json
new file mode 100644
index 0000000..9c18b3e
--- /dev/null
+++ b/service/src/test/resources/controller-test/no-subjectidentifier-subject.json
@@ -0,0 +1,13 @@
+{
+	"attributes" : [
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "group",
+	      "value": "admin" 
+	    }
+	    ,
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "department",
+	       "value": "sales" 
+	    }
+	]
+}
diff --git a/service/src/test/resources/controller-test/resources-collection.json b/service/src/test/resources/controller-test/resources-collection.json
new file mode 100644
index 0000000..92f3874
--- /dev/null
+++ b/service/src/test/resources/controller-test/resources-collection.json
@@ -0,0 +1,33 @@
+[
+	{
+		"resourceIdentifier" : "/services/secured-api",
+		"resourceId" : "0192837410239874",
+		"attributes" : [
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "group",
+		      "value": "admin" 
+		    }
+		    ,
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "department",
+		       "value": "sales" 
+		    }
+		]
+	}
+	,
+	{
+		"resourceIdentifier" : "/services/reports/01928374102398741235123",
+		"resourceId" : "01928374102398741235123",
+		"attributes" : [
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "group",
+		      "value": "admin" 
+		    }
+		    ,
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "department",
+		       "value": "sales" 
+		    }
+		]
+	}
+]
diff --git a/service/src/test/resources/controller-test/simple-policy-set-empty-name.json b/service/src/test/resources/controller-test/simple-policy-set-empty-name.json
new file mode 100644
index 0000000..ff38342
--- /dev/null
+++ b/service/src/test/resources/controller-test/simple-policy-set-empty-name.json
@@ -0,0 +1,9 @@
+{
+    "name" : "",
+    "policies" : [
+        {
+            "name" : "deny-everything",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/controller-test/simple-policy-set-no-name.json b/service/src/test/resources/controller-test/simple-policy-set-no-name.json
new file mode 100644
index 0000000..89946ef
--- /dev/null
+++ b/service/src/test/resources/controller-test/simple-policy-set-no-name.json
@@ -0,0 +1,8 @@
+{
+    "policies" : [
+        {
+            "name" : "deny-everything",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/controller-test/special-character-resource-identifier.json b/service/src/test/resources/controller-test/special-character-resource-identifier.json
new file mode 100644
index 0000000..c32d0a3
--- /dev/null
+++ b/service/src/test/resources/controller-test/special-character-resource-identifier.json
@@ -0,0 +1,14 @@
+{
+	"resourceIdentifier" : "/services/special/@#!$*&()_-+=[]:;{}'~`,",
+	"attributes" : [
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "group",
+	      "value": "supervisor" 
+	    }
+	    ,
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "department",
+	       "value": "it" 
+	    }
+	]
+}
diff --git a/service/src/test/resources/controller-test/subjects-collection-with-different-attributes.json b/service/src/test/resources/controller-test/subjects-collection-with-different-attributes.json
new file mode 100644
index 0000000..bc93dbb
--- /dev/null
+++ b/service/src/test/resources/controller-test/subjects-collection-with-different-attributes.json
@@ -0,0 +1,31 @@
+[
+	{
+		"subjectIdentifier" : "dave",
+		"attributes" : [
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "group",
+		      "value": "different" 
+		    }
+		    ,
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "department",
+		       "value": "sales" 
+		    }
+		]
+	}
+	,
+	{
+		"subjectIdentifier" : "vineet",
+		"attributes" : [
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "group",
+		      "value": "different" 
+		    }
+		    ,
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "department",
+		       "value": "sales" 
+		    }
+		]
+	}
+]
diff --git a/service/src/test/resources/controller-test/subjects-collection.json b/service/src/test/resources/controller-test/subjects-collection.json
new file mode 100644
index 0000000..d2aa0fc
--- /dev/null
+++ b/service/src/test/resources/controller-test/subjects-collection.json
@@ -0,0 +1,31 @@
+[
+	{
+		"subjectIdentifier" : "dave",
+		"attributes" : [
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "group",
+		      "value": "admin" 
+		    }
+		    ,
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "department",
+		       "value": "sales" 
+		    }
+		]
+	}
+	,
+	{
+		"subjectIdentifier" : "vineet",
+		"attributes" : [
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "group",
+		      "value": "admin" 
+		    }
+		    ,
+		    { "issuer" : "https://acs.attributes.int",
+		      "name" : "department",
+		       "value": "sales" 
+		    }
+		]
+	}
+]
diff --git a/service/src/test/resources/controller-test/updateZone.json b/service/src/test/resources/controller-test/updateZone.json
new file mode 100644
index 0000000..b65751d
--- /dev/null
+++ b/service/src/test/resources/controller-test/updateZone.json
@@ -0,0 +1,4 @@
+{
+    "name" : "zone-1",
+    "subdomain" : "subdomain-2"
+}
\ No newline at end of file
diff --git a/service/src/test/resources/controller-test/with-resourceIdentifier-resource.json b/service/src/test/resources/controller-test/with-resourceIdentifier-resource.json
new file mode 100644
index 0000000..c50e979
--- /dev/null
+++ b/service/src/test/resources/controller-test/with-resourceIdentifier-resource.json
@@ -0,0 +1,14 @@
+{
+	"resourceIdentifier" : "/services/secured-api/subresource",
+	"attributes" : [
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "group",
+	      "value": "supervisor" 
+	    }
+	    ,
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "department",
+	       "value": "it" 
+	    }
+	]
+}
diff --git a/service/src/test/resources/controller-test/with-subjectidentifier-subject.json b/service/src/test/resources/controller-test/with-subjectidentifier-subject.json
new file mode 100644
index 0000000..d630779
--- /dev/null
+++ b/service/src/test/resources/controller-test/with-subjectidentifier-subject.json
@@ -0,0 +1,14 @@
+{
+	"subjectIdentifier" : "fermin",
+	"attributes" : [
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "group",
+	      "value": "admin" 
+	    }
+	    ,
+	    { "issuer" : "https://acs.attributes.int",
+	      "name" : "department",
+	       "value": "sales" 
+	    }
+	]
+}
diff --git a/service/src/test/resources/controller-test/zone-with-no-subdomain.json b/service/src/test/resources/controller-test/zone-with-no-subdomain.json
new file mode 100644
index 0000000..8d91cef
--- /dev/null
+++ b/service/src/test/resources/controller-test/zone-with-no-subdomain.json
@@ -0,0 +1,4 @@
+{
+	"name" : "zone-3",
+	"subdomain" : ""
+}
\ No newline at end of file
diff --git a/service/src/test/resources/controller-tests-context.xml b/service/src/test/resources/controller-tests-context.xml
new file mode 100644
index 0000000..af8f5d5
--- /dev/null
+++ b/service/src/test/resources/controller-tests-context.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:context="http://www.springframework.org/schema/context"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
+
+    <context:component-scan base-package="com.ge.predix.acs" />
+<!--     <bean
+        class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
+        <property name="order" value="-1"></property>
+        <property name="urlPathHelper">
+            <bean class="com.ge.predix.acs.config.UrlPathHelperNonDecoding"></bean>
+        </property>
+    </bean> -->
+
+    <bean
+        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
+        xmlns="http://www.springframework.org/schema/beans">
+        <property name="locations">
+            <list>
+                <value>classpath:application.properties</value>
+            </list>
+        </property>
+    </bean>
+</beans>
diff --git a/service/src/test/resources/db/migration/V0__service_instance.sql b/service/src/test/resources/db/migration/V0__service_instance.sql
new file mode 100644
index 0000000..4d0888f
--- /dev/null
+++ b/service/src/test/resources/db/migration/V0__service_instance.sql
@@ -0,0 +1,45 @@
+CREATE TABLE `policy_set` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `client_id` varchar(128) NOT NULL,
+  `issuer_id` varchar(128) NOT NULL,
+  `policy_set_id` varchar(128) NOT NULL,
+  `policy_set_json` MEDIUMTEXT NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+CREATE TABLE `resource` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `issuer_id` int(18) NOT NULL,
+  `client_id` varchar(128) NOT NULL,
+  `resource_identifier` varchar(128) NOT NULL,
+  `resource_id` varchar(128) NOT NULL,
+  `attributes` MEDIUMTEXT NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+CREATE TABLE `subject` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `issuer_id` int(18) NOT NULL,
+  `client_id` varchar(128) NOT NULL,
+  `subject_identifier` varchar(128) NOT NULL,
+  `subject_id` varchar(128) NOT NULL,
+  `attributes` MEDIUMTEXT NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+CREATE TABLE `issuer` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `issuer_id` varchar(128) NOT NULL,
+  `issuer_check_token_url` varchar(128) NOT NULL,
+  PRIMARY KEY (`id`)  
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+ALTER TABLE policy_set ADD CONSTRAINT unique_issuer_client_pset UNIQUE (issuer_id, client_id, policy_set_id);
+
+ALTER TABLE issuer ADD CONSTRAINT unique_issuer_id UNIQUE (issuer_id);
+
+ALTER TABLE resource ADD CONSTRAINT unique_issuer_client_resource_identifier UNIQUE (issuer_id, client_id, resource_identifier);
+ALTER TABLE resource ADD CONSTRAINT unique_issuer_client_resource_id UNIQUE (issuer_id, client_id, resource_id);
+
+ALTER TABLE subject ADD CONSTRAINT unique_issuer_client_subject_id UNIQUE (issuer_id, client_id, subject_id);
+ALTER TABLE subject ADD CONSTRAINT unique_issuer_client_subject_identifier UNIQUE (issuer_id, client_id, subject_identifier);
\ No newline at end of file
diff --git a/service/src/test/resources/db/migration/V1__drop_subject_resource_id_constraints_columns.sql b/service/src/test/resources/db/migration/V1__drop_subject_resource_id_constraints_columns.sql
new file mode 100644
index 0000000..5662680
--- /dev/null
+++ b/service/src/test/resources/db/migration/V1__drop_subject_resource_id_constraints_columns.sql
@@ -0,0 +1,4 @@
+ALTER TABLE resource DROP CONSTRAINT unique_issuer_client_resource_id;
+ALTER TABLE resource DROP COLUMN resource_id;
+ALTER TABLE subject DROP CONSTRAINT unique_issuer_client_subject_id;
+ALTER TABLE subject DROP COLUMN subject_id;
\ No newline at end of file
diff --git a/service/src/test/resources/db/migration/V2_0_0__create_authz_zones.sql b/service/src/test/resources/db/migration/V2_0_0__create_authz_zones.sql
new file mode 100644
index 0000000..5d3612b
--- /dev/null
+++ b/service/src/test/resources/db/migration/V2_0_0__create_authz_zones.sql
@@ -0,0 +1,22 @@
+CREATE TABLE `authorization_zone` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) NOT NULL,
+  `description` varchar(1024) NOT NULL,
+  `subdomain` varchar(255) NOT NULL,
+  PRIMARY KEY(`id`),
+  UNIQUE KEY `name` (`name`),
+  UNIQUE KEY `subdomain` (`subdomain`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+CREATE TABLE `authorization_zone_client` (
+  `id` int(18) NOT NULL AUTO_INCREMENT,
+  `issuer_id` int(18) NOT NULL,
+  `client_id` varchar(255) NOT NULL,
+  `authorization_zone_id` int(18) NOT NULL,
+  PRIMARY KEY(`id`),
+  UNIQUE KEY `client_in_zone` (`issuer_id`,`client_id`,`authorization_zone_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+ALTER TABLE subject ADD COLUMN authorization_zone_id int(18) DEFAULT 0;
+ALTER TABLE resource ADD COLUMN authorization_zone_id int(18) DEFAULT 0;
+ALTER TABLE policy_set ADD COLUMN authorization_zone_id int(18) DEFAULT 0;
\ No newline at end of file
diff --git a/service/src/test/resources/db/migration/V2_0_2__PostInitializeIdentityZones.sql b/service/src/test/resources/db/migration/V2_0_2__PostInitializeIdentityZones.sql
new file mode 100644
index 0000000..3cac453
--- /dev/null
+++ b/service/src/test/resources/db/migration/V2_0_2__PostInitializeIdentityZones.sql
@@ -0,0 +1,21 @@
+
+ALTER TABLE resource DROP CONSTRAINT unique_issuer_client_resource_identifier;
+ALTER TABLE resource DROP COLUMN issuer_id;
+ALTER TABLE resource DROP COLUMN client_id;
+
+ALTER TABLE subject DROP CONSTRAINT unique_issuer_client_subject_identifier;
+ALTER TABLE subject DROP COLUMN issuer_id;
+ALTER TABLE subject DROP COLUMN client_id;
+
+ALTER TABLE policy_set DROP CONSTRAINT unique_issuer_client_pset;
+ALTER TABLE policy_set DROP COLUMN issuer_id;
+ALTER TABLE policy_set DROP COLUMN client_id;
+
+ALTER TABLE resource ADD CONSTRAINT unique_zid_resource_identifier UNIQUE (authorization_zone_id, resource_identifier);
+ALTER TABLE subject ADD CONSTRAINT unique_zid_subject_identifier UNIQUE (authorization_zone_id, subject_identifier);
+ALTER TABLE policy_set ADD CONSTRAINT unique_zid_pset UNIQUE (authorization_zone_id, policy_set_id);
+
+ALTER TABLE resource ADD FOREIGN KEY (authorization_zone_id) REFERENCES authorization_zone(id) ON DELETE CASCADE;
+ALTER TABLE subject ADD FOREIGN KEY (authorization_zone_id) REFERENCES authorization_zone(id) ON DELETE CASCADE;
+ALTER TABLE policy_set ADD FOREIGN KEY (authorization_zone_id) REFERENCES authorization_zone(id) ON DELETE CASCADE;
+ALTER TABLE authorization_zone_client ADD FOREIGN KEY (authorization_zone_id) REFERENCES authorization_zone(id) ON DELETE CASCADE;
\ No newline at end of file
diff --git a/service/src/test/resources/db/migration/V2_0_3__add_zone_issuer_table.sql b/service/src/test/resources/db/migration/V2_0_3__add_zone_issuer_table.sql
new file mode 100644
index 0000000..7b5b806
--- /dev/null
+++ b/service/src/test/resources/db/migration/V2_0_3__add_zone_issuer_table.sql
@@ -0,0 +1,13 @@
+CREATE TABLE `zone_issuer` (
+  `issuer_id` int(18) NOT NULL,
+  `zone_id` int(18) NOT NULL,
+  PRIMARY KEY(`issuer_id`, `zone_id`),
+  FOREIGN KEY (zone_id) REFERENCES authorization_zone(id) ON DELETE CASCADE,
+  FOREIGN KEY (issuer_id) REFERENCES issuer(id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
+
+
+ALTER TABLE authorization_zone_client ADD FOREIGN KEY (issuer_id) REFERENCES issuer(id) ON DELETE CASCADE;
+ALTER TABLE issuer ADD CONSTRAINT unique_issuer_check_token_url UNIQUE (issuer_check_token_url);
+
+ALTER TABLE issuer ALTER column issuer_check_token_url varchar(1024) NOT NULL;
diff --git a/service/src/test/resources/db/migration/V2_0_5__remove_zone_client_and_issuer_tables.sql b/service/src/test/resources/db/migration/V2_0_5__remove_zone_client_and_issuer_tables.sql
new file mode 100644
index 0000000..064670f
--- /dev/null
+++ b/service/src/test/resources/db/migration/V2_0_5__remove_zone_client_and_issuer_tables.sql
@@ -0,0 +1,3 @@
+DROP TABLE zone_issuer;
+DROP TABLE authorization_zone_client;
+DROP TABLE issuer;
diff --git a/service/src/test/resources/db/migration/V3_0_1__alter_attribute_connectors.sql b/service/src/test/resources/db/migration/V3_0_1__alter_attribute_connectors.sql
new file mode 100644
index 0000000..06af8c7
--- /dev/null
+++ b/service/src/test/resources/db/migration/V3_0_1__alter_attribute_connectors.sql
@@ -0,0 +1,2 @@
+ALTER TABLE authorization_zone ADD resource_attribute_connector_json MEDIUMTEXT NULL;
+ALTER TABLE authorization_zone ADD subject_attribute_connector_json MEDIUMTEXT NULL;
diff --git a/service/src/test/resources/policies/policySetWithOverlappingURIs.json b/service/src/test/resources/policies/policySetWithOverlappingURIs.json
new file mode 100644
index 0000000..edfd291
--- /dev/null
+++ b/service/src/test/resources/policies/policySetWithOverlappingURIs.json
@@ -0,0 +1,29 @@
+{
+    "name" : "alarmsPolicySet",
+    "policies" : [
+        {
+            "name" : "assetAlarmsPolicy",
+            "target" : {
+                "name" : "getAssetAlarms",
+                "resource" : {
+                    "name" : "site",
+                    "uriTemplate" : "/alarm/site/{site_id}/asset/{asset_id}"
+                 }
+             },
+            "effect" : "PERMIT"
+        }
+        ,
+        {
+            "name" : "siteAlarmsPolicy",
+            "target" : {
+                "name" : "getSiteAlarms",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/alarm/site/{site_id}"
+                }
+            },
+            "effect" : "DENY"
+        }
+
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policies/singlePolicyNoCondition.json b/service/src/test/resources/policies/singlePolicyNoCondition.json
new file mode 100644
index 0000000..9031b92
--- /dev/null
+++ b/service/src/test/resources/policies/singlePolicyNoCondition.json
@@ -0,0 +1,16 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "resourceURIPolicy",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                }
+             },
+            "effect" : "PERMIT"
+        }
+   ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policies/testApmPolicySetLoadsSuccessfully.json b/service/src/test/resources/policies/testApmPolicySetLoadsSuccessfully.json
new file mode 100644
index 0000000..ab7196b
--- /dev/null
+++ b/service/src/test/resources/policies/testApmPolicySetLoadsSuccessfully.json
@@ -0,0 +1,2025 @@
+{  
+   "name":"PolicySet-for-APM",
+   "policies":[  
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/alarms/{uuid}"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/asset/tz/timeZone/{assetId}"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/config/{version}/tenants/{tenantId}/components/{componentName}"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/faulttolerance/create"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profiles"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profiles"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profiles"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profiles"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profiles"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileSources"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileSources"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileSources"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileSources"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileSources"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileTypes"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileTypes"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileTypes"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileTypes"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileTypes"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/tags"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/tags"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/tags"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/tags"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/tags"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/templates"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/templates"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/templates"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/templates"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/templates"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/ss/v0/analytics"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/ss/v0/analytics/{assetUUID}"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/ss/v0/blueprints"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/timeseries/data"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/timeseries/smartsignaldata"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/{version}/umgmt/users/me"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/acs/{anySubPath}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/acs/{anySubPath}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/acs/{anySubPath}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/acs/{anySubPath}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/pgs/asset/incremental/upload"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/pgs/asset/upload"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/pgs/assetType/upload"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/pgs/om/alarm/create"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/pgs/ss/alarm/create"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/task/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/tasks"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/alarms"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmreports/{version}/timeseries/digest"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmreports/{version}/timeseries/init"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/alarmprofiles/profile/profileByName"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/alarmtemplates/getTemplatesForAlarm"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/assetType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/assetType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/assetType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterpriseType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterpriseType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterpriseType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segmentType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segmentType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segmentType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/siteType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/siteType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/siteType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/algorithm"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/algorithm/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/algorithm/associate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/tag/associate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/tag/disassociate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}/tag/associate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}/tag/disassociate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}/tag/associate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}/tag/disassociate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/tag"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/tag"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/tag/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/blobstorage/attachments"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/alarms"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/alarms/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/v2/alarms"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/v2/alarms"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmreports/{version}/reports"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"services/apm/{version}/alarmtemplates/views/getViews"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/alarmtemplates/getTemplateName"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/alarmtemplates/templates"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/alarmtemplates/views/getViews"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetbrowser/asset"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/bySourceKey"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/children"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/parent"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/assetType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise/{uuid}/sites"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprises"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterpriseType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterpriseType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}/children"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}/parent"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segmentType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segmentType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}/segments"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/siteType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/siteType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/measurementtags"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/tags"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/timeseries/{uuid}/{startTime}/{endTime}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/blobstorage/attachments"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/blobstorage/attachments"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/blobstorage/attachments/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/blobstorage/attachments/{uuid}/download"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/casemanagement/cases"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/casemanagement/cases"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/casemanagement/cases/{caseuuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/notemanagement/notes"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/notemanagement/notes"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/notemanagement/notes/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/gasturbineperformance/assetpair"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/gasturbineperformance/{page}/currentperformance"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/gasturbineperformance/{page}/timeseries/{startTimeMillis}/{endTimeMillis}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/heatexchanger/assetpair"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/heatexchanger/{phase}/currentperformance"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/heatexchanger/{phase}/timeseries/{startTimeMillis}/{endTimeMillis}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/processcompressor/assetpair"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/processcompressor/{page}/currentperformance"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/processcompressor/{page}/timeseries/{startTimeMillis}/{endTimeMillis}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/processsurveillance/kpi"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/processsurveillance/tier2/pages"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/propanephase/historical/{phase}/{startTimeMillis}/{endTimeMillis}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/{version}/umgmt/users"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/{version}/umgmt/users/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/{version}/umgmt/users/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/assetType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager,' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "resource":{  
+               "uriTemplate":"/services/{anySubPath:.+}"
+            },
+            "action":"GET"
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'ReadOnly' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "name":"Deny all operations by default",
+         "effect":"DENY"
+      }
+   ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policies/testMatchPolicyFailureBecauseMissingResourceAttribute.json b/service/src/test/resources/policies/testMatchPolicyFailureBecauseMissingResourceAttribute.json
new file mode 100644
index 0000000..efefa21
--- /dev/null
+++ b/service/src/test/resources/policies/testMatchPolicyFailureBecauseMissingResourceAttribute.json
@@ -0,0 +1,32 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher MUST NOT match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policies/testMatchPolicyUriCanonicalization.json b/service/src/test/resources/policies/testMatchPolicyUriCanonicalization.json
new file mode 100644
index 0000000..4226adc
--- /dev/null
+++ b/service/src/test/resources/policies/testMatchPolicyUriCanonicalization.json
@@ -0,0 +1,29 @@
+{
+    "name" : "URI Canonicalization Test",
+    "policies" : [
+        {
+            "name" : "Policy for allowed actions.",
+            "target" : {
+                "name" : "When an operator attempts an allowed action",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/allowed/{.*}"
+                },
+                "action" : "GET"
+            },
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Policy for not allowed actions.",
+            "target" : {
+                "name" : "When an operator attempts a not allowed action",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/not_allowed/{.*}"
+                },
+                "action" : "GET"
+            },
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policies/testMatchPolicyWithMultipleActions.json b/service/src/test/resources/policies/testMatchPolicyWithMultipleActions.json
new file mode 100644
index 0000000..996a288
--- /dev/null
+++ b/service/src/test/resources/policies/testMatchPolicyWithMultipleActions.json
@@ -0,0 +1,24 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher MUST match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}"
+                },
+                "action" : "GET,POST, DELETE",
+                "subject" : {
+                    "name" : "Operator"
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policies/testMatchPolicyWithMultipleActionsNoMatch.json b/service/src/test/resources/policies/testMatchPolicyWithMultipleActionsNoMatch.json
new file mode 100644
index 0000000..4c0aa68
--- /dev/null
+++ b/service/src/test/resources/policies/testMatchPolicyWithMultipleActionsNoMatch.json
@@ -0,0 +1,24 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher MUST match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}"
+                },
+                "action" : "POST, PUT",
+                "subject" : {
+                    "name" : "Operator"
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policies/testMatchPolicyWithNoResource.json b/service/src/test/resources/policies/testMatchPolicyWithNoResource.json
new file mode 100644
index 0000000..631de77
--- /dev/null
+++ b/service/src/test/resources/policies/testMatchPolicyWithNoResource.json
@@ -0,0 +1,50 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher MUST NOT match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "PolicyMatcher MUST match this policy during this test.",
+            "target" : {
+                "name" : "When an administrator reads something",
+                "action" : "GET",
+                "subject" : {
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "role" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is an administrator",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'role'),'admin')" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policies/testMatchPolicyWithNoSubject.json b/service/src/test/resources/policies/testMatchPolicyWithNoSubject.json
new file mode 100644
index 0000000..f7e875e
--- /dev/null
+++ b/service/src/test/resources/policies/testMatchPolicyWithNoSubject.json
@@ -0,0 +1,44 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher MUST NOT match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "PolicyMatcher MUST match this policy during this test.",
+            "target" : {
+                "name" : "When anyone reads public assets",
+                "resource" : {
+                    "name" : "Assets",
+                    "uriTemplate" : "/public"
+                },
+                "action" : "GET"
+            },
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policies/testMatchPolicyWithNoTarget.json b/service/src/test/resources/policies/testMatchPolicyWithNoTarget.json
new file mode 100644
index 0000000..47b252e
--- /dev/null
+++ b/service/src/test/resources/policies/testMatchPolicyWithNoTarget.json
@@ -0,0 +1,36 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher MUST NOT match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "PolicyMatcher MUST match this policy during this test.",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policies/testMatchPolicyWithNoTargetAction.json b/service/src/test/resources/policies/testMatchPolicyWithNoTargetAction.json
new file mode 100644
index 0000000..b656302
--- /dev/null
+++ b/service/src/test/resources/policies/testMatchPolicyWithNoTargetAction.json
@@ -0,0 +1,35 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher MUST NOT match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                },
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "PolicyMatcher MUST match this policy during this test.",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policies/testMatchPolicyWithSubjectAndResourceAttributeRequirements.json b/service/src/test/resources/policies/testMatchPolicyWithSubjectAndResourceAttributeRequirements.json
new file mode 100644
index 0000000..730cfd9
--- /dev/null
+++ b/service/src/test/resources/policies/testMatchPolicyWithSubjectAndResourceAttributeRequirements.json
@@ -0,0 +1,59 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher MUST NOT match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "mojo" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "mojo" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "PolicyMatcher MUST match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policies/testMatchPolicyWithUriTemplateAndSubjectAttribute.json b/service/src/test/resources/policies/testMatchPolicyWithUriTemplateAndSubjectAttribute.json
new file mode 100644
index 0000000..dd06fcd
--- /dev/null
+++ b/service/src/test/resources/policies/testMatchPolicyWithUriTemplateAndSubjectAttribute.json
@@ -0,0 +1,55 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher MUST NOT match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "PolicyMatcher MUST match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uri.placeHolder('site_id'))" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policies/testMultiplePoliciesNoMatch.json b/service/src/test/resources/policies/testMultiplePoliciesNoMatch.json
new file mode 100644
index 0000000..06d0ca2
--- /dev/null
+++ b/service/src/test/resources/policies/testMultiplePoliciesNoMatch.json
@@ -0,0 +1,153 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher MUST NOT match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uri.placeHolder('site_id'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "PolicyMatcher MUST NOT match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "PolicyMatcher MUST NOT match this policy during this test.",
+            "target" : {
+                "name" : "When an operator modifies a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "PUT",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "role" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), resource.uri.placeHolder('site_id'))" },
+                { "name" : "is a manager",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'role'), 'manager')" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "PolicyMatcher MUST NOT match this policy during this test.",
+            "target" : {
+                "name" : "When an operator creates an case for an asset alarm",
+                "resource" : {
+                    "name" : "Asset alarm cases",
+                    "uriTemplate" : "/assets/{asset_id}/alarms/{alarm_id}/cases",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "owner" }
+                    ]
+                },
+                "action" : "POST",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "name_id" },
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" },
+                { "name" : "is owner of",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'name_id'), resource.attributes('https://acs.attributes.int', 'owner'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "PolicyMatcher MUST NOT match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads all sites",
+                "resource" : {
+                    "name" : "Sites",
+                    "uriTemplate" : "/sites"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "PolicyMatcher MUST NOT match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads all assets",
+                "resource" : {
+                    "name" : "Assets",
+                    "uriTemplate" : "/assets"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "group" }
+                    ]
+                }
+            },
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policies/testPolicyEvalDeny.json b/service/src/test/resources/policies/testPolicyEvalDeny.json
new file mode 100644
index 0000000..f03cdb6
--- /dev/null
+++ b/service/src/test/resources/policies/testPolicyEvalDeny.json
@@ -0,0 +1,9 @@
+{
+    "name" : "deny-policy-set",
+    "policies" : [
+        {
+            "name" : "This policy should always return DENY.",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policies/testPolicyEvalNotApplicable.json b/service/src/test/resources/policies/testPolicyEvalNotApplicable.json
new file mode 100644
index 0000000..1dd8183
--- /dev/null
+++ b/service/src/test/resources/policies/testPolicyEvalNotApplicable.json
@@ -0,0 +1,13 @@
+{
+    "name" : "not-applicable-policy-set",
+    "policies" : [
+        {
+            "name" : "This policy should not match during test.",
+            "conditions" : [
+                { "name" : "is always false",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policy-set-with-attribute-uri-template.json b/service/src/test/resources/policy-set-with-attribute-uri-template.json
new file mode 100644
index 0000000..8aab3d2
--- /dev/null
+++ b/service/src/test/resources/policy-set-with-attribute-uri-template.json
@@ -0,0 +1,25 @@
+{
+    "name" : "policy-set-with-uri-attribute-template",
+    "policies" : [
+        {
+            "target" : {
+                "resource" : {
+                    "uriTemplate" : "/v1/site/{site_id}/asset/{asset-id}",
+                    "attributeUriTemplate": "/v1{attribute_uri}/asset/{asset-id}",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "role" }
+                    ]
+                },
+                "action" : "GET"
+            },
+            "effect" : "PERMIT"
+        }
+        
+        ,
+        
+        {
+            "effect" : "DENY"
+        }
+    ]
+}
diff --git a/service/src/test/resources/policy-set-with-multiple-policies-default-deny-with-condition.json b/service/src/test/resources/policy-set-with-multiple-policies-default-deny-with-condition.json
new file mode 100644
index 0000000..94915d7
--- /dev/null
+++ b/service/src/test/resources/policy-set-with-multiple-policies-default-deny-with-condition.json
@@ -0,0 +1,82 @@
+{
+    "name" : "test-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Deny all other operations by default",
+            "effect" : "DENY"
+        }
+        
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policy-set-with-multiple-policies-deny-missing-optional-tags.json b/service/src/test/resources/policy-set-with-multiple-policies-deny-missing-optional-tags.json
new file mode 100644
index 0000000..a70f0c2
--- /dev/null
+++ b/service/src/test/resources/policy-set-with-multiple-policies-deny-missing-optional-tags.json
@@ -0,0 +1,109 @@
+{
+    "name" : "test-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET"
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "PERMIT"
+        },
+        {
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET"
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Deny all other operations by default",
+            "effect" : "DENY"
+        }
+        
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policy-set-with-multiple-policies-deny-with-condition.json b/service/src/test/resources/policy-set-with-multiple-policies-deny-with-condition.json
new file mode 100644
index 0000000..592e6e6
--- /dev/null
+++ b/service/src/test/resources/policy-set-with-multiple-policies-deny-with-condition.json
@@ -0,0 +1,78 @@
+{
+    "name" : "test-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('sanramon')" }
+            ],
+            
+            "effect" : "DENY"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('sanramon')" }
+            ],
+            
+            "effect" : "PERMIT"
+        }
+        
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policy-set-with-multiple-policies-first-match.json b/service/src/test/resources/policy-set-with-multiple-policies-first-match.json
new file mode 100644
index 0000000..bfbb7b5
--- /dev/null
+++ b/service/src/test/resources/policy-set-with-multiple-policies-first-match.json
@@ -0,0 +1,45 @@
+{
+    "name" : "test-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            
+            "effect" : "DENY"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policy-set-with-multiple-policies-na-with-condition.json b/service/src/test/resources/policy-set-with-multiple-policies-na-with-condition.json
new file mode 100644
index 0000000..6cee5ae
--- /dev/null
+++ b/service/src/test/resources/policy-set-with-multiple-policies-na-with-condition.json
@@ -0,0 +1,78 @@
+{
+    "name" : "test-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "DENY"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "PERMIT"
+        }
+        
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policy-set-with-multiple-policies-permit-with-condition.json b/service/src/test/resources/policy-set-with-multiple-policies-permit-with-condition.json
new file mode 100644
index 0000000..064f639
--- /dev/null
+++ b/service/src/test/resources/policy-set-with-multiple-policies-permit-with-condition.json
@@ -0,0 +1,53 @@
+{
+    "name" : "test-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('ny')" }
+            ],
+            
+            "effect" : "DENY"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('sanramon')" }
+            ],
+            
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policy-set-with-one-policy-invalid-condition.json b/service/src/test/resources/policy-set-with-one-policy-invalid-condition.json
new file mode 100644
index 0000000..6c7cb7b
--- /dev/null
+++ b/service/src/test/resources/policy-set-with-one-policy-invalid-condition.json
@@ -0,0 +1,28 @@
+{
+    "name" : "test-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "System.exit(0)" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policy-set-with-one-policy-nocondition.json b/service/src/test/resources/policy-set-with-one-policy-nocondition.json
new file mode 100644
index 0000000..142b479
--- /dev/null
+++ b/service/src/test/resources/policy-set-with-one-policy-nocondition.json
@@ -0,0 +1,25 @@
+{
+    "name" : "policy-set-with-one-policy-nocondition",
+    "policies" : [
+        {
+            "name" : "Operators assigned to a site are denied access to resources from any site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policy-set-with-one-policy-one-condition-indeterminate.json b/service/src/test/resources/policy-set-with-one-policy-one-condition-indeterminate.json
new file mode 100644
index 0000000..e7b277d
--- /dev/null
+++ b/service/src/test/resources/policy-set-with-one-policy-one-condition-indeterminate.json
@@ -0,0 +1,28 @@
+{
+    "name" : "test-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                   "condition" : "System.out.println('something')" } 
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policy-set-with-one-policy-one-condition-using-attributes.json b/service/src/test/resources/policy-set-with-one-policy-one-condition-using-attributes.json
new file mode 100644
index 0000000..0ce4497
--- /dev/null
+++ b/service/src/test/resources/policy-set-with-one-policy-one-condition-using-attributes.json
@@ -0,0 +1,30 @@
+{
+    "name" : "policy-set-with-one-policy-one-condition-using-attributes",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they have appropriate role.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "role" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "has role analyst",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'role'), 'analyst')" },
+                { "name" : "has role administrator",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'role'), 'administrator')" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policy-set-with-one-policy-one-condition-using-res-attributes.json b/service/src/test/resources/policy-set-with-one-policy-one-condition-using-res-attributes.json
new file mode 100644
index 0000000..6086e8f
--- /dev/null
+++ b/service/src/test/resources/policy-set-with-one-policy-one-condition-using-res-attributes.json
@@ -0,0 +1,30 @@
+{
+    "name" : "test-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "role" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "has role analyst",
+                  "condition" : "match.single(resource.attributes('https://acs.attributes.int', 'location'), 'sanramon')" },
+                { "name" : "has role administrator",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'role'), resource.attributes('https://acs.attributes.int', 'role_required'))" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policy-set-with-one-policy-one-condition.json b/service/src/test/resources/policy-set-with-one-policy-one-condition.json
new file mode 100644
index 0000000..18db444
--- /dev/null
+++ b/service/src/test/resources/policy-set-with-one-policy-one-condition.json
@@ -0,0 +1,28 @@
+{
+    "name" : "policy-set-with-one-policy-one-condition",
+    "policies" : [
+        {
+            "name" : "Operators assigned to a site can read a resource from any site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Operator",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "'sanramon'.equals('sanramon')" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/empty-attributes-target-subject.json b/service/src/test/resources/policyset/validator/test/empty-attributes-target-subject.json
new file mode 100644
index 0000000..1d6ba83
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/empty-attributes-target-subject.json
@@ -0,0 +1,28 @@
+{
+    "name" : "set-with-1-policy",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/sanramon"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "subject1",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "subject.attributes('https://acs.attributes.int', 'site')=='sanramon'" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/empty-conditions-policy.json b/service/src/test/resources/policyset/validator/test/empty-conditions-policy.json
new file mode 100644
index 0000000..a33bebe
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/empty-conditions-policy.json
@@ -0,0 +1,25 @@
+{
+    "name" : "set-with-1-policy",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/sanramon"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "subject1",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/empty-policies-policy-set.json b/service/src/test/resources/policyset/validator/test/empty-policies-policy-set.json
new file mode 100644
index 0000000..118eb86
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/empty-policies-policy-set.json
@@ -0,0 +1,4 @@
+{
+    "name" : "set-with-1-policy",
+    "policies" : []
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/missing-condition-name-policy.json b/service/src/test/resources/policyset/validator/test/missing-condition-name-policy.json
new file mode 100644
index 0000000..e95052f
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/missing-condition-name-policy.json
@@ -0,0 +1,28 @@
+{
+    "name" : "set-with-1-policy",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/sanramon"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "subject1",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { 
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), 'sanramon')" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/missing-condition-policy-condition.json b/service/src/test/resources/policyset/validator/test/missing-condition-policy-condition.json
new file mode 100644
index 0000000..75f3468
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/missing-condition-policy-condition.json
@@ -0,0 +1,28 @@
+{
+    "name" : "set-with-1-policy",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/sanramon"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "subject1",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site"
+                }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/missing-effect-policy.json b/service/src/test/resources/policyset/validator/test/missing-effect-policy.json
new file mode 100644
index 0000000..6bde63e
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/missing-effect-policy.json
@@ -0,0 +1,27 @@
+{
+    "name" : "set-with-1-policy",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/sanramon"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "subject1",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), 'sanramon')" }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/missing-name-policy-set.json b/service/src/test/resources/policyset/validator/test/missing-name-policy-set.json
new file mode 100644
index 0000000..bdfdd62
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/missing-name-policy-set.json
@@ -0,0 +1,27 @@
+{
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/sanramon"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "subject1",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), 'sanramon')" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/missing-resource-policy-target.json b/service/src/test/resources/policyset/validator/test/missing-resource-policy-target.json
new file mode 100644
index 0000000..897ae8a
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/missing-resource-policy-target.json
@@ -0,0 +1,24 @@
+{
+    "name" : "set-with-1-policy",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "action" : "GET",
+                "subject" : {
+                    "name" : "subject1",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "subject.attributes('https://acs.attributes.int', 'site')=='sanramon'" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/missing-target-name-policy.json b/service/src/test/resources/policyset/validator/test/missing-target-name-policy.json
new file mode 100644
index 0000000..0b75308
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/missing-target-name-policy.json
@@ -0,0 +1,28 @@
+{
+    "name" : "set-with-1-policy",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/sanramon"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "subject1",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { 
+                  "name" : "condition name",
+                  "condition" : "subject.attributes('https://acs.attributes.int', 'site')=='sanramon'" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/missing-uritemplate-policy-resource.json b/service/src/test/resources/policyset/validator/test/missing-uritemplate-policy-resource.json
new file mode 100644
index 0000000..ab001a2
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/missing-uritemplate-policy-resource.json
@@ -0,0 +1,27 @@
+{
+    "name" : "set-with-1-policy",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "subject1",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), 'sanramon')" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/multiple-policies-with-multiple-conditions.json b/service/src/test/resources/policyset/validator/test/multiple-policies-with-multiple-conditions.json
new file mode 100644
index 0000000..b89d89e
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/multiple-policies-with-multiple-conditions.json
@@ -0,0 +1,61 @@
+{
+    "name" : "test-policy-set",
+    "policies" : [
+        {
+            "name" : "Agents can access a site if they are stationed at the site.",
+            "target" : {
+                "name" : "When an agent accesses a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Agent",
+                    "attributes" : [
+                        { "issuer" : "acs.example.org",
+                            "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                    "condition" : "match.single(subject.attributes('acs.example.org', 'site'), resource.uriVariable('site_id'))" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Agents can access evidence if they are a member of the evidence group and have the right clearance.",
+            "target" : {
+                "name" : "When an agent accesses evidence",
+                "resource" : {
+                    "name" : "Evidence",
+                    "uriTemplate" : "/evidence/{evidence_id}",
+                    "attributes" : [
+                        { "issuer" : "acs.example.org",
+                            "name" : "group" }
+                    ]
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "Agent",
+                    "attributes" : [
+                        { "issuer" : "acs.example.org",
+                            "name" : "group" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                    "condition" : "resource.and(subject).haveSame('acs.example.org', 'group').result()" },
+                { "name" : "has clearance",
+                    "condition" : "resource.and(subject).haveSame('acs.example.org', 'classification').result()" }
+            ],
+            "effect" : "PERMIT"
+        },
+        {
+            "name" : "Deny all other operations by default",
+            "effect" : "DENY"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/no-policies-policy-set.json b/service/src/test/resources/policyset/validator/test/no-policies-policy-set.json
new file mode 100644
index 0000000..2b2cd35
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/no-policies-policy-set.json
@@ -0,0 +1,3 @@
+{
+    "name" : "set-with-1-policy"
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/policy-set-with-only-name-effect.json b/service/src/test/resources/policyset/validator/test/policy-set-with-only-name-effect.json
new file mode 100644
index 0000000..457f9c2
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/policy-set-with-only-name-effect.json
@@ -0,0 +1,8 @@
+{
+    "name": "my-policy-set",
+    "policies" : [
+        {
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/policyWithEmptyTargetAction.json b/service/src/test/resources/policyset/validator/test/policyWithEmptyTargetAction.json
new file mode 100644
index 0000000..3be5e28
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/policyWithEmptyTargetAction.json
@@ -0,0 +1,24 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher MUST match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}"
+                },
+                "action" : "    ",
+                "subject" : {
+                    "name" : "Operator"
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/policyWithNullTargetAction.json b/service/src/test/resources/policyset/validator/test/policyWithNullTargetAction.json
new file mode 100644
index 0000000..f75a717
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/policyWithNullTargetAction.json
@@ -0,0 +1,24 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher MUST match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}"
+                },
+                "action" : null,
+                "subject" : {
+                    "name" : "Operator"
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/testMatchPolicyWithInvalidAction.json b/service/src/test/resources/policyset/validator/test/testMatchPolicyWithInvalidAction.json
new file mode 100644
index 0000000..c7ad6ba
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/testMatchPolicyWithInvalidAction.json
@@ -0,0 +1,24 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher MUST match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}"
+                },
+                "action" : "GET1",
+                "subject" : {
+                    "name" : "Operator"
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/testMatchPolicyWithMultipleActions.json b/service/src/test/resources/policyset/validator/test/testMatchPolicyWithMultipleActions.json
new file mode 100644
index 0000000..bfc55c4
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/testMatchPolicyWithMultipleActions.json
@@ -0,0 +1,24 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher MUST match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}"
+                },
+                "action" : "GET,POST, DELETE, SUBSCRIBE, MESSAGE",
+                "subject" : {
+                    "name" : "Operator"
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/policyset/validator/test/testMatchPolicyWithMultipleActionsOneInvalid.json b/service/src/test/resources/policyset/validator/test/testMatchPolicyWithMultipleActionsOneInvalid.json
new file mode 100644
index 0000000..8658ce5
--- /dev/null
+++ b/service/src/test/resources/policyset/validator/test/testMatchPolicyWithMultipleActionsOneInvalid.json
@@ -0,0 +1,24 @@
+{
+    "name" : "Operator policy set",
+    "policies" : [
+        {
+            "name" : "PolicyMatcher MUST match this policy during this test.",
+            "target" : {
+                "name" : "When an operator reads an asset",
+                "resource" : {
+                    "name" : "Asset",
+                    "uriTemplate" : "/assets/{asset_id}"
+                },
+                "action" : "GET,POST, DELETE,PATCH,PUT1",
+                "subject" : {
+                    "name" : "Operator"
+                }
+            },
+            "conditions" : [
+                { "name" : "is member of group",
+                  "condition" : "match.any(subject.attributes('https://acs.attributes.int', 'group'), resource.attributes('https://acs.attributes.int', 'group'))" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/set-with-1-policy.json b/service/src/test/resources/set-with-1-policy.json
new file mode 100644
index 0000000..00b4f5f
--- /dev/null
+++ b/service/src/test/resources/set-with-1-policy.json
@@ -0,0 +1,28 @@
+{
+    "name" : "set-with-1-policy",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When an operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/sanramon"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name" : "subject1",
+                    "attributes" : [
+                        { "issuer" : "https://acs.attributes.int",
+                          "name" : "site" }
+                    ]
+                }
+            },
+            "conditions" : [
+                { "name" : "is assigned to site",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), 'sanramon')" }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/set-with-2-policy.json b/service/src/test/resources/set-with-2-policy.json
new file mode 100644
index 0000000..f30780c
--- /dev/null
+++ b/service/src/test/resources/set-with-2-policy.json
@@ -0,0 +1,47 @@
+{
+    "name" : "multiple-site-test-policy-set",
+    "policies" : [
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When a operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name":""
+                }
+            },
+            "conditions" : [
+                { 
+                  "name":"",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), 'sanramon')" 
+                }
+            ],
+            "effect" : "DENY"
+        },
+        {
+            "name" : "Operators can read a site if they are assigned to the site.",
+            "target" : {
+                "name" : "When a operator reads a site",
+                "resource" : {
+                    "name" : "Site",
+                    "uriTemplate" : "/secured-by-value/sites/{site_id}"
+                },
+                "action" : "GET",
+                "subject" : {
+                    "name":""
+                }                
+            },
+            "conditions" : [
+                { 
+                  "name":"",
+                  "condition" : "match.single(subject.attributes('https://acs.attributes.int', 'site'), 'sanramon')" 
+                }
+            ],
+            "effect" : "PERMIT"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/set-with-no-policy.json b/service/src/test/resources/set-with-no-policy.json
new file mode 100644
index 0000000..2b2cd35
--- /dev/null
+++ b/service/src/test/resources/set-with-no-policy.json
@@ -0,0 +1,3 @@
+{
+    "name" : "set-with-1-policy"
+}
\ No newline at end of file
diff --git a/service/src/test/resources/testApmPolicySetLoadsSuccessfully.json b/service/src/test/resources/testApmPolicySetLoadsSuccessfully.json
new file mode 100644
index 0000000..ab7196b
--- /dev/null
+++ b/service/src/test/resources/testApmPolicySetLoadsSuccessfully.json
@@ -0,0 +1,2025 @@
+{  
+   "name":"PolicySet-for-APM",
+   "policies":[  
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/alarms/{uuid}"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/asset/tz/timeZone/{assetId}"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/config/{version}/tenants/{tenantId}/components/{componentName}"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/faulttolerance/create"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profiles"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profiles"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profiles"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profiles"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profiles"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileSources"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileSources"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileSources"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileSources"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileSources"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileTypes"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileTypes"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileTypes"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileTypes"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/profileTypes"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/tags"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/tags"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/tags"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/tags"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/tags"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/templates"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/templates"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/templates"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/templates"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/apm/profileService/{version}/templates"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/ss/v0/analytics"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/ss/v0/analytics/{assetUUID}"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/ss/v0/blueprints"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/timeseries/data"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/timeseries/smartsignaldata"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/{version}/umgmt/users/me"
+            }
+         },
+         "conditions":[  
+
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/acs/{anySubPath}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/acs/{anySubPath}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/acs/{anySubPath}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/acs/{anySubPath}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/pgs/asset/incremental/upload"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/pgs/asset/upload"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/pgs/assetType/upload"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/pgs/om/alarm/create"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/pgs/ss/alarm/create"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/task/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/adapter/tasks"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/alarms"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmreports/{version}/timeseries/digest"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmreports/{version}/timeseries/init"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/alarmprofiles/profile/profileByName"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/alarmtemplates/getTemplatesForAlarm"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/assetType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/assetType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/assetType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterpriseType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterpriseType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterpriseType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segmentType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segmentType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segmentType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/siteType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/siteType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/siteType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/algorithm"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/algorithm/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/algorithm/associate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/tag/associate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/tag/disassociate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}/tag/associate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}/tag/disassociate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}/tag/associate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}/tag/disassociate"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/tag"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/tag"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/tag/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/blobstorage/attachments"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'Ingestor' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/alarms"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/alarms/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/v2/alarms"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmmanagement/v2/alarms"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/alarmreports/{version}/reports"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"services/apm/{version}/alarmtemplates/views/getViews"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/alarmtemplates/getTemplateName"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/alarmtemplates/templates"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/{version}/alarmtemplates/views/getViews"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetbrowser/asset"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/bySourceKey"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/children"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/parent"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/assetType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprise/{uuid}/sites"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterprises"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterpriseType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/enterpriseType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}/children"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segment/{uuid}/parent"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segmentType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/segmentType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/site/{uuid}/segments"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/siteType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/siteType/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/measurementtags"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/asset/{uuid}/tags"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/timeseries/{uuid}/{startTime}/{endTime}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/blobstorage/attachments"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/blobstorage/attachments"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/blobstorage/attachments/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/blobstorage/attachments/{uuid}/download"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/casemanagement/cases"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PUT",
+            "resource":{  
+               "uriTemplate":"/services/apm/casemanagement/cases"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/casemanagement/cases/{caseuuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"POST",
+            "resource":{  
+               "uriTemplate":"/services/apm/notemanagement/notes"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/notemanagement/notes"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"DELETE",
+            "resource":{  
+               "uriTemplate":"/services/apm/notemanagement/notes/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/gasturbineperformance/assetpair"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/gasturbineperformance/{page}/currentperformance"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/gasturbineperformance/{page}/timeseries/{startTimeMillis}/{endTimeMillis}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/heatexchanger/assetpair"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/heatexchanger/{phase}/currentperformance"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/heatexchanger/{phase}/timeseries/{startTimeMillis}/{endTimeMillis}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/processcompressor/assetpair"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/processcompressor/{page}/currentperformance"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/processcompressor/{page}/timeseries/{startTimeMillis}/{endTimeMillis}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/processsurveillance/kpi"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/processsurveillance/tier2/pages"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/pgsanalytics/propanephase/historical/{phase}/{startTimeMillis}/{endTimeMillis}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/{version}/umgmt/users"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/{version}/umgmt/users/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"PATCH",
+            "resource":{  
+               "uriTemplate":"/services/{version}/umgmt/users/{uuid}"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "action":"GET",
+            "resource":{  
+               "uriTemplate":"/services/apm/assetregistry/assetType"
+            }
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'M&D Manager,' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "target":{  
+            "resource":{  
+               "uriTemplate":"/services/{anySubPath:.+}"
+            },
+            "action":"GET"
+         },
+         "conditions":[  
+            {  
+               "condition":"subject.attributes('https://acs.apm.ge.com', 'role')*.split(':').any { sa -> sa.size() > 0 & sa[0] == 'ReadOnly' }"
+            }
+         ],
+         "effect":"PERMIT"
+      },
+      {  
+         "name":"Deny all operations by default",
+         "effect":"DENY"
+      }
+   ]
+}
\ No newline at end of file
diff --git a/service/src/test/resources/unit-tests.xml b/service/src/test/resources/unit-tests.xml
new file mode 100644
index 0000000..98951d8
--- /dev/null
+++ b/service/src/test/resources/unit-tests.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ - Copyright 2017 General Electric Company
+ -
+ - 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.
+ -
+ - SPDX-License-Identifier: Apache-2.0
+ -->
+
+<!DOCTYPE xml>
+<suite name="unit-tests">
+    <test name="unit-test">
+        <packages>
+            <package name="com.ge.predix.acs.*" />
+        </packages>
+    </test>
+</suite>
diff --git a/service/start-acs-postgres.sh b/service/start-acs-postgres.sh
new file mode 100755
index 0000000..aff6f2c
--- /dev/null
+++ b/service/start-acs-postgres.sh
@@ -0,0 +1,36 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+#!/usr/bin/env bash
+
+# This script will run ACS against a local PostgreSQL database. To setup the database:
+# 1. Install PostgreSQL
+# 2. Open psql
+# 3. execute: 'create database acs;'
+# 4. execute: 'create user postgres;'
+# 5. execute: 'grant all privileges on database acs to postgres;'
+
+if [[ -z "$SPRING_PROFILES_ACTIVE" ]]; then
+    export SPRING_PROFILES_ACTIVE='envDbConfig,public,simple-cache'
+fi
+export DB_DRIVER_CLASS_NAME='org.postgresql.Driver'
+export DB_URL='jdbc:postgresql:acs'
+export DB_USERNAME='postgres'
+export DB_PASSWORD='postgres'
+export DIR=$( dirname "$( python -c "import os; print os.path.abspath('${BASH_SOURCE[0]}')" )" )
+source "${DIR}/start-acs.sh" "$@"
diff --git a/service/start-acs-public-titan.sh b/service/start-acs-public-titan.sh
new file mode 100755
index 0000000..0bd9813
--- /dev/null
+++ b/service/start-acs-public-titan.sh
@@ -0,0 +1,27 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+#!/usr/bin/env bash
+
+unset PROXY_OPTS
+
+if [[ -z "$SPRING_PROFILES_ACTIVE" ]]; then
+    export SPRING_PROFILES_ACTIVE='h2,public,simple-cache,titan'
+fi
+export DIR=$( dirname "$( python -c "import os; print os.path.abspath('${BASH_SOURCE[0]}')" )" )
+source "${DIR}/start-acs.sh" "$@"
diff --git a/service/start-acs-public.sh b/service/start-acs-public.sh
new file mode 100755
index 0000000..07e4e77
--- /dev/null
+++ b/service/start-acs-public.sh
@@ -0,0 +1,27 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+#!/usr/bin/env bash
+
+unset PROXY_OPTS
+
+if [[ -z "$SPRING_PROFILES_ACTIVE" ]]; then
+    export SPRING_PROFILES_ACTIVE='h2,public,simple-cache'
+fi
+export DIR=$( dirname "$( python -c "import os; print os.path.abspath('${BASH_SOURCE[0]}')" )" )
+source "${DIR}/start-acs.sh" "$@"
diff --git a/service/start-acs.sh b/service/start-acs.sh
new file mode 100755
index 0000000..13e141a
--- /dev/null
+++ b/service/start-acs.sh
@@ -0,0 +1,62 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+#!/usr/bin/env bash
+
+echo "JAVA_LOCAL_OPTS: ${JAVA_LOCAL_OPTS}"
+
+HTTP_VALIDATION_SPRING_PROFILE='httpValidation'
+if [[ -z "$SPRING_PROFILES_ACTIVE" ]]; then
+    export SPRING_PROFILES_ACTIVE="$HTTP_VALIDATION_SPRING_PROFILE"
+elif [[ "$SPRING_PROFILES_ACTIVE" != *"$HTTP_VALIDATION_SPRING_PROFILE"* ]]; then
+    export SPRING_PROFILES_ACTIVE="${SPRING_PROFILES_ACTIVE},${HTTP_VALIDATION_SPRING_PROFILE}"
+fi
+echo "SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}"
+
+unset PORT_OFFSET
+source ./set-env-local.sh
+
+export DIR=$( dirname "$( python -c "import os; print os.path.abspath('${BASH_SOURCE[0]}')" )" )
+
+if [ "$#" -eq 0 ]; then
+    unset JAVA_DEBUG_OPTS
+    unset LOGGING_OPTS
+fi
+
+main() {
+    while [ "$1" != '' ]; do
+        case $1 in
+            'debug')
+                JAVA_DEBUG_OPTS='-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005'
+                shift
+                ;;
+            'human-readable-logging')
+                LOGGING_OPTS="-Dlog4j.debug -Dlog4j.configuration=file:${DIR}/src/main/resources/log4j-dev.xml"
+                shift
+                ;;
+            *)
+                break
+                ;;
+        esac
+    done
+
+    cp "${DIR}"/target/acs-service-*-exec.jar "${DIR}"/.acs-service-copy.jar
+    java -Xms1g -Xmx1g $JAVA_LOCAL_OPTS $JAVA_DEBUG_OPTS $LOGGING_OPTS $PROXY_OPTS -jar "${DIR}"/.acs-service-copy.jar
+}
+
+main "$@"
diff --git a/set-env-local.sh b/set-env-local.sh
new file mode 100755
index 0000000..420f1e5
--- /dev/null
+++ b/set-env-local.sh
@@ -0,0 +1,54 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+#!/usr/bin/env bash
+
+export PORT_OFFSET=${PORT_OFFSET:-0}
+export UAA_LOCAL_PORT=$(( 8080 + ${PORT_OFFSET} ))
+export ACS_LOCAL_PORT=$(( 8181 + ${PORT_OFFSET} ))
+export ZAC_LOCAL_PORT=$(( 8888 + ${PORT_OFFSET} ))
+
+export ACS_URL="http://localhost:${ACS_LOCAL_PORT}"
+export ZAC_URL="http://localhost:${ZAC_LOCAL_PORT}"
+export ACS_TESTING_UAA="http://localhost:${UAA_LOCAL_PORT}/uaa"
+
+export ENCRYPTION_KEY=1234567890123456
+
+if [[ "$SPRING_PROFILES_ACTIVE" != *'predix'* ]]; then
+    export ZAC_UAA_URL="http://localhost:${UAA_LOCAL_PORT}/uaa"
+    export ACS_UAA_URL="http://localhost:${UAA_LOCAL_PORT}/uaa"
+fi
+
+python <<EOF
+import re, shutil
+
+input_file = './acs-integration-tests/uaa/config/uaa.yml'
+output_file = input_file + '.tmp'
+
+input = open(input_file)
+output = open(output_file, 'w')
+
+regex = re.compile(r'localhost:\d+/uaa')
+for line in input.xreadlines():
+    output.write(regex.sub('localhost:${UAA_LOCAL_PORT}/uaa', line))
+
+input.close()
+output.close()
+
+shutil.move(output_file, input_file)
+EOF
diff --git a/versioning.sh b/versioning.sh
new file mode 100755
index 0000000..2a668b8
--- /dev/null
+++ b/versioning.sh
@@ -0,0 +1,46 @@
+################################################################################
+# Copyright 2017 General Electric Company
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+#!/usr/bin/env bash
+
+set -ex
+
+if [[ -z "$1" ]]; then
+    echo "Please provide the version to set in all POM files"
+    exit 2
+fi
+
+XMLSTARLET_VERSION='1.6.1'
+XMLSTARLET_DIRNAME="xmlstarlet-${XMLSTARLET_VERSION}"
+XMLSTARLET_ARCHIVE_NAME="${XMLSTARLET_DIRNAME}.tar.gz"
+if [[ ! -f "${XMLSTARLET_DIRNAME}/xml" ]]; then
+    curl -OL "https://downloads.sourceforge.net/project/xmlstar/xmlstarlet/${XMLSTARLET_VERSION}/${XMLSTARLET_ARCHIVE_NAME}"
+    tar -xvzf "${XMLSTARLET_ARCHIVE_NAME}"
+    cd "$XMLSTARLET_DIRNAME"
+    ./configure && make
+else
+    cd "$XMLSTARLET_DIRNAME"
+fi
+
+./xml ed -P -L -N x='http://maven.apache.org/POM/4.0.0' -u '/x:project/x:version' -v "$1" '../pom.xml'
+
+for f in 'commons/pom.xml' 'model/pom.xml' 'service/pom.xml' 'acs-integration-tests/pom.xml'; do
+    ./xml ed -P -L -N x='http://maven.apache.org/POM/4.0.0' -u '/x:project/x:parent/x:version' -v "$1" "../${f}"
+done
+
+./xml ed -P -L -N x='http://maven.apache.org/POM/4.0.0' -u '/x:project/x:dependencies/x:dependency[x:artifactId="acs-service"]/x:version' -v "$1" '../acs-integration-tests/pom.xml'