metron-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rmerri...@apache.org
Subject [2/3] metron git commit: METRON-1337 List of facets should not be hardcoded (merrimanr) closes apache/metron#853
Date Tue, 06 Mar 2018 14:09:38 GMT
http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/HBaseConfigTest.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/HBaseConfigTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/HBaseConfigTest.java
new file mode 100644
index 0000000..835aa8d
--- /dev/null
+++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/HBaseConfigTest.java
@@ -0,0 +1,69 @@
+/**
+ * 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 org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.metron.hbase.HTableProvider;
+import org.apache.metron.rest.service.GlobalConfigService;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.HashMap;
+
+import static org.apache.metron.hbase.client.UserSettingsClient.USER_SETTINGS_HBASE_CF;
+import static org.apache.metron.hbase.client.UserSettingsClient.USER_SETTINGS_HBASE_TABLE;
+import static org.mockito.Mockito.*;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+import static org.powermock.api.mockito.PowerMockito.whenNew;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({HTableProvider.class, HBaseConfiguration.class, HBaseConfig.class})
+public class HBaseConfigTest {
+
+  private GlobalConfigService globalConfigService;
+  private HBaseConfig hBaseConfig;
+
+  @Before
+  public void setUp() throws Exception {
+    globalConfigService = mock(GlobalConfigService.class);
+    hBaseConfig = new HBaseConfig(globalConfigService);
+    mockStatic(HBaseConfiguration.class);
+  }
+
+  @Test
+  public void userSettingsTableShouldBeReturnedFromGlobalConfigByDefault() throws Exception {
+    when(globalConfigService.get()).thenReturn(new HashMap<String, Object>() {{
+      put(USER_SETTINGS_HBASE_TABLE, "global_config_user_settings_table");
+      put(USER_SETTINGS_HBASE_CF, "global_config_user_settings_cf");
+    }});
+    HTableProvider htableProvider = mock(HTableProvider.class);
+    whenNew(HTableProvider.class).withNoArguments().thenReturn(htableProvider);
+    Configuration configuration = mock(Configuration.class);
+    when(HBaseConfiguration.create()).thenReturn(configuration);
+
+    hBaseConfig.userSettingsClient();
+    verify(htableProvider).getTable(configuration, "global_config_user_settings_table");
+    verifyZeroInteractions(htableProvider);
+  }
+
+
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java
index 1150189..008f3fc 100644
--- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java
+++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java
@@ -17,14 +17,6 @@
  */
 package org.apache.metron.rest.config;
 
-import static org.apache.metron.rest.MetronRestConstants.TEST_PROFILE;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
 import kafka.admin.AdminUtils$;
 import kafka.utils.ZKStringSerializer$;
 import kafka.utils.ZkUtils;
@@ -34,15 +26,18 @@ import org.apache.curator.RetryPolicy;
 import org.apache.curator.framework.CuratorFramework;
 import org.apache.curator.framework.CuratorFrameworkFactory;
 import org.apache.curator.retry.ExponentialBackoffRetry;
+import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.kafka.clients.producer.KafkaProducer;
 import org.apache.metron.common.configuration.ConfigurationsUtils;
 import org.apache.metron.common.zookeeper.ConfigurationsCache;
 import org.apache.metron.common.zookeeper.ZKConfigurationsCache;
+import org.apache.metron.hbase.client.UserSettingsClient;
 import org.apache.metron.hbase.mock.MockHBaseTableProvider;
 import org.apache.metron.integration.ComponentRunner;
 import org.apache.metron.integration.UnableToStartException;
 import org.apache.metron.integration.components.KafkaComponent;
 import org.apache.metron.integration.components.ZKServerComponent;
+import org.apache.metron.rest.RestException;
 import org.apache.metron.rest.mock.MockStormCLIClientWrapper;
 import org.apache.metron.rest.mock.MockStormRestTemplate;
 import org.apache.metron.rest.service.impl.StormCLIWrapper;
@@ -53,6 +48,16 @@ import org.springframework.kafka.core.ConsumerFactory;
 import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
 import org.springframework.web.client.RestTemplate;
 
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.apache.metron.rest.MetronRestConstants.TEST_PROFILE;
+
 @Configuration
 @Profile(TEST_PROFILE)
 public class TestConfig {
@@ -175,4 +180,9 @@ public class TestConfig {
     return AdminUtils$.MODULE$;
   }
 
+
+  @Bean()
+  public UserSettingsClient userSettingsClient() throws RestException, IOException {
+    return new UserSettingsClient(new MockHBaseTableProvider().addToCache("user_settings", "cf"), Bytes.toBytes("cf"));
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/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
deleted file mode 100644
index c3a4ac4..0000000
--- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertControllerIntegrationTest.java
+++ /dev/null
@@ -1,345 +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 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;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.http.MediaType;
-import org.springframework.test.context.ActiveProfiles;
-import org.springframework.test.context.junit4.SpringRunner;
-import org.springframework.test.web.servlet.MockMvc;
-import org.springframework.test.web.servlet.setup.MockMvcBuilders;
-import org.springframework.web.context.WebApplicationContext;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
-@ActiveProfiles(TEST_PROFILE)
-public class AlertControllerIntegrationTest {
-
-    /**
-     [
-     {
-     "is_alert": true,
-     "field": "value1"
-     },
-     {
-     "is_alert": true,
-     "field": "value2"
-     }
-     ]
-     */
-    @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 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();
-    }
-
-    @Test
-    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 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/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsUIControllerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsUIControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsUIControllerIntegrationTest.java
new file mode 100644
index 0000000..49863d6
--- /dev/null
+++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsUIControllerIntegrationTest.java
@@ -0,0 +1,340 @@
+/**
+ * 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.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.service.AlertsUIService;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@ActiveProfiles(TEST_PROFILE)
+public class AlertsUIControllerIntegrationTest {
+
+  /**
+   * [
+   * {
+   * "is_alert": true,
+   * "field": "value1"
+   * },
+   * {
+   * "is_alert": true,
+   * "field": "value2"
+   * }
+   * ]
+   */
+  @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 user1AlertUserSettingsJson;
+
+
+  /**
+   * {
+   *   "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 user2AlertUserSettingsJson;
+
+  // 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;
+
+  private MockMvc mockMvc;
+
+  @Autowired
+  private AlertsUIService alertsUIService;
+  
+  private String alertUrl = "/api/v1/alerts/ui";
+  private String user1 = "user1";
+  private String user2 = "user2";
+  private String admin = "admin";
+  private String password = "password";
+
+  @Before
+  public void setup() throws Exception {
+    for (String user : alertsUIService.findAllAlertsUIUserSettings().keySet()) {
+      alertsUIService.deleteAlertsUIUserSettings(user);
+    }
+    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build();
+  }
+
+  @Test
+  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 + "/settings"))
+            .andExpect(status().isUnauthorized());
+    this.mockMvc.perform(get(alertUrl + "/settings/all"))
+            .andExpect(status().isUnauthorized());
+    this.mockMvc.perform(post(alertUrl + "/settings").with(csrf())
+            .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
+            .content(user1AlertUserSettingsJson))
+            .andExpect(status().isUnauthorized());
+    this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(user1, password)).with(csrf()))
+            .andExpect(status().isForbidden());
+    this.mockMvc.perform(delete(alertUrl + "/settings/user1").with(httpBasic(user1, password)).with(csrf()))
+            .andExpect(status().isForbidden());
+  }
+
+  @Test
+  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 + "/settings").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 + "/settings").with(httpBasic(user2, password)))
+            .andExpect(status().isNotFound());
+
+    // getting all alerts profiles should return an empty list
+    this.mockMvc.perform(get(alertUrl + "/settings/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 + "/settings").with(httpBasic(user1, password)).with(csrf())
+            .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
+            .content(user1AlertUserSettingsJson))
+            .andExpect(status().isCreated());
+
+    // user1 updates their alerts profile
+    this.mockMvc.perform(post(alertUrl + "/settings").with(httpBasic(user1, password)).with(csrf())
+            .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
+            .content(user1AlertUserSettingsJson))
+            .andExpect(status().isOk());
+
+    // user1 gets their alerts profile
+    this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user1, password)))
+            .andExpect(status().isOk())
+            .andExpect(
+                    content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+            .andExpect(content().json(user1AlertUserSettingsJson));
+
+    // user2 alerts profile should still be empty
+    this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user2, password)))
+            .andExpect(status().isNotFound());
+
+    // getting all alerts profiles should only return user1's
+    this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(admin, password)))
+            .andExpect(status().isOk())
+            .andExpect(
+                    content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+            .andExpect(content().json("{\"" + user1 + "\": " + user1AlertUserSettingsJson + "}"));
+
+    // user2 creates their alerts profile
+    this.mockMvc.perform(post(alertUrl + "/settings").with(httpBasic(user2, password)).with(csrf())
+            .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
+            .content(user2AlertUserSettingsJson))
+            .andExpect(status().isCreated());
+
+    // user2 updates their alerts profile
+    this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user1, password)))
+            .andExpect(status().isOk())
+            .andExpect(
+                    content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+            .andExpect(content().json(user1AlertUserSettingsJson));
+
+    // user2 gets their alerts profile
+    this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user2, password)))
+            .andExpect(status().isOk())
+            .andExpect(
+                    content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+            .andExpect(content().json(user2AlertUserSettingsJson));
+
+    // getting all alerts profiles should return both
+    this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(admin, password)))
+            .andExpect(status().isOk())
+            .andExpect(
+                    content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+            .andExpect(content().json("{\"" + user1 + "\": " + user1AlertUserSettingsJson + ",\"" + user2 + "\": " + user2AlertUserSettingsJson + "}"));
+  }
+
+  /** 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 + "/settings/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 + "/settings/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 + "/settings").with(httpBasic(user1, password)))
+            .andExpect(status().isNotFound());
+
+    // user2's alerts profile should still exist
+    this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user2, password)))
+            .andExpect(status().isOk())
+            .andExpect(
+                    content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+            .andExpect(content().json(user2AlertUserSettingsJson));
+
+    // getting all alerts profiles should only return user2's
+    this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(admin, password)))
+            .andExpect(status().isOk())
+            .andExpect(
+                    content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+            .andExpect(content().json("{\"" + user2 + "\": " + user2AlertUserSettingsJson + "}"));
+
+    // user2 deletes their profile
+    this.mockMvc.perform(delete(alertUrl + "/settings/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 + "/settings").with(httpBasic(user1, password)))
+            .andExpect(status().isNotFound());
+
+    // user2 should get a 404 when trying to retrieve their alerts profile
+    this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user2, password)))
+            .andExpect(status().isNotFound());
+
+    // getting all alerts profiles should return an empty list
+    this.mockMvc.perform(get(alertUrl + "/settings/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/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/SearchControllerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/SearchControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/SearchControllerIntegrationTest.java
index d8758cd..a55132c 100644
--- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/SearchControllerIntegrationTest.java
+++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/SearchControllerIntegrationTest.java
@@ -29,12 +29,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
 import com.google.common.collect.ImmutableMap;
+
 import java.util.HashMap;
 import java.util.Map;
 import org.adrianwalker.multilinestring.Multiline;
 import org.apache.metron.indexing.dao.InMemoryDao;
 import org.apache.metron.indexing.dao.SearchIntegrationTest;
 import org.apache.metron.indexing.dao.search.FieldType;
+import org.apache.metron.rest.service.AlertsUIService;
 import org.apache.metron.rest.service.SensorIndexingConfigService;
 import org.json.simple.parser.ParseException;
 import org.junit.After;
@@ -66,16 +68,28 @@ public class SearchControllerIntegrationTest extends DaoControllerTest {
    *     "field": "timestamp",
    *     "sortOrder": "desc"
    *   }
-   * ]
+   * ],
+   * "facetFields": []
    * }
    */
   @Multiline
   public static String defaultQuery;
 
+  /**
+   * {
+   *   "facetFields": ["ip_src_port"]
+   * }
+   */
+  @Multiline
+  public static String alertProfile;
+
   @Autowired
   private SensorIndexingConfigService sensorIndexingConfigService;
 
   @Autowired
+  private AlertsUIService alertsUIService;
+
+  @Autowired
   private WebApplicationContext wac;
 
   private MockMvc mockMvc;
@@ -93,6 +107,7 @@ public class SearchControllerIntegrationTest extends DaoControllerTest {
     );
     loadTestData(testData);
     loadColumnTypes();
+    loadFacetCounts();
   }
 
   @After
@@ -107,7 +122,7 @@ public class SearchControllerIntegrationTest extends DaoControllerTest {
   }
 
   @Test
-  public void testDefaultQuery() throws Exception {
+  public void testSearchWithDefaults() throws Exception {
     sensorIndexingConfigService.save("bro", new HashMap<String, Object>() {{
       put("index", "bro");
     }});
@@ -126,12 +141,40 @@ public class SearchControllerIntegrationTest extends DaoControllerTest {
             .andExpect(jsonPath("$.results[3].source.timestamp").value(2))
             .andExpect(jsonPath("$.results[4].source.source:type").value("bro"))
             .andExpect(jsonPath("$.results[4].source.timestamp").value(1))
+            .andExpect(jsonPath("$.facetCounts.*", hasSize(1)))
+            .andExpect(jsonPath("$.facetCounts.ip_src_addr.*", hasSize(2)))
+            .andExpect(jsonPath("$.facetCounts.ip_src_addr['192.168.1.1']").value(3))
+            .andExpect(jsonPath("$.facetCounts.ip_src_addr['192.168.1.2']").value(1))
     );
 
     sensorIndexingConfigService.delete("bro");
   }
 
   @Test
+  public void testSearchWithAlertProfileFacetFields() throws Exception {
+    assertEventually(() -> this.mockMvc.perform(
+        post("/api/v1/alerts/ui/settings").with(httpBasic(user, password)).with(csrf())
+            .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
+            .content(alertProfile))
+        .andExpect(status().isOk())
+    );
+
+    assertEventually(() -> this.mockMvc.perform(
+        post(searchUrl + "/search").with(httpBasic(user, password)).with(csrf())
+            .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
+            .content(defaultQuery))
+        .andExpect(status().isOk())
+        .andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
+        .andExpect(jsonPath("$.facetCounts.*", hasSize(1)))
+        .andExpect(jsonPath("$.facetCounts.ip_src_port.*", hasSize(2)))
+        .andExpect(jsonPath("$.facetCounts.ip_src_port['8010']").value(1))
+        .andExpect(jsonPath("$.facetCounts.ip_src_port['8009']").value(2))
+    );
+
+    alertsUIService.deleteAlertsUIUserSettings(user);
+  }
+
+  @Test
   public void testColumnMetadataUsingDefaultIndices() throws Exception {
     // Setup the default indices of bro and snort
     sensorIndexingConfigService.save("bro", new HashMap<String, Object>() {{
@@ -314,4 +357,18 @@ public class SearchControllerIntegrationTest extends DaoControllerTest {
     columnTypes.put("snort", snortTypes);
     InMemoryDao.setColumnMetadata(columnTypes);
   }
+
+  private void loadFacetCounts() {
+    Map<String, Map<String, Long>> facetCounts = new HashMap<>();
+    Map<String, Long> ipSrcAddrCounts = new HashMap<>();
+    ipSrcAddrCounts.put("192.168.1.1", 3L);
+    ipSrcAddrCounts.put("192.168.1.2", 1L);
+    Map<String, Long> ipSrcPortCounts = new HashMap<>();
+    ipSrcPortCounts.put("8010", 1L);
+    ipSrcPortCounts.put("8009", 2L);
+    facetCounts.put("ip_src_addr", ipSrcAddrCounts);
+    facetCounts.put("ip_src_port", ipSrcPortCounts);
+    InMemoryDao.setFacetCounts(facetCounts);
+  }
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/UserControllerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/UserControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/UserControllerIntegrationTest.java
index 9bdd439..b1e432e 100644
--- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/UserControllerIntegrationTest.java
+++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/UserControllerIntegrationTest.java
@@ -36,30 +36,34 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
 @RunWith(SpringRunner.class)
-@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
 @ActiveProfiles(TEST_PROFILE)
 public class UserControllerIntegrationTest {
 
-    private MockMvc mockMvc;
+  private MockMvc mockMvc;
 
-    @Autowired
-    private WebApplicationContext wac;
+  @Autowired
+  private WebApplicationContext wac;
 
-    private String user = "user";
-    private String password = "password";
+  private String userUrl = "/api/v1/user";
+  private String user1 = "user1";
+  private String password = "password";
 
-    @Before
-    public void setup() throws Exception {
-        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build();
-    }
+  @Before
+  public void setup() throws Exception {
+    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build();
+  }
 
-    @Test
-    public void test() throws Exception {
-        this.mockMvc.perform(get("/api/v1/user"))
-                .andExpect(status().isUnauthorized());
+  @Test
+  public void testSecurity() throws Exception {
+    this.mockMvc.perform(get(userUrl))
+            .andExpect(status().isUnauthorized());
+  }
 
-        this.mockMvc.perform(get("/api/v1/user").with(httpBasic(user,password)))
-                .andExpect(status().isOk())
-                .andExpect(content().string(user));
-    }
+  @Test
+  public void testGetUser() throws Exception {
+    this.mockMvc.perform(get(userUrl).with(httpBasic(user1, password)))
+            .andExpect(status().isOk())
+            .andExpect(content().string(user1));
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/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
deleted file mode 100644
index 8bed6b3..0000000
--- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertServiceImplTest.java
+++ /dev/null
@@ -1,152 +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.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);
-    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
-  public void produceMessageShouldProperlyProduceMessage() throws Exception {
-    String escalationTopic = "escalation";
-    final Map<String, Object> message1 = new HashMap<>();
-    message1.put("field", "value1");
-    final Map<String, Object> message2 = new HashMap<>();
-    message2.put("field", "value2");
-    List<Map<String, Object>> messages = Arrays.asList(message1, message2);
-    when(environment.getProperty(MetronRestConstants.KAFKA_TOPICS_ESCALATION_PROPERTY)).thenReturn(escalationTopic);
-
-    alertService.escalateAlerts(messages);
-
-    String expectedMessage1 = "{\"field\":\"value1\"}";
-    String expectedMessage2 = "{\"field\":\"value2\"}";
-    verify(kafkaService).produceMessage("escalation", expectedMessage1);
-    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/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsUIServiceImplTest.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsUIServiceImplTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsUIServiceImplTest.java
new file mode 100644
index 0000000..dc52712
--- /dev/null
+++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsUIServiceImplTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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 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.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.adrianwalker.multilinestring.Multiline;
+import org.apache.metron.rest.MetronRestConstants;
+import org.apache.metron.rest.model.AlertsUIUserSettings;
+import org.apache.metron.hbase.client.UserSettingsClient;
+import org.apache.metron.rest.service.AlertsUIService;
+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.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+
+@SuppressWarnings("unchecked")
+public class AlertsUIServiceImplTest {
+
+  public static ThreadLocal<ObjectMapper> _mapper = ThreadLocal.withInitial(() ->
+          new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL));
+
+  /**
+   * {
+   *   "tableColumns": ["user1_field"]
+   * }
+   */
+  @Multiline
+  public static String user1AlertUserSettings;
+
+  /**
+   * {
+   *   "tableColumns": ["user2_field"]
+   * }
+   */
+  @Multiline
+  public static String user2AlertUserSettings;
+
+  private KafkaService kafkaService;
+  private Environment environment;
+  private UserSettingsClient userSettingsClient;
+  private AlertsUIService alertsUIService;
+  private String user1 = "user1";
+  private String user2 = "user2";
+
+  @SuppressWarnings("unchecked")
+  @Before
+  public void setUp() throws Exception {
+    kafkaService = mock(KafkaService.class);
+    environment = mock(Environment.class);
+    userSettingsClient = mock(UserSettingsClient.class);
+    alertsUIService = new AlertsUIServiceImpl(kafkaService, environment, userSettingsClient);
+
+    // assume user1 is logged in for tests
+    Authentication authentication = Mockito.mock(Authentication.class);
+    UserDetails userDetails = Mockito.mock(UserDetails.class);
+    when(authentication.getPrincipal()).thenReturn(userDetails);
+    when(userDetails.getUsername()).thenReturn(user1);
+    SecurityContextHolder.getContext().setAuthentication(authentication);
+  }
+
+  @Test
+  public void produceMessageShouldProperlyProduceMessage() throws Exception {
+    String escalationTopic = "escalation";
+    final Map<String, Object> message1 = new HashMap<>();
+    message1.put("field", "value1");
+    final Map<String, Object> message2 = new HashMap<>();
+    message2.put("field", "value2");
+    List<Map<String, Object>> messages = Arrays.asList(message1, message2);
+    when(environment.getProperty(MetronRestConstants.KAFKA_TOPICS_ESCALATION_PROPERTY)).thenReturn(escalationTopic);
+
+    alertsUIService.escalateAlerts(messages);
+
+    String expectedMessage1 = "{\"field\":\"value1\"}";
+    String expectedMessage2 = "{\"field\":\"value2\"}";
+    verify(kafkaService).produceMessage("escalation", expectedMessage1);
+    verify(kafkaService).produceMessage("escalation", expectedMessage2);
+    verifyZeroInteractions(kafkaService);
+  }
+
+  @Test
+  public void getShouldProperlyReturnActiveProfile() throws Exception {
+    when(userSettingsClient.findOne(user1, AlertsUIServiceImpl.ALERT_USER_SETTING_TYPE)).thenReturn(Optional.of(user1AlertUserSettings));
+
+    AlertsUIUserSettings expectedAlertsUIUserSettings = new AlertsUIUserSettings();
+    expectedAlertsUIUserSettings.setTableColumns(Collections.singletonList("user1_field"));
+    assertEquals(expectedAlertsUIUserSettings, alertsUIService.getAlertsUIUserSettings().get());
+    verify(userSettingsClient, times(1)).findOne(user1, AlertsUIServiceImpl.ALERT_USER_SETTING_TYPE);
+    verifyNoMoreInteractions(userSettingsClient);
+  }
+
+  @Test
+  public void findAllShouldProperlyReturnActiveProfiles() throws Exception {
+    AlertsUIUserSettings alertsProfile1 = new AlertsUIUserSettings();
+    alertsProfile1.setUser(user1);
+    AlertsUIUserSettings alertsProfile2 = new AlertsUIUserSettings();
+    alertsProfile2.setUser(user1);
+    when(userSettingsClient.findAll(AlertsUIServiceImpl.ALERT_USER_SETTING_TYPE))
+            .thenReturn(new HashMap<String, Optional<String>>() {{
+              put(user1,  Optional.of(user1AlertUserSettings));
+              put(user2, Optional.of(user2AlertUserSettings));
+              }});
+
+    AlertsUIUserSettings expectedAlertsUIUserSettings1 = new AlertsUIUserSettings();
+    expectedAlertsUIUserSettings1.setTableColumns(Collections.singletonList("user1_field"));
+    AlertsUIUserSettings expectedAlertsUIUserSettings2 = new AlertsUIUserSettings();
+    expectedAlertsUIUserSettings2.setTableColumns(Collections.singletonList("user2_field"));
+    Map<String, AlertsUIUserSettings> actualAlertsProfiles = alertsUIService.findAllAlertsUIUserSettings();
+    assertEquals(2, actualAlertsProfiles.size());
+    assertEquals(expectedAlertsUIUserSettings1, actualAlertsProfiles.get(user1));
+    assertEquals(expectedAlertsUIUserSettings2, actualAlertsProfiles.get(user2));
+
+    verify(userSettingsClient, times(1)).findAll(AlertsUIServiceImpl.ALERT_USER_SETTING_TYPE);
+    verifyNoMoreInteractions(userSettingsClient);
+  }
+
+  @Test
+  public void saveShouldProperlySaveActiveProfile() throws Exception {
+    AlertsUIUserSettings alertsUIUserSettings = new AlertsUIUserSettings();
+    alertsUIUserSettings.setTableColumns(Collections.singletonList("user1_field"));
+
+    alertsUIService.saveAlertsUIUserSettings(alertsUIUserSettings);
+
+    String expectedAlertUserSettings = _mapper.get().writeValueAsString(alertsUIUserSettings);
+    verify(userSettingsClient, times(1))
+            .save(user1, AlertsUIServiceImpl.ALERT_USER_SETTING_TYPE, expectedAlertUserSettings);
+    verifyNoMoreInteractions(userSettingsClient);
+  }
+
+  @Test
+  public void deleteShouldProperlyDeleteActiveProfile() throws Exception {
+    assertTrue(alertsUIService.deleteAlertsUIUserSettings(user1));
+
+    doThrow(new IOException()).when(userSettingsClient).delete(user1, AlertsUIServiceImpl.ALERT_USER_SETTING_TYPE);
+    assertFalse(alertsUIService.deleteAlertsUIUserSettings(user1));
+
+    verify(userSettingsClient, times(2)).delete(user1, AlertsUIServiceImpl.ALERT_USER_SETTING_TYPE);
+    verifyNoMoreInteractions(userSettingsClient);
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/SearchServiceImplTest.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/SearchServiceImplTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/SearchServiceImplTest.java
index 1d8f182..82ca0e9 100644
--- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/SearchServiceImplTest.java
+++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/SearchServiceImplTest.java
@@ -18,6 +18,7 @@
 package org.apache.metron.rest.service.impl;
 
 import static org.apache.metron.rest.MetronRestConstants.INDEX_WRITER_NAME;
+import static org.apache.metron.rest.MetronRestConstants.SEARCH_FACET_FIELDS_SPRING_PROPERTY;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
@@ -27,10 +28,14 @@ import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Optional;
+
 import org.apache.metron.indexing.dao.IndexDao;
 import org.apache.metron.indexing.dao.search.InvalidSearchException;
 import org.apache.metron.indexing.dao.search.SearchRequest;
 import org.apache.metron.rest.RestException;
+import org.apache.metron.rest.model.AlertsUIUserSettings;
+import org.apache.metron.rest.service.AlertsUIService;
 import org.apache.metron.rest.service.SearchService;
 import org.apache.metron.rest.service.SensorIndexingConfigService;
 import org.junit.Before;
@@ -47,6 +52,7 @@ public class SearchServiceImplTest {
   IndexDao dao;
   Environment environment;
   SensorIndexingConfigService sensorIndexingConfigService;
+  AlertsUIService alertsUIService;
   SearchService searchService;
 
   @Before
@@ -54,7 +60,8 @@ public class SearchServiceImplTest {
     dao = mock(IndexDao.class);
     environment = mock(Environment.class);
     sensorIndexingConfigService = mock(SensorIndexingConfigService.class);
-    searchService = new SearchServiceImpl(dao, environment, sensorIndexingConfigService);
+    alertsUIService = mock(AlertsUIService.class);
+    searchService = new SearchServiceImpl(dao, environment, sensorIndexingConfigService, alertsUIService);
   }
 
 
@@ -74,13 +81,63 @@ public class SearchServiceImplTest {
   }
 
   @Test
+  public void searchShouldProperlySearchWithEmptyDefaultFacetFields() throws Exception {
+    when(environment.getProperty(SEARCH_FACET_FIELDS_SPRING_PROPERTY, String.class, ""))
+        .thenReturn("");
+
+    SearchRequest searchRequest = new SearchRequest();
+    searchRequest.setIndices(Arrays.asList("bro", "snort", "metaalert"));
+    searchService.search(searchRequest);
+
+    SearchRequest expectedSearchRequest = new SearchRequest();
+    expectedSearchRequest.setIndices(Arrays.asList("bro", "snort", "metaalert"));
+    verify(dao).search(eq(expectedSearchRequest));
+  }
+
+  @Test
+  public void searchShouldProperlySearchDefaultFacetFields() throws Exception {
+    when(environment.getProperty(SEARCH_FACET_FIELDS_SPRING_PROPERTY, String.class, ""))
+        .thenReturn("source:type,ip_src_addr");
+    when(alertsUIService.getAlertsUIUserSettings()).thenReturn(Optional.empty());
+
+    SearchRequest searchRequest = new SearchRequest();
+    searchRequest.setIndices(Arrays.asList("bro", "snort", "metaalert"));
+    searchRequest.setFacetFields(new ArrayList<>());
+    searchService.search(searchRequest);
+
+    SearchRequest expectedSearchRequest = new SearchRequest();
+    expectedSearchRequest.setIndices(Arrays.asList("bro", "snort", "metaalert"));
+    expectedSearchRequest.setFacetFields(Arrays.asList("source:type", "ip_src_addr"));
+    verify(dao).search(eq(expectedSearchRequest));
+  }
+
+  @Test
+  public void searchShouldProperlySearchWithUserSettingsFacetFields() throws Exception {
+    AlertsUIUserSettings alertsUIUserSettings = new AlertsUIUserSettings();
+    alertsUIUserSettings.setFacetFields(Arrays.asList("source:type", "ip_dst_addr"));
+    when(alertsUIService.getAlertsUIUserSettings()).thenReturn(Optional.of(alertsUIUserSettings));
+
+    SearchRequest searchRequest = new SearchRequest();
+    searchRequest.setIndices(Arrays.asList("bro", "snort", "metaalert"));
+    searchRequest.setFacetFields(new ArrayList<>());
+    searchService.search(searchRequest);
+
+    SearchRequest expectedSearchRequest = new SearchRequest();
+    expectedSearchRequest.setIndices(Arrays.asList("bro", "snort", "metaalert"));
+    expectedSearchRequest.setFacetFields(Arrays.asList("source:type", "ip_dst_addr"));
+    verify(dao).search(eq(expectedSearchRequest));
+  }
+
+  @Test
   public void searchShouldProperlySearch() throws Exception {
     SearchRequest searchRequest = new SearchRequest();
     searchRequest.setIndices(Arrays.asList("bro"));
+    searchRequest.setFacetFields(Arrays.asList("ip_src_addr"));
     searchService.search(searchRequest);
 
     SearchRequest expectedSearchRequest = new SearchRequest();
     expectedSearchRequest.setIndices(Arrays.asList("bro"));
+    expectedSearchRequest.setFacetFields(Arrays.asList("ip_src_addr"));
     verify(dao).search(eq(expectedSearchRequest));
 
     verifyNoMoreInteractions(dao);
@@ -94,6 +151,7 @@ public class SearchServiceImplTest {
 
     SearchRequest searchRequest = new SearchRequest();
     searchRequest.setIndices(Arrays.asList("bro"));
+    searchRequest.setFacetFields(Arrays.asList("ip_src_addr"));
     searchService.search(searchRequest);
   }
 

http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchDao.java b/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchDao.java
index 650462e..9bb109d 100644
--- a/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchDao.java
+++ b/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchDao.java
@@ -37,7 +37,6 @@ import org.apache.metron.indexing.dao.search.SearchResult;
 import org.apache.metron.indexing.dao.search.SortField;
 import org.apache.metron.indexing.dao.search.SortOrder;
 import org.apache.metron.indexing.dao.update.Document;
-import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest;
 import org.elasticsearch.action.bulk.BulkRequestBuilder;
 import org.elasticsearch.action.bulk.BulkResponse;
 import org.elasticsearch.action.index.IndexRequest;
@@ -45,8 +44,6 @@ import org.elasticsearch.action.index.IndexResponse;
 import org.elasticsearch.action.search.SearchRequestBuilder;
 import org.elasticsearch.action.support.replication.ReplicationResponse.ShardInfo;
 import org.elasticsearch.client.transport.TransportClient;
-import org.elasticsearch.cluster.metadata.MappingMetaData;
-import org.elasticsearch.common.collect.ImmutableOpenMap;
 import org.elasticsearch.index.mapper.LegacyIpFieldMapper;
 import org.elasticsearch.index.query.IdsQueryBuilder;
 import org.elasticsearch.index.query.QueryBuilder;
@@ -194,7 +191,7 @@ public class ElasticsearchDao implements IndexDao {
             .from(searchRequest.getFrom())
             .query(queryBuilder)
             .trackScores(true);
-    Optional<List<String>> fields = searchRequest.getFields();
+    List<String> fields = searchRequest.getFields();
     // column metadata needed to understand the type of each sort field
     Map<String, FieldType> meta;
     try {
@@ -227,18 +224,18 @@ public class ElasticsearchDao implements IndexDao {
     }
 
     // handle search fields
-    if (fields.isPresent()) {
+    if (fields != null) {
       searchBuilder.fetchSource("*", null);
     } else {
       searchBuilder.fetchSource(true);
     }
 
-    Optional<List<String>> facetFields = searchRequest.getFacetFields();
+    List<String> facetFields = searchRequest.getFacetFields();
 
     // handle facet fields
-    if (searchRequest.getFacetFields().isPresent()) {
+    if (facetFields != null) {
       // https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/_bucket_aggregations.html
-      for(String field : searchRequest.getFacetFields().get()) {
+      for(String field : facetFields) {
         String name = getFacetAggregationName(field);
         TermsAggregationBuilder terms = AggregationBuilders.terms( name).field(field);
                // new TermsBuilder(name).field(field);
@@ -282,8 +279,8 @@ public class ElasticsearchDao implements IndexDao {
     searchResponse.setResults(results);
 
     // handle facet fields
-    if (searchRequest.getFacetFields().isPresent()) {
-      List<String> facetFields = searchRequest.getFacetFields().get();
+    if (searchRequest.getFacetFields() != null) {
+      List<String> facetFields = searchRequest.getFacetFields();
       Map<String, FieldType> commonColumnMetadata;
       try {
         commonColumnMetadata = getColumnMetadata(searchRequest.getIndices());
@@ -674,14 +671,14 @@ public class ElasticsearchDao implements IndexDao {
     return searchResultGroups;
   }
 
-  private SearchResult getSearchResult(SearchHit searchHit, Optional<List<String>> fields) {
+  private SearchResult getSearchResult(SearchHit searchHit, List<String> fields) {
     SearchResult searchResult = new SearchResult();
     searchResult.setId(searchHit.getId());
     Map<String, Object> source;
-    if (fields.isPresent()) {
+    if (fields != null) {
       Map<String, Object> resultSourceAsMap = searchHit.getSourceAsMap();
       source = new HashMap<>();
-      fields.get().forEach(field -> {
+      fields.forEach(field -> {
         source.put(field, resultSourceAsMap.get(field));
       });
     } else {

http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-platform/metron-hbase/src/main/java/org/apache/metron/hbase/client/UserSettingsClient.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-hbase/src/main/java/org/apache/metron/hbase/client/UserSettingsClient.java b/metron-platform/metron-hbase/src/main/java/org/apache/metron/hbase/client/UserSettingsClient.java
new file mode 100644
index 0000000..f492497
--- /dev/null
+++ b/metron-platform/metron-hbase/src/main/java/org/apache/metron/hbase/client/UserSettingsClient.java
@@ -0,0 +1,175 @@
+/**
+ * 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.hbase.client;
+
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.client.Delete;
+import org.apache.hadoop.hbase.client.Get;
+import org.apache.hadoop.hbase.client.HTableInterface;
+import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.Result;
+import org.apache.hadoop.hbase.client.ResultScanner;
+import org.apache.hadoop.hbase.client.Scan;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.metron.hbase.TableProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+public class UserSettingsClient {
+
+  private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  public static String USER_SETTINGS_HBASE_TABLE = "user.settings.hbase.table";
+  public static String USER_SETTINGS_HBASE_CF = "user.settings.hbase.cf";
+
+  private HTableInterface userSettingsTable;
+  private byte[] cf;
+  private Supplier<Map<String, Object>> globalConfigSupplier;
+  private TableProvider tableProvider;
+
+  public UserSettingsClient() {
+  }
+
+  public UserSettingsClient(Supplier<Map<String, Object>> globalConfigSupplier, TableProvider tableProvider) {
+    this.globalConfigSupplier = globalConfigSupplier;
+    this.tableProvider = tableProvider;
+  }
+
+  public UserSettingsClient(HTableInterface userSettingsTable, byte[] cf) {
+    this.userSettingsTable = userSettingsTable;
+    this.cf = cf;
+  }
+
+  public synchronized void init(Supplier<Map<String, Object>> globalConfigSupplier, TableProvider tableProvider) {
+    if (this.userSettingsTable == null) {
+      Map<String, Object> globalConfig = globalConfigSupplier.get();
+      if(globalConfig == null) {
+        throw new IllegalStateException("Cannot find the global config.");
+      }
+      String table = (String)globalConfig.get(USER_SETTINGS_HBASE_TABLE);
+      String cf = (String) globalConfigSupplier.get().get(USER_SETTINGS_HBASE_CF);
+      if(table == null || cf == null) {
+        throw new IllegalStateException("You must configure " + USER_SETTINGS_HBASE_TABLE + " and " + USER_SETTINGS_HBASE_CF + " in the global config.");
+      }
+      try {
+        userSettingsTable = tableProvider.getTable(HBaseConfiguration.create(), table);
+        this.cf = cf.getBytes();
+      } catch (IOException e) {
+        throw new IllegalStateException("Unable to initialize HBaseDao: " + e.getMessage(), e);
+      }
+
+    }
+  }
+
+  public HTableInterface getTableInterface() {
+    if(userSettingsTable == null) {
+      init(globalConfigSupplier, tableProvider);
+    }
+    return userSettingsTable;
+  }
+
+  public Map<String, String> findOne(String user) throws IOException {
+    Result result = getResult(user);
+    return getAllUserSettings(result);
+  }
+
+  public Optional<String> findOne(String user, String type) throws IOException {
+    Result result = getResult(user);
+    return getUserSettings(result, type);
+  }
+
+  public Map<String, Map<String, String>> findAll() throws IOException {
+    Scan scan = new Scan();
+    ResultScanner results = getTableInterface().getScanner(scan);
+    Map<String, Map<String, String>> allUserSettings = new HashMap<>();
+    for (Result result : results) {
+      allUserSettings.put(new String(result.getRow()), getAllUserSettings(result));
+    }
+    return allUserSettings;
+  }
+
+  public Map<String, Optional<String>> findAll(String type) throws IOException {
+    Scan scan = new Scan();
+    ResultScanner results = getTableInterface().getScanner(scan);
+    Map<String, Optional<String>> allUserSettings = new HashMap<>();
+    for (Result result : results) {
+      allUserSettings.put(new String(result.getRow()), getUserSettings(result, type));
+    }
+    return allUserSettings;
+  }
+
+  public void save(String user, String type, String userSettings) throws IOException {
+    byte[] rowKey = Bytes.toBytes(user);
+    Put put = new Put(rowKey);
+    put.addColumn(cf, Bytes.toBytes(type), Bytes.toBytes(userSettings));
+    getTableInterface().put(put);
+  }
+
+  public void delete(String user) throws IOException {
+    Delete delete = new Delete(Bytes.toBytes(user));
+    getTableInterface().delete(delete);
+  }
+
+  public void delete(String user, String type) throws IOException {
+    Delete delete = new Delete(Bytes.toBytes(user));
+    delete.addColumn(cf, Bytes.toBytes(type));
+    getTableInterface().delete(delete);
+  }
+
+  private Result getResult(String user) throws IOException {
+    byte[] rowKey = Bytes.toBytes(user);
+    Get get = new Get(rowKey);
+    get.addFamily(cf);
+    return getTableInterface().get(get);
+  }
+
+  private Optional<String> getUserSettings(Result result, String type) throws IOException {
+    Optional<String> userSettings = Optional.empty();
+    if (result != null) {
+      byte[] value = result.getValue(cf, Bytes.toBytes(type));
+      if (value != null) {
+        userSettings = Optional.of(new String(value, StandardCharsets.UTF_8));
+      }
+    }
+    return userSettings;
+  }
+
+  public Map<String, String> getAllUserSettings(Result result) {
+    if (result == null) {
+      return new HashMap<>();
+    }
+    NavigableMap<byte[], byte[]> columns = result.getFamilyMap(cf);
+    if(columns == null || columns.size() == 0) {
+      return new HashMap<>();
+    }
+    Map<String, String> userSettingsMap = new HashMap<>();
+    for(Map.Entry<byte[], byte[]> column: columns.entrySet()) {
+      userSettingsMap.put(new String(column.getKey(), StandardCharsets.UTF_8), new String(column.getValue(), StandardCharsets.UTF_8));
+    }
+    return userSettingsMap;
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/client/UserSettingsClientTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/client/UserSettingsClientTest.java b/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/client/UserSettingsClientTest.java
new file mode 100644
index 0000000..55a28ad
--- /dev/null
+++ b/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/client/UserSettingsClientTest.java
@@ -0,0 +1,101 @@
+/**
+ * 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.hbase.client;
+
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.hadoop.hbase.client.Get;
+import org.apache.hadoop.hbase.client.HTableInterface;
+import org.apache.hadoop.hbase.client.Result;
+import org.apache.hadoop.hbase.client.ResultScanner;
+import org.apache.hadoop.hbase.client.Scan;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import static org.apache.metron.hbase.client.UserSettingsClient.USER_SETTINGS_HBASE_CF;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class UserSettingsClientTest {
+
+  @Rule
+  public final ExpectedException exception = ExpectedException.none();
+
+  private static ThreadLocal<ObjectMapper> _mapper = ThreadLocal.withInitial(() ->
+          new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL));
+
+  private HTableInterface userSettingsTable;
+  private Supplier<Map<String, Object>> globalConfigSupplier;
+  private UserSettingsClient userSettingsClient;
+  private static byte[] cf = Bytes.toBytes("cf");
+
+  @Before
+  public void setUp() throws Exception {
+    userSettingsTable = mock(HTableInterface.class);
+    globalConfigSupplier = () -> new HashMap<String, Object>() {{
+      put(USER_SETTINGS_HBASE_CF, "cf");
+    }};
+  }
+
+  @Test
+  public void shouldFindOne() throws Exception {
+    Result result = mock(Result.class);
+    when(result.getValue(cf, Bytes.toBytes("type"))).thenReturn("userSettings1String".getBytes());
+    Get get = new Get("user1".getBytes());
+    get.addFamily(cf);
+    when(userSettingsTable.get(get)).thenReturn(result);
+
+    UserSettingsClient userSettingsClient = new UserSettingsClient(userSettingsTable, cf);
+    Assert.assertEquals("userSettings1String", userSettingsClient.findOne("user1", "type").get());
+    assertFalse(userSettingsClient.findOne("missingUser", "type").isPresent());
+  }
+
+  @Test
+  public void shouldFindAll() throws Exception {
+    ResultScanner resultScanner = mock(ResultScanner.class);
+    Result result1 = mock(Result.class);
+    Result result2 = mock(Result.class);
+    when(result1.getRow()).thenReturn("user1".getBytes());
+    when(result2.getRow()).thenReturn("user2".getBytes());
+    when(result1.getValue(cf, Bytes.toBytes("type"))).thenReturn("userSettings1String".getBytes());
+    when(result2.getValue(cf, Bytes.toBytes("type"))).thenReturn("userSettings2String".getBytes());
+    when(resultScanner.iterator()).thenReturn(Arrays.asList(result1, result2).iterator());
+    when(userSettingsTable.getScanner(any(Scan.class))).thenReturn(resultScanner);
+
+    UserSettingsClient userSettingsClient = new UserSettingsClient(userSettingsTable, cf);
+    assertEquals(new HashMap<String, Optional<String>>() {{
+      put("user1", Optional.of("userSettings1String"));
+      put("user2", Optional.of("userSettings2String"));
+    }}, userSettingsClient.findAll("type"));
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/mock/MockHTable.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/mock/MockHTable.java b/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/mock/MockHTable.java
index e75b533..484d0e8 100644
--- a/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/mock/MockHTable.java
+++ b/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/mock/MockHTable.java
@@ -534,7 +534,12 @@ public class MockHTable implements HTableInterface {
 
   @Override
   public void delete(Delete delete) throws IOException {
-    throw new UnsupportedOperationException();
+    byte[] row = delete.getRow();
+    if (data.containsKey(row)) {
+      data.remove(row);
+    } else {
+      throw new IOException();
+    }
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/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 f4b6196..26c566c 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
@@ -19,8 +19,8 @@
 package org.apache.metron.indexing.dao.search;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
-import java.util.Optional;
 
 public class SearchRequest {
 
@@ -38,7 +38,6 @@ public class SearchRequest {
     defaultSortField.setSortOrder(SortOrder.DESC.toString());
     sort = new ArrayList<>();
     sort.add(defaultSortField);
-    facetFields = new ArrayList<>();
   }
 
   /**
@@ -101,16 +100,16 @@ public class SearchRequest {
     this.sort = sort;
   }
 
-  public Optional<List<String>> getFields() {
-    return fields == null || fields.size() == 0 ? Optional.empty() : Optional.of(fields);
+  public List<String> getFields() {
+    return fields;
   }
 
   public void setFields(List<String> fields) {
     this.fields = fields;
   }
 
-  public Optional<List<String>> getFacetFields() {
-    return facetFields == null || facetFields.size() == 0 ? Optional.empty() : Optional.of(facetFields);
+  public List<String> getFacetFields() {
+    return facetFields;
   }
 
   public void setFacetFields(List<String> facetFields) {
@@ -128,10 +127,11 @@ public class SearchRequest {
 
     SearchRequest that = (SearchRequest) o;
 
-    return indices != null ? indices.equals(that.indices) : that.indices == null &&
+    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) &&
+        (fields != null ? fields.equals(that.fields) : that.fields == null) &&
         (facetFields != null ? facetFields.equals(that.facetFields) : that.facetFields == null);
   }
 
@@ -142,6 +142,7 @@ public class SearchRequest {
     result = 31 * result + getSize();
     result = 31 * result + getFrom();
     result = 31 * result + (sort != null ? sort.hashCode() : 0);
+    result = 31 * result + (fields != null ? fields.hashCode() : 0);
     result = 31 * result + (facetFields != null ? facetFields.hashCode() : 0);
     return result;
   }

http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SortField.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SortField.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SortField.java
index a3473fc..4dd9e82 100644
--- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SortField.java
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SortField.java
@@ -36,4 +36,19 @@ public class SortField {
   public void setSortOrder(String sortOrder) {
     this.sortOrder = SortOrder.fromString(sortOrder);
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    SortField that = (SortField) o;
+
+    return (field != null ? field.equals(that.field) : that.field == null) &&
+            (sortOrder != null ? sortOrder.equals(that.sortOrder) : that.sortOrder == null);
+  }
 }


Mime
View raw message