You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metron.apache.org by rm...@apache.org on 2017/09/29 01:01:50 UTC

metron git commit: METRON-1085 Add REST endpoint to save a user profile for the Alerts UI (merrimanr) closes apache/metron#694

Repository: metron
Updated Branches:
  refs/heads/master e2de1caa4 -> e5d876371


METRON-1085 Add REST endpoint to save a user profile for the Alerts UI (merrimanr) closes apache/metron#694


Project: http://git-wip-us.apache.org/repos/asf/metron/repo
Commit: http://git-wip-us.apache.org/repos/asf/metron/commit/e5d87637
Tree: http://git-wip-us.apache.org/repos/asf/metron/tree/e5d87637
Diff: http://git-wip-us.apache.org/repos/asf/metron/diff/e5d87637

Branch: refs/heads/master
Commit: e5d876371e523294598bfdb80a9332fa8917a08f
Parents: e2de1ca
Author: merrimanr <me...@gmail.com>
Authored: Thu Sep 28 20:01:36 2017 -0500
Committer: merrimanr <me...@apache.org>
Committed: Thu Sep 28 20:01:36 2017 -0500

----------------------------------------------------------------------
 dependencies_with_url.csv                       |   8 +
 metron-interface/metron-rest-client/pom.xml     |  11 +
 .../metron/rest/converter/JsonConverter.java    |  53 ++++
 .../apache/metron/rest/model/AlertProfile.java  |  88 ++++++
 .../apache/metron/rest/model/SavedSearch.java   |  66 +++++
 metron-interface/metron-rest/README.md          |  39 ++-
 metron-interface/metron-rest/pom.xml            | 121 +++++++++
 .../apache/metron/rest/MetronRestConstants.java |   3 +
 .../metron/rest/config/JpaConfiguration.java    |  51 ++++
 .../metron/rest/config/WebSecurityConfig.java   |  13 +-
 .../metron/rest/controller/AlertController.java | 119 +++++++++
 .../rest/controller/AlertsController.java       |  55 ----
 .../rest/repository/AlertProfileRepository.java |  25 ++
 .../metron/rest/security/SecurityUtils.java     |  40 +++
 .../metron/rest/service/AlertService.java       |   9 +
 .../rest/service/AlertsProfileService.java      |  32 +++
 .../rest/service/impl/AlertServiceImpl.java     |  37 ++-
 .../service/impl/AlertsProfileServiceImpl.java  |  66 +++++
 .../src/main/resources/application.yml          |   2 +
 .../src/main/resources/log4j.properties         |   2 +-
 .../AlertControllerIntegrationTest.java         | 265 ++++++++++++++++++-
 .../rest/service/impl/AlertServiceImplTest.java |  85 +++++-
 .../indexing/dao/search/SearchRequest.java      |  30 +++
 23 files changed, 1153 insertions(+), 67 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/dependencies_with_url.csv
----------------------------------------------------------------------
diff --git a/dependencies_with_url.csv b/dependencies_with_url.csv
index b95f25b..f022647 100644
--- a/dependencies_with_url.csv
+++ b/dependencies_with_url.csv
@@ -113,6 +113,7 @@ com.fasterxml.jackson.core:jackson-core:jar:2.6.6:compile,ASLv2,https://github.c
 com.fasterxml.jackson.core:jackson-core:jar:2.7.4:compile,ASLv2,https://github.com/FasterXML/jackson-core
 com.fasterxml.jackson.core:jackson-core:jar:2.8.3:compile,ASLv2,https://github.com/FasterXML/jackson-core
 com.fasterxml.jackson.core:jackson-databind:jar:2.2.3:compile,ASLv2,http://wiki.fasterxml.com/JacksonHome
+com.fasterxml.jackson.core:jackson-databind:jar:2.4.3:compile,ASLv2,http://github.com/FasterXML/jackson
 com.fasterxml.jackson.core:jackson-databind:jar:2.7.4:compile,ASLv2,http://github.com/FasterXML/jackson
 com.fasterxml.jackson.core:jackson-databind:jar:2.8.3:compile,ASLv2,http://github.com/FasterXML/jackson
 com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:jar:2.6.6:compile,ASLv2,http://wiki.fasterxml.com/JacksonForCbor
@@ -309,3 +310,10 @@ org.springframework.security.kerberos:spring-security-kerberos-core:jar:1.0.1.RE
 org.springframework.kafka:spring-kafka:jar:1.1.1.RELEASE:compile,ASLv2,https://github.com/spring-projects/spring-kafka
 ch.hsr:geohash:jar:1.3.0:compile,ASLv2,https://github.com/kungfoo/geohash-java
 org.locationtech.spatial4j:spatial4j:jar:0.6:compile,ASLv2,https://github.com/locationtech/spatial4j
+org.glassfish:javax.json:jar:1.0.4:compile,Common Development and Distribution License (CDDL) v1.0,https://github.com/javaee/jsonp
+org.eclipse.persistence:javax.persistence:jar:2.1.1:compile,EPL 1.0,http://www.eclipse.org/eclipselink
+org.eclipse.persistence:org.eclipse.persistence.antlr:jar:2.6.4:compile,EPL 1.0,http://www.eclipse.org/eclipselink
+org.eclipse.persistence:org.eclipse.persistence.asm:jar:2.6.4:compile,EPL 1.0,http://www.eclipse.org/eclipselink
+org.eclipse.persistence:org.eclipse.persistence.core:jar:2.6.4:compile,EPL 1.0,http://www.eclipse.org/eclipselink
+org.eclipse.persistence:org.eclipse.persistence.jpa.jpql:jar:2.6.4:compile,EPL 1.0,http://www.eclipse.org/eclipselink
+org.eclipse.persistence:org.eclipse.persistence.jpa:jar:2.6.4:compile,EPL 1.0,http://www.eclipse.org/eclipselink

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest-client/pom.xml
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest-client/pom.xml b/metron-interface/metron-rest-client/pom.xml
index 21e4ffc..95f1bc2 100644
--- a/metron-interface/metron-rest-client/pom.xml
+++ b/metron-interface/metron-rest-client/pom.xml
@@ -23,6 +23,7 @@
     <artifactId>metron-rest-client</artifactId>
     <url>https://metron.apache.org/</url>
     <properties>
+      <eclipse.javax.persistence.version>2.1.1</eclipse.javax.persistence.version>
     </properties>
     <dependencies>
         <dependency>
@@ -36,6 +37,16 @@
                 </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>org.apache.metron</groupId>
+            <artifactId>metron-indexing</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.persistence</groupId>
+            <artifactId>javax.persistence</artifactId>
+            <version>${eclipse.javax.persistence.version}</version>
+        </dependency>
     </dependencies>
 
 </project>

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/converter/JsonConverter.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/converter/JsonConverter.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/converter/JsonConverter.java
new file mode 100644
index 0000000..1059f5c
--- /dev/null
+++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/converter/JsonConverter.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.metron.rest.converter;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import javax.persistence.AttributeConverter;
+import javax.persistence.Converter;
+import org.apache.metron.common.utils.JSONUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Converter
+public class JsonConverter implements AttributeConverter<Object, String> {
+  private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  @Override
+  public String convertToDatabaseColumn(Object savedSearches) {
+    try {
+      return JSONUtils.INSTANCE.toJSON(savedSearches, false);
+    } catch (JsonProcessingException e) {
+      LOG.error("Error converting value to JSON", e);
+    }
+    return null;
+  }
+
+  @Override
+  public Object convertToEntityAttribute(String savedSearches) {
+    try {
+      return JSONUtils.INSTANCE.load(savedSearches, Object.class);
+    } catch (IOException e) {
+      LOG.error("Error converting JSON to value", e);
+    }
+    return null;
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertProfile.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertProfile.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertProfile.java
new file mode 100644
index 0000000..c126ce7
--- /dev/null
+++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/AlertProfile.java
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.metron.rest.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+import javax.persistence.Convert;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import org.apache.metron.rest.converter.JsonConverter;
+
+@Entity
+public class AlertProfile {
+
+  @Id
+  @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+  private String id;
+
+  @Convert(converter = JsonConverter.class)
+  private List<String> tableColumns;
+
+  @Convert(converter = JsonConverter.class)
+  private List<SavedSearch> savedSearches;
+
+  public String getId() {
+    return id;
+  }
+
+  public void setId(String id) {
+    this.id = id;
+  }
+
+  public List<String> getTableColumns() {
+    return tableColumns;
+  }
+
+  public void setTableColumns(List<String> tableColumns) {
+    this.tableColumns = tableColumns;
+  }
+
+  public List<SavedSearch> getSavedSearches() {
+    return savedSearches;
+  }
+
+  public void setSavedSearches(List<SavedSearch> savedSearches) {
+    this.savedSearches = savedSearches;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    AlertProfile that = (AlertProfile) o;
+
+    return id != null ? id.equals(that.id) : that.id == null &&
+        (tableColumns != null ? tableColumns.equals(that.tableColumns) : that.tableColumns == null &&
+        (savedSearches != null ? savedSearches.equals(that.savedSearches) : that.savedSearches == null));
+  }
+
+  @Override
+  public int hashCode() {
+    int result = id != null ? id.hashCode() : 0;
+    result = 31 * result + (tableColumns != null ? tableColumns.hashCode() : 0);
+    result = 31 * result + (savedSearches != null ? savedSearches.hashCode() : 0);
+    return result;
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/SavedSearch.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/SavedSearch.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/SavedSearch.java
new file mode 100644
index 0000000..9b71d06
--- /dev/null
+++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/SavedSearch.java
@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.metron.rest.model;
+
+import org.apache.metron.indexing.dao.search.SearchRequest;
+
+public class SavedSearch {
+
+  private String name;
+  private SearchRequest searchRequest;
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public SearchRequest getSearchRequest() {
+    return searchRequest;
+  }
+
+  public void setSearchRequest(SearchRequest searchRequest) {
+    this.searchRequest = searchRequest;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    SavedSearch that = (SavedSearch) o;
+
+    return name != null ? name.equals(that.name) : that.name == null
+        && (searchRequest != null ? searchRequest.equals(that.searchRequest)
+        : that.searchRequest == null);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = name != null ? name.hashCode() : 0;
+    result = 31 * result + (searchRequest != null ? searchRequest.hashCode() : 0);
+    return result;
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/README.md
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/README.md b/metron-interface/metron-rest/README.md
index 3d8d83b..394f4a8 100644
--- a/metron-interface/metron-rest/README.md
+++ b/metron-interface/metron-rest/README.md
@@ -80,11 +80,12 @@ These are set in the `/etc/sysconfig/metron` file.
 
 ## Database setup
 
-The REST application persists data in a relational database and requires a dedicated database user and database (see https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html for more detail).
+The REST application persists data in a relational database and requires a dedicated database user and database (see https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html for more detail).  
+Spring uses Hibernate as the default ORM framework but another framework is needed becaused Hibernate is not compatible with the Apache 2 license.  For this reason Metron uses [EclipseLink](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-embedded-database-support).  See the [Spring Data JPA - EclipseLink](https://github.com/spring-projects/spring-data-examples/tree/master/jpa/eclipselink) project for an example on how to configure EclipseLink in Spring.
 
 ### Development
 
-The REST application comes with embedded database support for development purposes (https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-embedded-database-support).
+The REST application comes with [embedded database support](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-embedded-database-support) for development purposes.
 
 For example, edit these variables in `/etc/sysconfig/metron` before starting the application to configure H2:
 ```
@@ -196,6 +197,10 @@ Request and Response objects are JSON formatted.  The JSON schemas are available
 |            |
 | ---------- |
 | [ `POST /api/v1/alert/escalate`](#get-apiv1alertescalate)|
+| [ `GET /api/v1/alert/profile`](#get-apiv1alertprofile)|
+| [ `GET /api/v1/alert/profile/all`](#get-apiv1alertprofileall)|
+| [ `DELETE /api/v1/alert/profile`](#delete-apiv1alertprofile)|
+| [ `POST /api/v1/alert/profile`](#post-apiv1alertprofile)|
 | [ `GET /api/v1/global/config`](#get-apiv1globalconfig)|
 | [ `DELETE /api/v1/global/config`](#delete-apiv1globalconfig)|
 | [ `POST /api/v1/global/config`](#post-apiv1globalconfig)|
@@ -269,6 +274,36 @@ Request and Response objects are JSON formatted.  The JSON schemas are available
     * alerts - The alerts to be escalated
   * Returns:
     * 200 - Alerts were escalated
+    
+### `GET /api/v1/alert/profile`
+  * Description: Retrieves the current user's alerts profile
+  * Returns:
+    * 200 - Alerts profile
+    * 404 - The current user does not have an alerts profile
+    
+### `GET /api/v1/alert/profile/all`
+  * Description: Retrieves all users' alerts profiles.  Only users that are part of the "ROLE_ADMIN" role are allowed to get all alerts profiles.
+  * Returns:
+    * 200 - List of all alerts profiles
+    * 403 - The current user does not have permission to get all alerts profiles
+
+### `DELETE /api/v1/alert/profile`
+  * Description: Deletes a user's alerts profile.  Only users that are part of the "ROLE_ADMIN" role are allowed to delete user alerts profiles.
+  * Input:
+    * user - The user whose prolife will be deleted
+  * Returns:
+    * 200 - Alerts profile was deleted
+    * 403 - The current user does not have permission to delete alerts profiles
+    * 404 - Alerts profile could not be found
+
+### `POST /api/v1/alert/profile`
+  * Description: Creates or updates the current user's alerts profile
+  * Input:
+    * alertsProfile - The alerts profile to be saved
+  * Returns:
+    * 200 - Alerts profile updated. Returns saved alerts profile.
+    * 201 - Alerts profile created. Returns saved alerts profile.
+
 
 ### `GET /api/v1/global/config`
   * Description: Retrieves the current Global Config from Zookeeper

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/pom.xml
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/pom.xml b/metron-interface/metron-rest/pom.xml
index 71caecc..dcc3b85 100644
--- a/metron-interface/metron-rest/pom.xml
+++ b/metron-interface/metron-rest/pom.xml
@@ -34,6 +34,8 @@
         <swagger.version>2.5.0</swagger.version>
         <mysql.client.version>5.1.40</mysql.client.version>
         <spring-kafka.version>1.1.1.RELEASE</spring-kafka.version>
+        <spring.version>4.2.2.RELEASE</spring.version>
+        <eclipse.link.version>2.6.4</eclipse.link.version>
     </properties>
     <dependencies>
         <dependency>
@@ -290,6 +292,34 @@
             <artifactId>reflections</artifactId>
             <version>${global_reflections_version}</version>
         </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>
+                <exclusion>
+                    <groupId>org.hibernate</groupId>
+                    <artifactId>hibernate-core</artifactId>
+                </exclusion>
+              <exclusion>
+                <groupId>org.hibernate</groupId>
+                <artifactId>hibernate-commons-annotations</artifactId>
+              </exclusion>
+              <exclusion>
+                <groupId>org.hibernate.javax.persistence</groupId>
+                <artifactId>hibernate-jpa-2.1-api</artifactId>
+              </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.persistence</groupId>
+            <artifactId>org.eclipse.persistence.jpa</artifactId>
+            <version>${eclipse.link.version}</version>
+        </dependency>
     </dependencies>
 
     <dependencyManagement>
@@ -385,4 +415,95 @@
             </plugin>
         </plugins>
     </build>
+
+  <profiles>
+      <profile>
+
+          <!--
+          Sets up the build to run the EclipseLink Maven plugin at compile time and instrument
+          domain types. This will prevent the need for load-time weaving when running the app.
+          -->
+
+          <id>static-weaving</id>
+
+          <build>
+              <plugins>
+
+                <!-- Static weaver for EclipseLink -->
+                <plugin>
+                    <groupId>com.ethlo.persistence.tools</groupId>
+                    <artifactId>eclipselink-maven-plugin</artifactId>
+                    <version>2.6.4.2</version>
+                    <executions>
+                        <execution>
+                            <phase>process-classes</phase>
+                            <goals>
+                                <goal>weave</goal>
+                            </goals>
+                        </execution>
+                    </executions>
+
+                    <dependencies>
+                        <dependency>
+                            <groupId>org.eclipse.persistence</groupId>
+                            <artifactId>org.eclipse.persistence.jpa</artifactId>
+                            <version>${eclipse.link.version}</version>
+                        </dependency>
+                    </dependencies>
+
+                </plugin>
+              </plugins>
+          </build>
+
+          <repositories>
+              <repository>
+                  <id>com.ethlo.eclipselink.tools</id>
+                  <url>http://ethlo.com/maven</url>
+              </repository>
+          </repositories>
+
+          <pluginRepositories>
+              <pluginRepository>
+                  <id>com.ethlo.eclipselink.tools</id>
+                  <url>http://ethlo.com/maven</url>
+              </pluginRepository>
+          </pluginRepositories>
+
+      </profile>
+
+      <profile>
+
+          <!--
+          Sets up the build to use load-time weaving by configuring the Surefire and Boot
+          plugins to run with Spring's instrumentation agent. They will automatically pick
+          up the EclipseLink weaver and instrument domain types at load time.
+          -->
+
+          <id>load-time-weaving</id>
+
+          <activation>
+              <activeByDefault>true</activeByDefault>
+          </activation>
+
+          <build>
+              <plugins>
+                  <plugin>
+                      <groupId>org.apache.maven.plugins</groupId>
+                      <artifactId>maven-surefire-plugin</artifactId>
+                      <dependencies>
+                          <dependency>
+                              <groupId>org.springframework</groupId>
+                              <artifactId>spring-instrument</artifactId>
+                              <version>${spring.version}</version>
+                          </dependency>
+                      </dependencies>
+                      <configuration>
+                          <argLine>-javaagent:${settings.localRepository}/org/springframework/spring-instrument/${spring.version}/spring-instrument-${spring.version}.jar</argLine>
+                      </configuration>
+                  </plugin>
+              </plugins>
+          </build>
+      </profile>
+
+  </profiles>
 </project>

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java
index b0f553f..0bd0ea3 100644
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java
@@ -62,4 +62,7 @@ public class MetronRestConstants {
 
   public static final String META_DAO_IMPL = "meta.dao.impl";
   public static final String META_DAO_SORT = "meta.dao.sort";
+
+  public static final String SECURITY_ROLE_USER = "USER";
+  public static final String SECURITY_ROLE_ADMIN = "ADMIN";
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/JpaConfiguration.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/JpaConfiguration.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/JpaConfiguration.java
new file mode 100644
index 0000000..80c9d1a
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/JpaConfiguration.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.metron.rest.config;
+
+import java.util.Collections;
+import java.util.Map;
+import javax.sql.DataSource;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
+import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
+import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
+import org.springframework.transaction.jta.JtaTransactionManager;
+
+@Configuration
+@EntityScan("org.apache.metron")
+public class JpaConfiguration extends JpaBaseConfiguration {
+
+  protected JpaConfiguration(DataSource dataSource, JpaProperties properties,
+      ObjectProvider<JtaTransactionManager> jtaTransactionManagerProvider) {
+    super(dataSource, properties, jtaTransactionManagerProvider);
+  }
+
+  @Override
+  protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
+    return new EclipseLinkJpaVendorAdapter();
+  }
+
+  @Override
+  protected Map<String, Object> getVendorProperties() {
+    return Collections.singletonMap("eclipselink.weaving", "false");
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java
index 789098a..8310df1 100644
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java
@@ -17,11 +17,15 @@
  */
 package org.apache.metron.rest.config;
 
+import static org.apache.metron.rest.MetronRestConstants.SECURITY_ROLE_ADMIN;
+import static org.apache.metron.rest.MetronRestConstants.SECURITY_ROLE_USER;
+
 import org.apache.metron.rest.MetronRestConstants;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.core.env.Environment;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@@ -36,6 +40,7 @@ import java.util.List;
 
 @Configuration
 @EnableWebSecurity
+@EnableGlobalMethodSecurity(securedEnabled = true)
 @Controller
 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
@@ -82,10 +87,10 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         if (activeProfiles.contains(MetronRestConstants.DEV_PROFILE) ||
                 activeProfiles.contains(MetronRestConstants.TEST_PROFILE)) {
             auth.jdbcAuthentication().dataSource(dataSource)
-                    .withUser("user").password("password").roles("USER").and()
-                    .withUser("user1").password("password").roles("USER").and()
-                    .withUser("user2").password("password").roles("USER").and()
-                    .withUser("admin").password("password").roles("USER", "ADMIN");
+                    .withUser("user").password("password").roles(SECURITY_ROLE_USER).and()
+                    .withUser("user1").password("password").roles(SECURITY_ROLE_USER).and()
+                    .withUser("user2").password("password").roles(SECURITY_ROLE_USER).and()
+                    .withUser("admin").password("password").roles(SECURITY_ROLE_USER, SECURITY_ROLE_ADMIN);
         } else {
             auth.jdbcAuthentication().dataSource(dataSource);
         }

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertController.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertController.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertController.java
new file mode 100644
index 0000000..d8d5411
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertController.java
@@ -0,0 +1,119 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.metron.rest.controller;
+
+import static org.apache.metron.rest.MetronRestConstants.SECURITY_ROLE_ADMIN;
+
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import java.util.List;
+import java.util.Map;
+import org.apache.metron.rest.RestException;
+import org.apache.metron.rest.model.AlertProfile;
+import org.apache.metron.rest.service.AlertService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.annotation.Secured;
+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.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * The API resource that is used for alert-related operations.
+ */
+@RestController
+@RequestMapping("/api/v1/alert")
+public class AlertController {
+
+  /**
+   * Service used to interact with alerts.
+   */
+  @Autowired
+  private AlertService alertService;
+
+  @ApiOperation(value = "Escalates a list of alerts by producing it to the Kafka escalate topic")
+  @ApiResponse(message = "Alerts were escalated", code = 200)
+  @RequestMapping(value = "/escalate", method = RequestMethod.POST)
+  ResponseEntity<Void> escalate(final @ApiParam(name = "alerts", value = "The alerts to be escalated", required = true) @RequestBody List<Map<String, Object>> alerts) throws RestException {
+    alertService.escalateAlerts(alerts);
+    return new ResponseEntity<>(HttpStatus.OK);
+  }
+
+  @ApiOperation(value = "Retrieves the current user's alerts profile")
+  @ApiResponses(value = {@ApiResponse(message = "Alerts profile", code = 200),
+      @ApiResponse(message = "The current user does not have an alerts profile", code = 404)})
+  @RequestMapping(value = "/profile", method = RequestMethod.GET)
+  ResponseEntity<AlertProfile> get() throws RestException {
+    AlertProfile alertsProfile = alertService.getProfile();
+    if (alertsProfile != null) {
+      return new ResponseEntity<>(alertsProfile, HttpStatus.OK);
+    } else {
+      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+    }
+  }
+
+  @Secured({"ROLE_" + SECURITY_ROLE_ADMIN})
+  @ApiOperation(value = "Retrieves all users' alerts profiles.  Only users that are part of "
+      + "the \"ROLE_ADMIN\" role are allowed to get all alerts profiles.")
+  @ApiResponses(value = {@ApiResponse(message = "List of all alerts profiles", code = 200),
+      @ApiResponse(message =
+          "The current user does not have permission to get all alerts profiles", code = 403)})
+  @RequestMapping(value = "/profile/all", method = RequestMethod.GET)
+  ResponseEntity<Iterable<AlertProfile>> findAll() throws RestException {
+    return new ResponseEntity<>(alertService.findAllProfiles(), HttpStatus.OK);
+  }
+
+  @ApiOperation(value = "Creates or updates the current user's alerts profile")
+  @ApiResponses(value = {
+      @ApiResponse(message = "Alerts profile updated. Returns saved alerts profile.", code = 200),
+      @ApiResponse(message = "Alerts profile created. Returns saved alerts profile.", code = 201)})
+  @RequestMapping(value = "/profile", method = RequestMethod.POST)
+  ResponseEntity<AlertProfile> save(@ApiParam(name = "alertsProfile", value =
+      "The alerts profile to be saved", required = true) @RequestBody AlertProfile alertsProfile)
+      throws RestException {
+    if (alertService.getProfile() == null) {
+      return new ResponseEntity<>(alertService.saveProfile(alertsProfile), HttpStatus.CREATED);
+    } else {
+      return new ResponseEntity<>(alertService.saveProfile(alertsProfile), HttpStatus.OK);
+    }
+  }
+
+  @Secured({"ROLE_" + SECURITY_ROLE_ADMIN})
+  @ApiOperation(value = "Deletes a user's alerts profile.  Only users that are part of "
+      + "the \"ROLE_ADMIN\" role are allowed to delete user alerts profiles.")
+  @ApiResponses(value = {@ApiResponse(message = "Alerts profile was deleted", code = 200),
+      @ApiResponse(message = "The current user does not have permission to delete alerts profiles",
+          code = 403),
+      @ApiResponse(message = "Alerts profile could not be found", code = 404)})
+  @RequestMapping(value = "/profile/{user}", method = RequestMethod.DELETE)
+  ResponseEntity<Void> delete(
+      @ApiParam(name = "user", value = "The user whose prolife will be deleted", required = true)
+      @PathVariable String user)
+      throws RestException {
+    if (alertService.deleteProfile(user)) {
+      return new ResponseEntity<>(HttpStatus.OK);
+    } else {
+      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsController.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsController.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsController.java
deleted file mode 100644
index 6f028a1..0000000
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsController.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.metron.rest.controller;
-
-import io.swagger.annotations.ApiOperation;
-import io.swagger.annotations.ApiParam;
-import io.swagger.annotations.ApiResponse;
-import java.util.List;
-import java.util.Map;
-import org.apache.metron.rest.RestException;
-import org.apache.metron.rest.service.AlertService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpStatus;
-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.RequestMethod;
-import org.springframework.web.bind.annotation.RestController;
-
-/**
- * The API resource that is used for alert-related operations.
- */
-@RestController
-@RequestMapping("/api/v1/alert")
-public class AlertsController {
-
-  /**
-   * Service used to interact with alerts.
-   */
-  @Autowired
-  private AlertService alertService;
-
-  @ApiOperation(value = "Escalates a list of alerts by producing it to the Kafka escalate topic")
-  @ApiResponse(message = "Alerts were escalated", code = 200)
-  @RequestMapping(value = "/escalate", method = RequestMethod.POST)
-  ResponseEntity<Void> escalate(final @ApiParam(name = "alerts", value = "The alerts to be escalated", required = true) @RequestBody List<Map<String, Object>> alerts) throws RestException {
-    alertService.escalateAlerts(alerts);
-    return new ResponseEntity<>(HttpStatus.OK);
-  }
-}

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertProfileRepository.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertProfileRepository.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertProfileRepository.java
new file mode 100644
index 0000000..1466d90
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/repository/AlertProfileRepository.java
@@ -0,0 +1,25 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.metron.rest.repository;
+
+import org.apache.metron.rest.model.AlertProfile;
+import org.springframework.data.repository.CrudRepository;
+
+public interface AlertProfileRepository extends CrudRepository<AlertProfile, String> {
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/security/SecurityUtils.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/security/SecurityUtils.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/security/SecurityUtils.java
new file mode 100644
index 0000000..ed10f14
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/security/SecurityUtils.java
@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.metron.rest.security;
+
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+
+public class SecurityUtils {
+
+  /** Returns the username of the currently logged in user.
+   *
+   * @return username
+   */
+  public static String getCurrentUser() {
+    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+    String user;
+    if (principal instanceof UserDetails) {
+      user = ((UserDetails)principal).getUsername();
+    } else {
+      user = principal.toString();
+    }
+    return user;
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertService.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertService.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertService.java
index 9668b7c..a5f244b 100644
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertService.java
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertService.java
@@ -20,6 +20,7 @@ package org.apache.metron.rest.service;
 import java.util.List;
 import java.util.Map;
 import org.apache.metron.rest.RestException;
+import org.apache.metron.rest.model.AlertProfile;
 
 /**
  * This is a set of operations created to interact with alerts.
@@ -27,4 +28,12 @@ import org.apache.metron.rest.RestException;
 public interface AlertService {
 
   void escalateAlerts(List<Map<String, Object>> alerts) throws RestException;
+
+  AlertProfile getProfile();
+
+  Iterable<AlertProfile> findAllProfiles();
+
+  AlertProfile saveProfile(AlertProfile alertsProfile);
+
+  boolean deleteProfile(String user);
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertsProfileService.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertsProfileService.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertsProfileService.java
new file mode 100644
index 0000000..84672b6
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/AlertsProfileService.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.metron.rest.service;
+
+import org.apache.metron.rest.model.AlertProfile;
+
+public interface AlertsProfileService {
+
+  AlertProfile get();
+
+  Iterable<AlertProfile> findAll();
+
+  AlertProfile save(AlertProfile alertsProfile);
+
+  boolean delete(String user);
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertServiceImpl.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertServiceImpl.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertServiceImpl.java
index 46370eb..73babbe 100644
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertServiceImpl.java
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertServiceImpl.java
@@ -23,10 +23,14 @@ import java.util.Map;
 import org.apache.metron.common.utils.JSONUtils;
 import org.apache.metron.rest.MetronRestConstants;
 import org.apache.metron.rest.RestException;
+import org.apache.metron.rest.model.AlertProfile;
+import org.apache.metron.rest.repository.AlertProfileRepository;
+import org.apache.metron.rest.security.SecurityUtils;
 import org.apache.metron.rest.service.AlertService;
 import org.apache.metron.rest.service.KafkaService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.core.env.Environment;
+import org.springframework.dao.EmptyResultDataAccessException;
 import org.springframework.stereotype.Service;
 
 /**
@@ -39,12 +43,15 @@ public class AlertServiceImpl implements AlertService {
 
   private Environment environment;
   private final KafkaService kafkaService;
+  private AlertProfileRepository alertProfileRepository;
 
   @Autowired
   public AlertServiceImpl(final KafkaService kafkaService,
-                          final Environment environment) {
+                          final Environment environment,
+                          final AlertProfileRepository alertsProfileRepository) {
     this.kafkaService = kafkaService;
     this.environment = environment;
+    this.alertProfileRepository = alertsProfileRepository;
   }
 
   @Override
@@ -59,4 +66,32 @@ public class AlertServiceImpl implements AlertService {
       throw new RestException(e);
     }
   }
+
+  @Override
+  public AlertProfile getProfile() {
+    return alertProfileRepository.findOne(SecurityUtils.getCurrentUser());
+  }
+
+  @Override
+  public Iterable<AlertProfile> findAllProfiles() {
+    return alertProfileRepository.findAll();
+  }
+
+  @Override
+  public AlertProfile saveProfile(AlertProfile alertsProfile) {
+    String user = SecurityUtils.getCurrentUser();
+    alertsProfile.setId(user);
+    return alertProfileRepository.save(alertsProfile);
+  }
+
+  @Override
+  public boolean deleteProfile(String user) {
+    boolean success = true;
+    try {
+      alertProfileRepository.delete(user);
+    } catch (EmptyResultDataAccessException e) {
+      success = false;
+    }
+    return success;
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java
new file mode 100644
index 0000000..239dbdc
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/AlertsProfileServiceImpl.java
@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.metron.rest.service.impl;
+
+import org.apache.metron.rest.model.AlertProfile;
+import org.apache.metron.rest.repository.AlertProfileRepository;
+import org.apache.metron.rest.security.SecurityUtils;
+import org.apache.metron.rest.service.AlertsProfileService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AlertsProfileServiceImpl implements AlertsProfileService {
+
+  private AlertProfileRepository alertsProfileRepository;
+
+  @Autowired
+  public AlertsProfileServiceImpl(AlertProfileRepository alertsProfileRepository) {
+    this.alertsProfileRepository = alertsProfileRepository;
+  }
+
+  @Override
+  public AlertProfile get() {
+    return alertsProfileRepository.findOne(SecurityUtils.getCurrentUser());
+  }
+
+  @Override
+  public Iterable<AlertProfile> findAll() {
+    return alertsProfileRepository.findAll();
+  }
+
+  @Override
+  public AlertProfile save(AlertProfile alertsProfile) {
+    String user = SecurityUtils.getCurrentUser();
+    alertsProfile.setId(user);
+    return alertsProfileRepository.save(alertsProfile);
+  }
+
+  @Override
+  public boolean delete(String user) {
+    boolean success = true;
+    try {
+      alertsProfileRepository.delete(user);
+    } catch (EmptyResultDataAccessException e) {
+      success = false;
+    }
+    return success;
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/resources/application.yml
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/resources/application.yml b/metron-interface/metron-rest/src/main/resources/application.yml
index 764bd40..c3012fa 100644
--- a/metron-interface/metron-rest/src/main/resources/application.yml
+++ b/metron-interface/metron-rest/src/main/resources/application.yml
@@ -58,3 +58,5 @@ meta:
   dao:
   # By default, we use the ElasticsearchMetaAlertDao
     impl: org.apache.metron.elasticsearch.dao.ElasticsearchMetaAlertDao
+
+spring.jpa.generate-ddl: true
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/main/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/resources/log4j.properties b/metron-interface/metron-rest/src/main/resources/log4j.properties
index 492cecf..e5f4095 100644
--- a/metron-interface/metron-rest/src/main/resources/log4j.properties
+++ b/metron-interface/metron-rest/src/main/resources/log4j.properties
@@ -13,4 +13,4 @@
 log4j.rootLogger=ERROR, stdout
 log4j.appender.stdout=org.apache.log4j.ConsoleAppender
 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd'T'HH:mm:ss.SSS} %-5p [%c] - %m%n
\ No newline at end of file
+log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd'T'HH:mm:ss.SSS} %-5p [%c] - %m%n

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertControllerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertControllerIntegrationTest.java
index be320fc..c3a4ac4 100644
--- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertControllerIntegrationTest.java
+++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertControllerIntegrationTest.java
@@ -18,13 +18,24 @@
 package org.apache.metron.rest.controller;
 
 import static org.apache.metron.rest.MetronRestConstants.TEST_PROFILE;
+import static org.hamcrest.Matchers.hasSize;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
 import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
+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.post;
+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 org.adrianwalker.multilinestring.Multiline;
+import org.apache.metron.integration.ComponentRunner;
+import org.apache.metron.integration.UnableToStartException;
+import org.apache.metron.integration.components.KafkaComponent;
+import org.apache.metron.rest.model.AlertProfile;
+import org.apache.metron.rest.service.AlertService;
+import org.apache.metron.rest.service.AlertsProfileService;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -57,17 +68,80 @@ public class AlertControllerIntegrationTest {
     @Multiline
     public static String alerts;
 
+    /**
+     * {
+     *   "tableColumns": ["user1_field"],
+     *   "savedSearches": [
+     *     {
+     *       "name": "user1 search 1",
+     *       "searchRequest": {
+     *         "from": 0,
+     *         "indices": ["bro"],
+     *         "query": "*",
+     *         "size": 5
+     *       }
+     *     },
+     *     {
+     *       "name": "user1 search 2",
+     *       "searchRequest": {
+     *         "from": 10,
+     *         "indices": ["snort"],
+     *         "query": "*",
+     *         "size": 10
+     *       }
+     *     }
+     *   ]
+     * }
+     */
+    @Multiline
+    public static String user1ProfileJson;
+
+
+    /**
+     * {
+     *   "tableColumns": ["user2_field"],
+     *   "savedSearches": [
+     *     {
+     *       "name": "user2 search 1",
+     *       "searchRequest": {
+     *         "from": 0,
+     *         "indices": ["bro", "snort"],
+     *         "query": "ip_src_addr:192.168.1.1",
+     *         "size": 100
+     *       }
+     *     }
+     *   ]
+     * }
+     */
+    @Multiline
+    public static String user2ProfileJson;
+
+    // A bug in Spring and/or Kafka forced us to move into a component that is spun up and down per test-case
+    // Given the large spinup time of components, please avoid this pattern until we upgrade Spring.
+    // See: https://issues.apache.org/jira/browse/METRON-1009
+    @Autowired
+    private KafkaComponent kafkaWithZKComponent;
+    private ComponentRunner runner;
+
     @Autowired
     private WebApplicationContext wac;
 
+    @Autowired
+    private AlertService alertService;
+
     private MockMvc mockMvc;
 
     private String alertUrl = "/api/v1/alert";
-    private String user = "user";
+    private String user1 = "user1";
+    private String user2 = "user2";
+    private String admin = "admin";
     private String password = "password";
 
     @Before
     public void setup() throws Exception {
+        for (AlertProfile alertsProfile : alertService.findAllProfiles()) {
+          alertService.deleteProfile(alertsProfile.getId());
+        }
         this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build();
     }
 
@@ -75,12 +149,197 @@ public class AlertControllerIntegrationTest {
     public void testSecurity() throws Exception {
         this.mockMvc.perform(post(alertUrl + "/escalate").with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(alerts))
                 .andExpect(status().isUnauthorized());
+        this.mockMvc.perform(get(alertUrl + "/profile"))
+            .andExpect(status().isUnauthorized());
+        this.mockMvc.perform(get(alertUrl + "/profile/all"))
+            .andExpect(status().isUnauthorized());
+        this.mockMvc.perform(post(alertUrl + "/profile").with(csrf())
+            .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
+            .content(user1ProfileJson))
+            .andExpect(status().isUnauthorized());
+        this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(user1, password)).with(csrf()))
+            .andExpect(status().isForbidden());
+        this.mockMvc.perform(delete(alertUrl + "/profile/user1").with(httpBasic(user1, password)).with(csrf()))
+            .andExpect(status().isForbidden());
     }
 
     @Test
-    public void test() throws Exception {
-        this.mockMvc.perform(post(alertUrl + "/escalate").with(httpBasic(user,password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(alerts))
+    public void escalateShouldEscalateAlerts() throws Exception {
+        startKafka();
+        this.mockMvc.perform(post(alertUrl + "/escalate").with(httpBasic(user1,password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(alerts))
                 .andExpect(status().isOk());
+        stopKafka();
+    }
+
+    @Test
+    public void testAlertProfiles() throws Exception {
+        emptyProfileShouldReturnNotFound();
+        alertsProfilesShouldBeCreatedOrUpdated();
+        alertsProfilesShouldBeProperlyDeleted();
+    }
+
+    /** Ensures a 404 is returned when an alerts profile cannot be found.  In the case of an admin getting
+     * all profiles, an empty list should be returned.  This tests depends on the alertsProfileRepository
+     * being empty.
+     *
+     * @throws Exception
+     */
+    private void emptyProfileShouldReturnNotFound() throws Exception {
+
+      // user1 should get a 404 because an alerts profile has not been created
+      this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user1, password)))
+          .andExpect(status().isNotFound());
+
+      // user2 should get a 404 because an alerts profile has not been created
+      this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user2, password)))
+          .andExpect(status().isNotFound());
+
+      // getting all alerts profiles should return an empty list
+      this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(admin, password)))
+          .andExpect(status().isOk())
+          .andExpect(
+              content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+          .andExpect(jsonPath("$.*", hasSize(0)));
+    }
+
+    /** Ensures users can update their profiles independently of other users.  When user1 updates an
+     * alerts profile, alerts profile for user2 should not be affected.  Tests that an initial update
+     * returns a 201 status and subsequent updates return 200 statuses.  A call to get all alerts profiles
+     * by an admin user should also work properly.  This tests depends on the alertsProfileRepository
+     * being empty initially.
+     *
+     * @throws Exception
+     */
+    private void alertsProfilesShouldBeCreatedOrUpdated() throws Exception {
+
+      // user1 creates their alerts profile
+      this.mockMvc.perform(post(alertUrl + "/profile").with(httpBasic(user1, password)).with(csrf())
+          .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
+          .content(user1ProfileJson))
+          .andExpect(status().isCreated())
+          .andExpect(content().json(user1ProfileJson));
+
+      // user1 updates their alerts profile
+      this.mockMvc.perform(post(alertUrl + "/profile").with(httpBasic(user1, password)).with(csrf())
+          .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
+          .content(user1ProfileJson))
+          .andExpect(status().isOk())
+          .andExpect(content().json(user1ProfileJson));
+
+      // user1 gets their alerts profile
+      this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user1, password)))
+          .andExpect(status().isOk())
+          .andExpect(
+              content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+          .andExpect(content().json(user1ProfileJson));
+
+      // user2 alerts profile should still be empty
+      this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user2, password)))
+          .andExpect(status().isNotFound());
+
+      // getting all alerts profiles should only return user1's
+      this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(admin, password)))
+          .andExpect(status().isOk())
+          .andExpect(
+              content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+          .andExpect(content().json("[" + user1ProfileJson + "]"));
+
+      // user2 creates their alerts profile
+      this.mockMvc.perform(post(alertUrl + "/profile").with(httpBasic(user2, password)).with(csrf())
+          .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
+          .content(user2ProfileJson))
+          .andExpect(status().isCreated())
+          .andExpect(content().json(user2ProfileJson));
+
+      // user2 updates their alerts profile
+      this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user1, password)))
+          .andExpect(status().isOk())
+          .andExpect(
+              content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+          .andExpect(content().json(user1ProfileJson));
+
+      // user2 gets their alerts profile
+      this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user2, password)))
+          .andExpect(status().isOk())
+          .andExpect(
+              content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+          .andExpect(content().json(user2ProfileJson));
+
+      // getting all alerts profiles should return both
+      this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(admin, password)))
+          .andExpect(status().isOk())
+          .andExpect(
+              content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+          .andExpect(content().json("[" + user1ProfileJson + "," + user2ProfileJson + "]"));
+    }
+
+    /** Ensures users can delete their profiles independently of other users.  When user1 deletes an
+     * alerts profile, alerts profile for user2 should not be deleted.  This tests depends on alerts
+     * profiles existing for user1 and user2.
+     *
+     * @throws Exception
+     */
+    private void alertsProfilesShouldBeProperlyDeleted() throws Exception {
+
+      // user1 deletes their profile
+      this.mockMvc.perform(delete(alertUrl + "/profile/user1").with(httpBasic(admin, password)))
+          .andExpect(status().isOk());
+
+      // user1 should get a 404 when trying to delete an alerts profile that doesn't exist
+      this.mockMvc.perform(delete(alertUrl + "/profile/user1").with(httpBasic(admin, password)))
+          .andExpect(status().isNotFound());
+
+      // user1 should get a 404 when trying to retrieve their alerts profile
+      this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user1, password)))
+          .andExpect(status().isNotFound());
+
+      // user2's alerts profile should still exist
+      this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user2, password)))
+          .andExpect(status().isOk())
+          .andExpect(
+              content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+          .andExpect(content().json(user2ProfileJson));
+
+      // getting all alerts profiles should only return user2's
+      this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(admin, password)))
+          .andExpect(status().isOk())
+          .andExpect(
+              content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+          .andExpect(content().json("[" + user2ProfileJson + "]"));
+
+      // user2 deletes their profile
+      this.mockMvc.perform(delete(alertUrl + "/profile/user2").with(httpBasic(admin, password)))
+          .andExpect(status().isOk());
+
+      // user2 should get a 404 when trying to delete an alerts profile that doesn't exist
+      this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user1, password)))
+          .andExpect(status().isNotFound());
+
+      // user2 should get a 404 when trying to retrieve their alerts profile
+      this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user2, password)))
+          .andExpect(status().isNotFound());
+
+      // getting all alerts profiles should return an empty list
+      this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(admin, password)))
+          .andExpect(status().isOk())
+          .andExpect(
+              content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+          .andExpect(jsonPath("$.*", hasSize(0)));
+    }
+
+    private void startKafka() {
+        runner = new ComponentRunner.Builder()
+            .withComponent("kafka", kafkaWithZKComponent)
+            .withCustomShutdownOrder(new String[]{"kafka"})
+            .build();
+        try {
+            runner.start();
+        } catch (UnableToStartException e) {
+            e.printStackTrace();
+        }
+    }
 
+    private  void stopKafka() {
+        runner.stop();
     }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertServiceImplTest.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertServiceImplTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertServiceImplTest.java
index c55e0a5..8bed6b3 100644
--- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertServiceImplTest.java
+++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertServiceImplTest.java
@@ -17,35 +17,58 @@
  */
 package org.apache.metron.rest.service.impl;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 import static org.powermock.api.mockito.PowerMockito.mock;
 
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import org.apache.metron.rest.MetronRestConstants;
+import org.apache.metron.rest.model.AlertProfile;
+import org.apache.metron.rest.repository.AlertProfileRepository;
 import org.apache.metron.rest.service.AlertService;
 import org.apache.metron.rest.service.KafkaService;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mockito;
 import org.springframework.core.env.Environment;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
 
 @SuppressWarnings("unchecked")
 public class AlertServiceImplTest {
 
   private KafkaService kafkaService;
   private Environment environment;
+  private AlertProfileRepository alertProfileRepository;
   private AlertService alertService;
+  private String testUser = "user1";
 
   @SuppressWarnings("unchecked")
   @Before
   public void setUp() throws Exception {
     kafkaService = mock(KafkaService.class);
     environment = mock(Environment.class);
-    alertService = new AlertServiceImpl(kafkaService, environment);
+    alertProfileRepository = Mockito.mock(AlertProfileRepository.class);
+    alertService = new AlertServiceImpl(kafkaService, environment, alertProfileRepository);
+
+    Authentication authentication = Mockito.mock(Authentication.class);
+    UserDetails userDetails = Mockito.mock(UserDetails.class);
+    when(authentication.getPrincipal()).thenReturn(userDetails);
+    when(userDetails.getUsername()).thenReturn(testUser);
+    SecurityContextHolder.getContext().setAuthentication(authentication);
   }
 
   @Test
@@ -66,4 +89,64 @@ public class AlertServiceImplTest {
     verify(kafkaService).produceMessage("escalation", expectedMessage2);
     verifyZeroInteractions(kafkaService);
   }
+
+  @Test
+  public void getShouldProperlyReturnActiveProfile() throws Exception {
+    AlertProfile alertsProfile = new AlertProfile();
+    alertsProfile.setId(testUser);
+    when(alertProfileRepository.findOne(testUser)).thenReturn(alertsProfile);
+
+    AlertProfile expectedAlertsProfile = new AlertProfile();
+    expectedAlertsProfile.setId(testUser);
+    assertEquals(expectedAlertsProfile, alertService.getProfile());
+    verify(alertProfileRepository, times(1)).findOne(testUser);
+    verifyNoMoreInteractions(alertProfileRepository);
+  }
+
+  @Test
+  public void findAllShouldProperlyReturnActiveProfiles() throws Exception {
+    AlertProfile alertsProfile1 = new AlertProfile();
+    alertsProfile1.setId(testUser);
+    AlertProfile alertsProfile2 = new AlertProfile();
+    alertsProfile2.setId(testUser);
+    when(alertProfileRepository.findAll())
+        .thenReturn(Arrays.asList(alertsProfile1, alertsProfile2));
+
+    AlertProfile expectedAlertsProfile1 = new AlertProfile();
+    expectedAlertsProfile1.setId(testUser);
+    AlertProfile expectedAlertsProfile2 = new AlertProfile();
+    expectedAlertsProfile2.setId(testUser);
+    Iterator<AlertProfile> actualAlertsProfiles = alertService.findAllProfiles().iterator();
+    assertEquals(expectedAlertsProfile1, actualAlertsProfiles.next());
+    assertEquals(expectedAlertsProfile2, actualAlertsProfiles.next());
+    assertFalse(actualAlertsProfiles.hasNext());
+    verify(alertProfileRepository, times(1)).findAll();
+    verifyNoMoreInteractions(alertProfileRepository);
+  }
+
+  @Test
+  public void saveShouldProperlySaveActiveProfile() throws Exception {
+    AlertProfile savedAlertsProfile = new AlertProfile();
+    savedAlertsProfile.setId(testUser);
+    when(alertProfileRepository.save(savedAlertsProfile)).thenReturn(savedAlertsProfile);
+
+    AlertProfile expectedAlertsProfile = new AlertProfile();
+    expectedAlertsProfile.setId(testUser);
+    AlertProfile alertsProfile = new AlertProfile();
+    assertEquals(expectedAlertsProfile, alertService.saveProfile(alertsProfile));
+
+    verify(alertProfileRepository, times(1)).save(savedAlertsProfile);
+    verifyNoMoreInteractions(alertProfileRepository);
+  }
+
+  @Test
+  public void deleteShouldProperlyDeleteActiveProfile() throws Exception {
+    assertTrue(alertService.deleteProfile(testUser));
+
+    doThrow(new EmptyResultDataAccessException(1)).when(alertProfileRepository).delete(testUser);
+    assertFalse(alertService.deleteProfile(testUser));
+
+    verify(alertProfileRepository, times(2)).delete(testUser);
+    verifyNoMoreInteractions(alertProfileRepository);
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/e5d87637/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java
index 3a72002..f4b6196 100644
--- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java
@@ -15,6 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.metron.indexing.dao.search;
 
 import java.util.ArrayList;
@@ -115,4 +116,33 @@ public class SearchRequest {
   public void setFacetFields(List<String> facetFields) {
     this.facetFields = facetFields;
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    SearchRequest that = (SearchRequest) o;
+
+    return indices != null ? indices.equals(that.indices) : that.indices == null &&
+        (query != null ? query.equals(that.query) : that.query == null) && size == that.size &&
+        from == that.from &&
+        (sort != null ? sort.equals(that.sort) : that.sort == null) &&
+        (facetFields != null ? facetFields.equals(that.facetFields) : that.facetFields == null);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = indices != null ? indices.hashCode() : 0;
+    result = 31 * result + (query != null ? query.hashCode() : 0);
+    result = 31 * result + getSize();
+    result = 31 * result + getFrom();
+    result = 31 * result + (sort != null ? sort.hashCode() : 0);
+    result = 31 * result + (facetFields != null ? facetFields.hashCode() : 0);
+    return result;
+  }
 }