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 2018/10/04 22:16:34 UTC

[1/3] metron git commit: METRON-1771 Update REST endpoints to support eventually consistent UI updates (merrimanr) closes apache/metron#1190

Repository: metron
Updated Branches:
  refs/heads/master 7e222fa47 -> de533063c


http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrUpdateDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrUpdateDao.java b/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrUpdateDao.java
index 2f83921..54b5b64 100644
--- a/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrUpdateDao.java
+++ b/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrUpdateDao.java
@@ -56,7 +56,7 @@ public class SolrUpdateDao implements UpdateDao {
   }
 
   @Override
-  public void update(Document update, Optional<String> rawIndex) throws IOException {
+  public Document update(Document update, Optional<String> rawIndex) throws IOException {
     Document newVersion = update;
     // Handle any case where we're given comments in Map form, instead of raw String
     Object commentsObj = update.getDocument().get(COMMENTS_FIELD);
@@ -79,10 +79,11 @@ public class SolrUpdateDao implements UpdateDao {
     } catch (SolrServerException e) {
       throw new IOException(e);
     }
+    return newVersion;
   }
 
   @Override
-  public void batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
+  public Map<Document, Optional<String>> batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
     // updates with a collection specified
     Map<String, Collection<SolrInputDocument>> solrCollectionUpdates = new HashMap<>();
     Set<String> collectionsUpdated = new HashSet<>();
@@ -117,18 +118,20 @@ public class SolrUpdateDao implements UpdateDao {
     } catch (SolrServerException e) {
       throw new IOException(e);
     }
+    return updates;
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
+  public Document addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
     Document latest = retrieveLatestDao.getLatest(request.getGuid(), request.getSensorType());
-    addCommentToAlert(request, latest);
+    return addCommentToAlert(request, latest);
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
-    if (latest == null) {
-      return;
+  public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
+    if (latest == null || latest.getDocument() == null) {
+      throw new IOException(String.format("Unable to add comment. Document with guid %s cannot be found.",
+              request.getGuid()));
     }
 
     @SuppressWarnings("unchecked")
@@ -149,21 +152,22 @@ public class SolrUpdateDao implements UpdateDao {
 
     Document newVersion = new Document(latest);
     newVersion.getDocument().put(COMMENTS_FIELD, commentStrs);
-    update(newVersion, Optional.empty());
+    return update(newVersion, Optional.empty());
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request)
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request)
       throws IOException {
     Document latest = retrieveLatestDao.getLatest(request.getGuid(), request.getSensorType());
-    removeCommentFromAlert(request, latest);
+    return removeCommentFromAlert(request, latest);
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest)
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest)
       throws IOException {
-    if (latest == null) {
-      return;
+    if (latest == null || latest.getDocument() == null) {
+      throw new IOException(String.format("Unable to remove comment. Document with guid %s cannot be found.",
+              request.getGuid()));
     }
 
     @SuppressWarnings("unchecked")
@@ -171,8 +175,8 @@ public class SolrUpdateDao implements UpdateDao {
         .get(COMMENTS_FIELD);
     // Can't remove anything if there's nothing there
     if (commentMap == null) {
-      LOG.debug("Provided alert had no comments to be able to remove from");
-      return;
+      throw new IOException(String.format("Unable to remove comment. Document with guid %s has no comments.",
+              request.getGuid()));
     }
     List<Map<String, Object>> originalComments = new ArrayList<>(commentMap);
     List<AlertComment> comments = new ArrayList<>();
@@ -186,7 +190,7 @@ public class SolrUpdateDao implements UpdateDao {
         .collect(Collectors.toList());
     Document newVersion = new Document(latest);
     newVersion.getDocument().put(COMMENTS_FIELD, commentsAsJson);
-    update(newVersion, Optional.empty());
+    return update(newVersion, Optional.empty());
   }
 
   public void convertCommentsToRaw(Map<String,Object> source) {

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-solr/src/test/java/org/apache/metron/solr/dao/SolrMetaAlertDaoTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-solr/src/test/java/org/apache/metron/solr/dao/SolrMetaAlertDaoTest.java b/metron-platform/metron-solr/src/test/java/org/apache/metron/solr/dao/SolrMetaAlertDaoTest.java
index 43bf1b1..8920d5a 100644
--- a/metron-platform/metron-solr/src/test/java/org/apache/metron/solr/dao/SolrMetaAlertDaoTest.java
+++ b/metron-platform/metron-solr/src/test/java/org/apache/metron/solr/dao/SolrMetaAlertDaoTest.java
@@ -86,31 +86,38 @@ public class SolrMetaAlertDaoTest {
       }
 
       @Override
-      public void update(Document update, Optional<String> index) {
+      public Document update(Document update, Optional<String> index) {
+        return null;
       }
 
       @Override
-      public void batchUpdate(Map<Document, Optional<String>> updates) {
+      public Map<Document, Optional<String>> batchUpdate(Map<Document, Optional<String>> updates) {
+        return null;
       }
 
       @Override
-      public void addCommentToAlert(CommentAddRemoveRequest request) {
+      public Document addCommentToAlert(CommentAddRemoveRequest request) {
+        return null;
       }
 
       @Override
-      public void removeCommentFromAlert(CommentAddRemoveRequest request) {
+      public Document removeCommentFromAlert(CommentAddRemoveRequest request) {
+        return null;
       }
 
       @Override
-      public void addCommentToAlert(CommentAddRemoveRequest request, Document latest) {
+      public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest) {
+        return null;
       }
 
       @Override
-      public void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) {
+      public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) {
+        return null;
       }
 
       @Override
-      public void patch(RetrieveLatestDao dao, PatchRequest request, Optional<Long> timestamp) {
+      public Document patch(RetrieveLatestDao dao, PatchRequest request, Optional<Long> timestamp) {
+        return null;
       }
 
       @Override

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-solr/src/test/java/org/apache/metron/solr/dao/SolrUpdateDaoTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-solr/src/test/java/org/apache/metron/solr/dao/SolrUpdateDaoTest.java b/metron-platform/metron-solr/src/test/java/org/apache/metron/solr/dao/SolrUpdateDaoTest.java
index bed43ae..21fc79b 100644
--- a/metron-platform/metron-solr/src/test/java/org/apache/metron/solr/dao/SolrUpdateDaoTest.java
+++ b/metron-platform/metron-solr/src/test/java/org/apache/metron/solr/dao/SolrUpdateDaoTest.java
@@ -39,10 +39,12 @@ import org.apache.metron.common.Constants;
 import org.apache.metron.common.configuration.IndexingConfigurations;
 import org.apache.metron.common.zookeeper.ConfigurationsCache;
 import org.apache.metron.indexing.dao.AccessConfig;
+import org.apache.metron.indexing.dao.UpdateDaoTest;
 import org.apache.metron.indexing.dao.search.AlertComment;
 import org.apache.metron.indexing.dao.update.Document;
 import org.apache.metron.indexing.dao.update.OriginalNotFoundException;
 import org.apache.metron.indexing.dao.update.PatchRequest;
+import org.apache.metron.indexing.dao.update.UpdateDao;
 import org.apache.metron.indexing.util.IndexingCacheUtil;
 import org.apache.metron.solr.matcher.SolrInputDocumentListMatcher;
 import org.apache.metron.solr.matcher.SolrInputDocumentMatcher;
@@ -51,19 +53,19 @@ import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.common.SolrInputDocument;
 import org.junit.Before;
 import org.junit.BeforeClass;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
+/**
+ * This class contains tests specific to the SolrUpdateDao implementation.  It also returns the SolrUpdateDao
+ * implementation to be used in UpdateDaoTest.  UpdateDaoTest contains a common set of tests that all Dao
+ * implementations must pass.
+ */
 @RunWith(PowerMockRunner.class)
 @PrepareForTest({CollectionAdminRequest.class})
-public class SolrUpdateDaoTest {
-
-  @Rule
-  public final ExpectedException exception = ExpectedException.none();
+public class SolrUpdateDaoTest extends UpdateDaoTest {
 
   private SolrClient client;
   private SolrRetrieveLatestDao solrRetrieveLatestDao;
@@ -238,4 +240,9 @@ public class SolrUpdateDaoTest {
     latest.getDocument().put("project", "metron");
     assertEquals(actual, latest);
   }
+
+  @Override
+  public UpdateDao getUpdateDao() {
+    return solrUpdateDao;
+  }
 }


[2/3] metron git commit: METRON-1771 Update REST endpoints to support eventually consistent UI updates (merrimanr) closes apache/metron#1190

Posted by rm...@apache.org.
http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/lucene/AbstractLuceneMetaAlertUpdateDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/lucene/AbstractLuceneMetaAlertUpdateDao.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/lucene/AbstractLuceneMetaAlertUpdateDao.java
index 4d48075..096baf1 100644
--- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/lucene/AbstractLuceneMetaAlertUpdateDao.java
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/lucene/AbstractLuceneMetaAlertUpdateDao.java
@@ -24,16 +24,17 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
 import org.apache.metron.common.Constants;
-import org.apache.metron.common.configuration.ConfigurationsUtils;
 import org.apache.metron.indexing.dao.RetrieveLatestDao;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertConfig;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertConstants;
@@ -80,15 +81,16 @@ public abstract class AbstractLuceneMetaAlertUpdateDao implements MetaAlertUpdat
    * @param retrieveLatestDao DAO to retrieve the item to be patched
    * @param request The patch request.
    * @param timestamp Optionally a timestamp to set. If not specified then current time is used.
+   * @return The patched document
    * @throws OriginalNotFoundException If no original document is found to patch.
    * @throws IOException If an error occurs performing the patch.
    */
   @Override
-  public void patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request,
+  public Document patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request,
       Optional<Long> timestamp)
       throws OriginalNotFoundException, IOException {
     if (isPatchAllowed(request)) {
-      updateDao.patch(retrieveLatestDao, request, timestamp);
+      return updateDao.patch(retrieveLatestDao, request, timestamp);
     } else {
       throw new IllegalArgumentException(
           "Meta alert patches are not allowed for /alert or /status paths.  "
@@ -97,7 +99,7 @@ public abstract class AbstractLuceneMetaAlertUpdateDao implements MetaAlertUpdat
   }
 
   @Override
-  public void batchUpdate(Map<Document, Optional<String>> updates) {
+  public Map<Document, Optional<String>> batchUpdate(Map<Document, Optional<String>> updates) {
     throw new UnsupportedOperationException("Meta alerts do not allow for bulk updates");
   }
 
@@ -170,21 +172,65 @@ public abstract class AbstractLuceneMetaAlertUpdateDao implements MetaAlertUpdat
     return updates;
   }
 
+  /**
+   * Adds alerts to a metaalert, based on a list of GetRequests provided for retrieval.
+   * @param metaAlertGuid The GUID of the metaalert to be given new children.
+   * @param alertRequests GetRequests for the appropriate alerts to add.
+   * @return The updated metaalert with alerts added.
+   */
+  @Override
+  public Document addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
+          throws IOException {
+    Document metaAlert = retrieveLatestDao
+            .getLatest(metaAlertGuid, MetaAlertConstants.METAALERT_TYPE);
+    if (metaAlert == null) {
+      throw new IOException(String.format("Unable to add alerts to meta alert.  Meta alert with guid %s cannot be found.",
+              metaAlertGuid));
+    }
+    if (MetaAlertStatus.ACTIVE.getStatusString()
+            .equals(metaAlert.getDocument().get(MetaAlertConstants.STATUS_FIELD))) {
+      Iterable<Document> alerts = retrieveLatestDao.getAllLatest(alertRequests);
+      Set<String> missingAlerts = getMissingAlerts(alertRequests, alerts);
+      if (!missingAlerts.isEmpty()) {
+        throw new IOException(String.format("Unable to add alerts to meta alert.  Alert with guid %s cannot be found.",
+                missingAlerts.iterator().next()));
+      }
+      Map<Document, Optional<String>> updates = buildAddAlertToMetaAlertUpdates(metaAlert, alerts);
+      update(updates);
+      return metaAlert;
+    } else {
+      throw new IllegalStateException("Adding alerts to an INACTIVE meta alert is not allowed");
+    }
+  }
+
+  /**
+   * Removes alerts from a metaalert, based on a list of GetRequests provided for retrieval.
+   * @param metaAlertGuid The GUID of the metaalert to remove children from.
+   * @param alertRequests A list of GetReqests that will provide the alerts to remove
+   * @return The updated metaalert with alerts removed.
+   * @throws IllegalStateException If the metaalert is inactive.
+   */
   @Override
   @SuppressWarnings("unchecked")
-  public boolean removeAlertsFromMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
-      throws IOException {
+  public Document removeAlertsFromMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
+      throws IOException, IllegalStateException {
     Document metaAlert = retrieveLatestDao
         .getLatest(metaAlertGuid, MetaAlertConstants.METAALERT_TYPE);
     if (metaAlert == null) {
-      return false;
+      throw new IOException(String.format("Unable to remove alerts from meta alert.  Meta alert with guid %s cannot be found.",
+              metaAlertGuid));
     }
     if (MetaAlertStatus.ACTIVE.getStatusString()
         .equals(metaAlert.getDocument().get(MetaAlertConstants.STATUS_FIELD))) {
       Iterable<Document> alerts = retrieveLatestDao.getAllLatest(alertRequests);
+      Set<String> missingAlerts = getMissingAlerts(alertRequests, alerts);
+      if (!missingAlerts.isEmpty()) {
+        throw new IOException(String.format("Unable to remove alerts from meta alert.  Alert with guid %s cannot be found.",
+                missingAlerts.iterator().next()));
+      }
       Map<Document, Optional<String>> updates = buildRemoveAlertsFromMetaAlert(metaAlert, alerts);
       update(updates);
-      return updates.size() != 0;
+      return metaAlert;
     } else {
       throw new IllegalStateException("Removing alerts from an INACTIVE meta alert is not allowed");
     }
@@ -213,10 +259,14 @@ public abstract class AbstractLuceneMetaAlertUpdateDao implements MetaAlertUpdat
   }
 
   @Override
-  public boolean updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status)
+  public Document updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status)
       throws IOException {
     Document metaAlert = retrieveLatestDao
         .getLatest(metaAlertGuid, MetaAlertConstants.METAALERT_TYPE);
+    if (metaAlert == null) {
+      throw new IOException(String.format("Unable to update meta alert status.  Meta alert with guid %s cannot be found.",
+              metaAlertGuid));
+    }
     String currentStatus = (String) metaAlert.getDocument().get(MetaAlertConstants.STATUS_FIELD);
     boolean metaAlertUpdated = !status.getStatusString().equals(currentStatus);
     if (metaAlertUpdated) {
@@ -231,7 +281,7 @@ public abstract class AbstractLuceneMetaAlertUpdateDao implements MetaAlertUpdat
       Map<Document, Optional<String>> updates = buildStatusChangeUpdates(metaAlert, alerts, status);
       update(updates);
     }
-    return metaAlertUpdated;
+    return metaAlert;
   }
 
   /**
@@ -334,4 +384,13 @@ public abstract class AbstractLuceneMetaAlertUpdateDao implements MetaAlertUpdat
     } // else we have no updates, so don't do anything
   }
 
+  protected Set<String> getMissingAlerts(List<GetRequest> alertRequests, Iterable<Document> results) throws IOException {
+    Set<String> requestGuids = alertRequests.stream().map(GetRequest::getGuid).collect(Collectors.toSet());
+    Set<String> resultGuids = StreamSupport.stream(results.spliterator(), false)
+            .map(Document::getGuid).collect(Collectors.toSet());
+    Set<String> missingGuids = new HashSet<>(requestGuids);
+    missingGuids.removeAll(resultGuids);
+    return missingGuids;
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/update/UpdateDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/update/UpdateDao.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/update/UpdateDao.java
index b5f38e4..8f6f6b0 100644
--- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/update/UpdateDao.java
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/update/UpdateDao.java
@@ -18,7 +18,6 @@
 package org.apache.metron.indexing.dao.update;
 
 import java.io.IOException;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
 import org.apache.metron.common.utils.JSONUtils;
@@ -33,25 +32,27 @@ public interface UpdateDao {
    *
    * @param update The document to replace from the index.
    * @param index The index where the document lives.
+   * @return The updated document
    * @throws IOException If an error occurs during the update.
    */
-  void update(Document update, Optional<String> index) throws IOException;
+  Document update(Document update, Optional<String> index) throws IOException;
 
   /**
    * Similar to the update method but accepts multiple documents and performs updates in batch.
    *
    * @param updates A map of the documents to update to the index where they live.
+   * @return The updated documents.
    * @throws IOException If an error occurs during the updates.
    */
-  void batchUpdate(Map<Document, Optional<String>> updates) throws IOException;
+  Map<Document, Optional<String>> batchUpdate(Map<Document, Optional<String>> updates) throws IOException;
 
-  void addCommentToAlert(CommentAddRemoveRequest request) throws IOException;
+  Document addCommentToAlert(CommentAddRemoveRequest request) throws IOException;
 
-  void removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException;
+  Document removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException;
 
-  void addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException;
+  Document addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException;
 
-  void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) throws IOException;
+  Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) throws IOException;
 
 
   /**
@@ -59,14 +60,15 @@ public interface UpdateDao {
    * https://tools.ietf.org/html/rfc6902)
    * @param request The patch request
    * @param timestamp Optionally a timestamp to set. If not specified then current time is used.
+   * @return The patched document.
    * @throws OriginalNotFoundException If the original is not found, then it cannot be patched.
    * @throws IOException If an error occurs while patching.
    */
-  default void patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request
+  default Document patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request
       , Optional<Long> timestamp
   ) throws OriginalNotFoundException, IOException {
     Document d = getPatchedDocument(retrieveLatestDao, request, timestamp);
-    update(d, Optional.ofNullable(request.getIndex()));
+    return update(d, Optional.ofNullable(request.getIndex()));
   }
 
   default Document getPatchedDocument(RetrieveLatestDao retrieveLatestDao, PatchRequest request,
@@ -94,15 +96,16 @@ public interface UpdateDao {
    * Replace a document in an index.
    * @param request The replacement request.
    * @param timestamp The timestamp (optional) of the update.  If not specified, then current time will be used.
+   * @return The replaced document.
    * @throws IOException If an error occurs during replacement.
    */
-  default void replace(ReplaceRequest request, Optional<Long> timestamp)
+  default Document replace(ReplaceRequest request, Optional<Long> timestamp)
       throws IOException {
     Document d = new Document(request.getReplacement(),
         request.getGuid(),
         request.getSensorType(),
         timestamp.orElse(System.currentTimeMillis())
     );
-    update(d, Optional.ofNullable(request.getIndex()));
+    return update(d, Optional.ofNullable(request.getIndex()));
   }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/InMemoryMetaAlertRetrieveLatestDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/InMemoryMetaAlertRetrieveLatestDao.java b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/InMemoryMetaAlertRetrieveLatestDao.java
new file mode 100644
index 0000000..28891d9
--- /dev/null
+++ b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/InMemoryMetaAlertRetrieveLatestDao.java
@@ -0,0 +1,49 @@
+/*
+ * 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.indexing;
+
+import org.apache.metron.indexing.dao.IndexDao;
+import org.apache.metron.indexing.dao.metaalert.MetaAlertRetrieveLatestDao;
+import org.apache.metron.indexing.dao.search.GetRequest;
+import org.apache.metron.indexing.dao.update.Document;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * This class is needed to compose an InMemoryMetaAlertUpdateDao implementation.  This allows the
+ * InMemoryMetaAlertUpdateDao class to extend AbstractLuceneMetaAlertUpdateDao and reuse common logic in that class.
+ */
+public class InMemoryMetaAlertRetrieveLatestDao implements MetaAlertRetrieveLatestDao {
+
+  private IndexDao indexDao;
+
+  public InMemoryMetaAlertRetrieveLatestDao(IndexDao indexDao) {
+    this.indexDao = indexDao;
+  }
+
+  @Override
+  public Document getLatest(String guid, String sensorType) throws IOException {
+    return indexDao.getLatest(guid, sensorType);
+  }
+
+  @Override
+  public Iterable<Document> getAllLatest(List<GetRequest> getRequests) throws IOException {
+    return indexDao.getAllLatest(getRequests);
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/HBaseDaoTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/HBaseDaoTest.java b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/HBaseDaoTest.java
new file mode 100644
index 0000000..d4823db
--- /dev/null
+++ b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/HBaseDaoTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.indexing.dao;
+
+import org.apache.metron.indexing.dao.update.UpdateDao;
+import org.junit.Before;
+
+/**
+ * This class returns the HBaseDao implementation to be used in UpdateDaoTest.  UpdateDaoTest contains a
+ * common set of tests that all Dao implementations must pass.
+ */
+public class HBaseDaoTest extends UpdateDaoTest{
+
+  private HBaseDao dao;
+
+  @Before
+  public void setup() {
+    dao = new HBaseDao();
+  }
+
+  @Override
+  public UpdateDao getUpdateDao() {
+    return dao;
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/InMemoryDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/InMemoryDao.java b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/InMemoryDao.java
index e306567..f49a6ad 100644
--- a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/InMemoryDao.java
+++ b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/InMemoryDao.java
@@ -248,7 +248,7 @@ public class InMemoryDao implements IndexDao {
   }
 
   @Override
-  public void update(Document update, Optional<String> index) throws IOException {
+  public Document update(Document update, Optional<String> index) throws IOException {
     for (Map.Entry<String, List<String>> kv : BACKING_STORE.entrySet()) {
       if (kv.getKey().startsWith(update.getSensorType())) {
         for (Iterator<String> it = kv.getValue().iterator(); it.hasNext(); ) {
@@ -261,13 +261,15 @@ public class InMemoryDao implements IndexDao {
         kv.getValue().add(JSONUtils.INSTANCE.toJSON(update.getDocument(), true));
       }
     }
+    return update;
   }
 
   @Override
-  public void batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
+  public Map<Document, Optional<String>> batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
     for (Map.Entry<Document, Optional<String>> update : updates.entrySet()) {
       update(update.getKey(), update.getValue());
     }
+    return updates;
   }
 
   @Override
@@ -293,19 +295,23 @@ public class InMemoryDao implements IndexDao {
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request) {
+  public Document addCommentToAlert(CommentAddRemoveRequest request) {
+    return null;
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request) {
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request) {
+    return null;
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request, Document latest) {
+  public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest) {
+    return null;
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) {
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) {
+    return null;
   }
 
   public static void setColumnMetadata(Map<String, Map<String, FieldType>> columnMetadata) {

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/InMemoryMetaAlertDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/InMemoryMetaAlertDao.java b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/InMemoryMetaAlertDao.java
index cb8837b..dbd3cb6 100644
--- a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/InMemoryMetaAlertDao.java
+++ b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/InMemoryMetaAlertDao.java
@@ -18,26 +18,24 @@
 
 package org.apache.metron.indexing.dao;
 
-import static org.apache.metron.common.Constants.GUID;
+import static org.apache.metron.common.Constants.SENSOR_TYPE;
 
-import com.google.common.collect.ImmutableList;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.stream.Collectors;
+import java.util.function.Supplier;
 import org.adrianwalker.multilinestring.Multiline;
 import org.apache.metron.common.utils.JSONUtils;
+import org.apache.metron.indexing.InMemoryMetaAlertRetrieveLatestDao;
+import org.apache.metron.indexing.dao.metaalert.MetaAlertConfig;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertConstants;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateRequest;
-import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateResponse;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertDao;
+import org.apache.metron.indexing.dao.metaalert.MetaAlertRetrieveLatestDao;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertStatus;
-import org.apache.metron.indexing.dao.metaalert.MetaScores;
+import org.apache.metron.indexing.dao.metaalert.MetaAlertUpdateDao;
 import org.apache.metron.indexing.dao.search.FieldType;
 import org.apache.metron.indexing.dao.search.GetRequest;
 import org.apache.metron.indexing.dao.search.GroupRequest;
@@ -46,20 +44,19 @@ import org.apache.metron.indexing.dao.search.InvalidCreateException;
 import org.apache.metron.indexing.dao.search.InvalidSearchException;
 import org.apache.metron.indexing.dao.search.SearchRequest;
 import org.apache.metron.indexing.dao.search.SearchResponse;
-import org.apache.metron.indexing.dao.search.SearchResult;
 import org.apache.metron.indexing.dao.update.CommentAddRemoveRequest;
 import org.apache.metron.indexing.dao.update.Document;
 import org.apache.metron.indexing.dao.update.OriginalNotFoundException;
 import org.apache.metron.indexing.dao.update.PatchRequest;
 import org.apache.metron.indexing.dao.update.ReplaceRequest;
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
 
 public class InMemoryMetaAlertDao implements MetaAlertDao {
 
-  public static Map<String, Collection<String>> METAALERT_STORE = new HashMap<>();
+  public static final String METAALERT_INDEX = "metaalert_index";
 
   private IndexDao indexDao;
+  private MetaAlertRetrieveLatestDao metaAlertRetrieveLatestDao;
+  private MetaAlertUpdateDao metaAlertUpdateDao;
   private int pageSize = 10;
 
   /**
@@ -97,6 +94,24 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
   @Override
   public void init(IndexDao indexDao, Optional<String> threatSort) {
     this.indexDao = indexDao;
+    this.metaAlertRetrieveLatestDao = new InMemoryMetaAlertRetrieveLatestDao(indexDao);
+    Supplier<Map<String, Object>> globalConfigSupplier = () -> new HashMap<>();
+    MetaAlertConfig config = new MetaAlertConfig(
+            METAALERT_INDEX,
+            null,
+            globalConfigSupplier
+    ) {
+      @Override
+      protected String getDefaultThreatTriageField() {
+        return MetaAlertConstants.THREAT_FIELD_DEFAULT;
+      }
+
+      @Override
+      protected String getDefaultSourceTypeField() {
+        return SENSOR_TYPE;
+      }
+    };
+    this.metaAlertUpdateDao = new InMemoryMetaAlertUpdateDao(indexDao, metaAlertRetrieveLatestDao, config, -1);
     // Ignore threatSort for test.
   }
 
@@ -112,12 +127,12 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
   }
 
   @Override
-  public void update(Document update, Optional<String> index) throws IOException {
-    indexDao.update(update, index);
+  public Document update(Document update, Optional<String> index) throws IOException {
+    return indexDao.update(update, index);
   }
 
   @Override
-  public void batchUpdate(Map<Document, Optional<String>> updates) {
+  public Map<Document, Optional<String>> batchUpdate(Map<Document, Optional<String>> updates) {
     throw new UnsupportedOperationException("InMemoryMetaAlertDao can't do bulk updates");
   }
 
@@ -128,19 +143,23 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request) {
+  public Document addCommentToAlert(CommentAddRemoveRequest request) {
+    return null;
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request) {
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request) {
+    return null;
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request, Document latest) {
+  public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest) {
+    return null;
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) {
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) {
+    return null;
   }
 
   @Override
@@ -149,15 +168,15 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
   }
 
   @Override
-  public void patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request,
+  public Document patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request,
       Optional<Long> timestamp)
       throws OriginalNotFoundException, IOException {
-    indexDao.patch(retrieveLatestDao, request, timestamp);
+    return indexDao.patch(retrieveLatestDao, request, timestamp);
   }
 
   @Override
-  public void replace(ReplaceRequest request, Optional<Long> timestamp) throws IOException {
-    indexDao.replace(request, timestamp);
+  public Document replace(ReplaceRequest request, Optional<Long> timestamp) throws IOException {
+    return indexDao.replace(request, timestamp);
   }
 
   @Override
@@ -174,145 +193,29 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
 
   @SuppressWarnings("unchecked")
   @Override
-  public MetaAlertCreateResponse createMetaAlert(MetaAlertCreateRequest request)
-      throws InvalidCreateException {
-    List<GetRequest> alertRequests = request.getAlerts();
-    if (alertRequests.isEmpty()) {
-      MetaAlertCreateResponse response = new MetaAlertCreateResponse();
-      response.setCreated(false);
-      return response;
-    }
-    // Build meta alert json.  Give it a reasonable GUID
-    JSONObject metaAlert = new JSONObject();
-    String metaAlertGuid =
-        "meta_" + (InMemoryDao.BACKING_STORE.get(getMetaAlertIndex()).size() + 1);
-    metaAlert.put(GUID, metaAlertGuid);
-
-    JSONArray groupsArray = new JSONArray();
-    groupsArray.addAll(request.getGroups());
-    metaAlert.put(MetaAlertConstants.GROUPS_FIELD, groupsArray);
-
-    // Retrieve the alert for each guid
-    // For the purpose of testing, we're just using guids for the alerts field and grabbing the scores.
-    JSONArray alertArray = new JSONArray();
-    List<Double> threatScores = new ArrayList<>();
-    Collection<String> alertGuids = new ArrayList<>();
-    for (GetRequest alertRequest : alertRequests) {
-      SearchRequest searchRequest = new SearchRequest();
-      searchRequest.setIndices(ImmutableList.of(alertRequest.getIndex().get()));
-      searchRequest.setQuery("guid:" + alertRequest.getGuid());
-      try {
-        SearchResponse searchResponse = search(searchRequest);
-        List<SearchResult> searchResults = searchResponse.getResults();
-        if (searchResults.size() > 1) {
-          throw new InvalidCreateException(
-              "Found more than one result for: " + alertRequest.getGuid() + ". Values: "
-                  + searchResults
-          );
-        }
-
-        if (searchResults.size() == 1) {
-          SearchResult result = searchResults.get(0);
-          alertArray.add(result.getSource());
-          Double threatScore = Double
-              .parseDouble(
-                  result.getSource().getOrDefault(MetaAlertConstants.THREAT_FIELD_DEFAULT, "0")
-                      .toString());
-
-          threatScores.add(threatScore);
-        }
-      } catch (InvalidSearchException e) {
-        throw new InvalidCreateException("Unable to find guid: " + alertRequest.getGuid(), e);
-      }
-      alertGuids.add(alertRequest.getGuid());
-    }
-
-    metaAlert.put(MetaAlertConstants.ALERT_FIELD, alertArray);
-    metaAlert.putAll(new MetaScores(threatScores).getMetaScores());
-    metaAlert.put(MetaAlertConstants.STATUS_FIELD, MetaAlertStatus.ACTIVE.getStatusString());
-
-    // Add the alert to the store, but make sure not to overwrite existing results
-    InMemoryDao.BACKING_STORE.get(getMetaAlertIndex()).add(metaAlert.toJSONString());
-
-    METAALERT_STORE.put(metaAlertGuid, new HashSet<>(alertGuids));
-
-    MetaAlertCreateResponse createResponse = new MetaAlertCreateResponse();
-    createResponse.setGuid(metaAlertGuid);
-    createResponse.setCreated(true);
-    return createResponse;
+  public Document createMetaAlert(MetaAlertCreateRequest request)
+      throws InvalidCreateException, IOException {
+    return metaAlertUpdateDao.createMetaAlert(request);
   }
 
   @Override
-  public boolean addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests) {
-    Collection<String> currentAlertGuids = METAALERT_STORE.get(metaAlertGuid);
-    if (currentAlertGuids == null) {
-      return false;
-    }
-    Collection<String> alertGuids = alertRequests.stream().map(GetRequest::getGuid)
-        .collect(Collectors.toSet());
-    boolean added = currentAlertGuids.addAll(alertGuids);
-    if (added) {
-      METAALERT_STORE.put(metaAlertGuid, currentAlertGuids);
-    }
-    return added;
+  public Document addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests) throws IOException {
+    return metaAlertUpdateDao.addAlertsToMetaAlert(metaAlertGuid, alertRequests);
   }
 
   @Override
-  public boolean removeAlertsFromMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests) {
-    Collection<String> currentAlertGuids = METAALERT_STORE.get(metaAlertGuid);
-    if (currentAlertGuids == null) {
-      return false;
-    }
-    Collection<String> alertGuids = alertRequests.stream().map(GetRequest::getGuid)
-        .collect(Collectors.toSet());
-    boolean removed = currentAlertGuids.removeAll(alertGuids);
-    if (removed) {
-      METAALERT_STORE.put(metaAlertGuid, currentAlertGuids);
-    }
-    return removed;
+  public Document removeAlertsFromMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests) throws IOException {
+    return metaAlertUpdateDao.removeAlertsFromMetaAlert(metaAlertGuid, alertRequests);
   }
 
   @SuppressWarnings("unchecked")
   @Override
-  public boolean updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status)
+  public Document updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status)
       throws IOException {
-    boolean statusChanged = false;
-    List<String> metaAlerts = InMemoryDao.BACKING_STORE.get(getMetaAlertIndex());
-    for (String metaAlert : metaAlerts) {
-      JSONObject metaAlertJSON = JSONUtils.INSTANCE.load(metaAlert, JSONObject.class);
-      if (metaAlertGuid.equals(metaAlertJSON.get(GUID))) {
-        statusChanged = !status.getStatusString()
-            .equals(metaAlertJSON.get(MetaAlertConstants.STATUS_FIELD));
-        if (statusChanged) {
-          metaAlertJSON.put(MetaAlertConstants.STATUS_FIELD, status.getStatusString());
-          metaAlerts.remove(metaAlert);
-          metaAlerts.add(metaAlertJSON.toJSONString());
-          InMemoryDao.BACKING_STORE.put(getMetaAlertIndex(), metaAlerts);
-        }
-        break;
-      }
-    }
-    return statusChanged;
-  }
-
-  public int getPageSize() {
-    return pageSize;
-  }
-
-  public void setPageSize(int pageSize) {
-    this.pageSize = pageSize;
-  }
-
-  public String getMetAlertSensorName() {
-    return MetaAlertConstants.METAALERT_TYPE;
-  }
-
-  public String getMetaAlertIndex() {
-    return "metaalert_index";
+    return metaAlertUpdateDao.updateMetaAlertStatus(metaAlertGuid, status);
   }
 
   public static void clear() {
     InMemoryDao.clear();
-    METAALERT_STORE.clear();
   }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/InMemoryMetaAlertUpdateDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/InMemoryMetaAlertUpdateDao.java b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/InMemoryMetaAlertUpdateDao.java
new file mode 100644
index 0000000..53564b0
--- /dev/null
+++ b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/InMemoryMetaAlertUpdateDao.java
@@ -0,0 +1,91 @@
+/*
+ * 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.indexing.dao;
+
+import org.apache.metron.indexing.dao.metaalert.MetaAlertConfig;
+import org.apache.metron.indexing.dao.metaalert.MetaAlertConstants;
+import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateRequest;
+import org.apache.metron.indexing.dao.metaalert.MetaAlertRetrieveLatestDao;
+import org.apache.metron.indexing.dao.metaalert.lucene.AbstractLuceneMetaAlertUpdateDao;
+import org.apache.metron.indexing.dao.search.GetRequest;
+import org.apache.metron.indexing.dao.search.InvalidCreateException;
+import org.apache.metron.indexing.dao.update.CommentAddRemoveRequest;
+import org.apache.metron.indexing.dao.update.Document;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+public class InMemoryMetaAlertUpdateDao extends AbstractLuceneMetaAlertUpdateDao {
+
+  private IndexDao indexDao;
+
+  public InMemoryMetaAlertUpdateDao(
+          IndexDao indexDao,
+          MetaAlertRetrieveLatestDao retrieveLatestDao,
+          MetaAlertConfig config,
+          int pageSize
+  ) {
+    super(indexDao, retrieveLatestDao, config);
+    this.indexDao = indexDao;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public Document createMetaAlert(MetaAlertCreateRequest request) throws InvalidCreateException, IOException {
+    List<GetRequest> alertRequests = request.getAlerts();
+    if (alertRequests.isEmpty()) {
+      return null;
+    }
+    // Retrieve the documents going into the meta alert and build it
+    Iterable<Document> alerts = indexDao.getAllLatest(alertRequests);
+
+    Document metaAlert = buildCreateDocument(alerts, request.getGroups(),
+            MetaAlertConstants.ALERT_FIELD);
+
+    metaAlert.getDocument()
+            .put(getConfig().getSourceTypeField(), MetaAlertConstants.METAALERT_TYPE);
+
+    return metaAlert;
+  }
+
+  @Override
+  public Document update(Document update, Optional<String> index) throws IOException {
+    return indexDao.update(update, index);
+  }
+
+  @Override
+  public Document addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
+    return null;
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/MultiIndexDaoTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/MultiIndexDaoTest.java b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/MultiIndexDaoTest.java
new file mode 100644
index 0000000..dad6a52
--- /dev/null
+++ b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/MultiIndexDaoTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.indexing.dao;
+
+import org.apache.metron.indexing.dao.update.CommentAddRemoveRequest;
+import org.apache.metron.indexing.dao.update.Document;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MultiIndexDaoTest {
+  @Rule
+  public final ExpectedException exception = ExpectedException.none();
+
+  private MultiIndexDao multiIndexDao;
+  private IndexDao dao1;
+  private IndexDao dao2;
+
+  @Before
+  public void setup() {
+    dao1 = mock(IndexDao.class);
+    dao2 = mock(IndexDao.class);
+    multiIndexDao = new MultiIndexDao(dao1, dao2);
+  }
+
+  @Test
+  public void getLatestShouldReturnLatestAlert() throws Exception {
+    Document document1 = new Document(new HashMap<>(), "guid", "bro", 1L);
+    Document document2 = new Document(new HashMap<>(), "guid", "bro", 2L);
+
+    CommentAddRemoveRequest request = new CommentAddRemoveRequest();
+    request.setGuid("guid");
+    when(dao1.getLatest("guid", "bro")).thenReturn(document1);
+    when(dao2.getLatest("guid", "bro")).thenReturn(document2);
+
+
+    Document expected = new Document(new HashMap<>(), "guid", "bro", 2L);
+    Assert.assertEquals(expected, multiIndexDao.getLatest("guid", "bro"));
+  }
+
+  @Test
+  public void addCommentShouldAddCommentToAlert() throws Exception {
+    Document latest = mock(Document.class);
+    Document document1 = new Document(new HashMap<>(), "guid", "bro", 1L);
+    Document document2 = new Document(new HashMap<>(), "guid", "bro", 2L);
+
+    CommentAddRemoveRequest request = new CommentAddRemoveRequest();
+    request.setGuid("guid");
+    when(dao1.addCommentToAlert(request, latest)).thenReturn(document1);
+    when(dao2.addCommentToAlert(request, latest)).thenReturn(document2);
+
+
+    Document expected = new Document(new HashMap<>(), "guid", "bro", 2L);
+    Assert.assertEquals(expected, multiIndexDao.addCommentToAlert(request, latest));
+  }
+
+  @Test
+  public void removeCommentShouldRemoveCommentFromAlert() throws Exception {
+    Document latest = mock(Document.class);
+    Document document1 = new Document(new HashMap<>(), "guid", "bro", 1L);
+    Document document2 = new Document(new HashMap<>(), "guid", "bro", 2L);
+
+    CommentAddRemoveRequest request = new CommentAddRemoveRequest();
+    request.setGuid("guid");
+    when(dao1.removeCommentFromAlert(request, latest)).thenReturn(document1);
+    when(dao2.removeCommentFromAlert(request, latest)).thenReturn(document2);
+
+
+    Document expected = new Document(new HashMap<>(), "guid", "bro", 2L);
+    Assert.assertEquals(expected, multiIndexDao.removeCommentFromAlert(request, latest));
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/UpdateDaoTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/UpdateDaoTest.java b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/UpdateDaoTest.java
new file mode 100644
index 0000000..bbe7fa8
--- /dev/null
+++ b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/UpdateDaoTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.indexing.dao;
+
+import org.apache.metron.indexing.dao.update.CommentAddRemoveRequest;
+import org.apache.metron.indexing.dao.update.Document;
+import org.apache.metron.indexing.dao.update.UpdateDao;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * The tests in this class are common among all UpdateDao implementations.
+ */
+public abstract class UpdateDaoTest {
+
+  @Rule
+  public final ExpectedException exception = ExpectedException.none();
+
+  @Test
+  public void addCommentShouldThrowExceptionOnMissingAlert() throws Exception {
+    exception.expect(IOException.class);
+    exception.expectMessage("Unable to add comment. Document with guid guid cannot be found.");
+
+    CommentAddRemoveRequest request = new CommentAddRemoveRequest();
+    request.setGuid("guid");
+
+    getUpdateDao().addCommentToAlert(request, null);
+  }
+
+  @Test
+  public void removeCommentShouldThrowExceptionOnMissingAlert() throws Exception {
+    exception.expect(IOException.class);
+    exception.expectMessage("Unable to remove comment. Document with guid guid cannot be found.");
+
+    CommentAddRemoveRequest request = new CommentAddRemoveRequest();
+    request.setGuid("guid");
+
+    getUpdateDao().removeCommentFromAlert(request, null);
+  }
+
+  @Test
+  public void removeCommentShouldThrowExceptionOnEmptyComments() throws Exception {
+    exception.expect(IOException.class);
+    exception.expectMessage("Unable to remove comment. Document with guid guid has no comments.");
+
+    CommentAddRemoveRequest request = new CommentAddRemoveRequest();
+    request.setGuid("guid");
+    Document latest = new Document(new HashMap<>(), "guid", "bro", System.currentTimeMillis());
+
+    getUpdateDao().removeCommentFromAlert(request, latest);
+  }
+
+  public abstract UpdateDao getUpdateDao();
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/UpdateIntegrationTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/UpdateIntegrationTest.java b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/UpdateIntegrationTest.java
index 1e35523..ef9714e 100644
--- a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/UpdateIntegrationTest.java
+++ b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/UpdateIntegrationTest.java
@@ -18,7 +18,6 @@ import static org.apache.metron.indexing.dao.IndexDao.COMMENTS_FIELD;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -99,13 +98,14 @@ public abstract class UpdateIntegrationTest {
         put("new-field", "metron");
       }};
       String guid = "" + message0.get(Constants.GUID);
-      getDao().replace(new ReplaceRequest(){{
+      Document update = getDao().replace(new ReplaceRequest(){{
         setReplacement(message0);
         setGuid(guid);
         setSensorType(SENSOR_NAME);
         setIndex(getIndexName());
       }}, Optional.empty());
 
+      Assert.assertEquals(message0, update.getDocument());
       Assert.assertEquals(1, getMockHTable().size());
       findUpdatedDoc(message0, guid, SENSOR_NAME);
       {
@@ -138,12 +138,13 @@ public abstract class UpdateIntegrationTest {
         put("new-field", "metron2");
       }};
       String guid = "" + message0.get(Constants.GUID);
-      getDao().replace(new ReplaceRequest(){{
+      Document update = getDao().replace(new ReplaceRequest(){{
         setReplacement(message0);
         setGuid(guid);
         setSensorType(SENSOR_NAME);
         setIndex(getIndexName());
       }}, Optional.empty());
+      Assert.assertEquals(message0, update.getDocument());
       Assert.assertEquals(1, getMockHTable().size());
       Document doc = getDao().getLatest(guid, SENSOR_NAME);
       Assert.assertEquals(message0, doc.getDocument());
@@ -184,33 +185,40 @@ public abstract class UpdateIntegrationTest {
     fields.put("source.type", SENSOR_NAME);
 
     Document document = new Document(fields, "add_comment", SENSOR_NAME, 1526306463050L);
-    getDao().update(document, Optional.of(SENSOR_NAME));
-    findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
-
-    addAlertComment("add_comment", "New Comment", "test_user", 1526306463050L);
-    // Ensure we have the first comment
+    {
+      Document update = getDao().update(document, Optional.of(SENSOR_NAME));
+      Assert.assertEquals(document, update);
+      findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
+    }
     ArrayList<AlertComment> comments = new ArrayList<>();
-    comments.add(new AlertComment("New Comment", "test_user", 1526306463050L));
-    document.getDocument().put(COMMENTS_FIELD, comments.stream().map(AlertComment::asMap).collect(
-        Collectors.toList()));
-    findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
-
-    List<Map<String, Object>> patchList = new ArrayList<>();
-    Map<String, Object> patch = new HashMap<>();
-    patch.put("op", "add");
-    patch.put("path", "/project");
-    patch.put("value", "metron");
-    patchList.add(patch);
+    {
+      Document update = addAlertComment("add_comment", "New Comment", "test_user", 1526306463050L);
+      // Ensure we have the first comment
+      comments.add(new AlertComment("New Comment", "test_user", 1526306463050L));
+      document.getDocument().put(COMMENTS_FIELD, comments.stream().map(AlertComment::asMap).collect(
+              Collectors.toList()));
+      Assert.assertEquals(document, update);
+      findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
+    }
+    {
+      List<Map<String, Object>> patchList = new ArrayList<>();
+      Map<String, Object> patch = new HashMap<>();
+      patch.put("op", "add");
+      patch.put("path", "/project");
+      patch.put("value", "metron");
+      patchList.add(patch);
 
-    PatchRequest pr = new PatchRequest();
-    pr.setGuid("add_comment");
-    pr.setIndex(SENSOR_NAME);
-    pr.setSensorType(SENSOR_NAME);
-    pr.setPatch(patchList);
-    getDao().patch(getDao(), pr, Optional.of(new Date().getTime()));
+      PatchRequest pr = new PatchRequest();
+      pr.setGuid("add_comment");
+      pr.setIndex(SENSOR_NAME);
+      pr.setSensorType(SENSOR_NAME);
+      pr.setPatch(patchList);
+      Document update = getDao().patch(getDao(), pr, Optional.of(1526306463050L));
 
-    document.getDocument().put("project", "metron");
-    findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
+      document.getDocument().put("project", "metron");
+      Assert.assertEquals(document, update);
+      findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
+    }
   }
 
   @Test
@@ -221,48 +229,60 @@ public abstract class UpdateIntegrationTest {
     fields.put("source.type", SENSOR_NAME);
 
     Document document = new Document(fields, "add_comment", SENSOR_NAME, 1526401584951L);
-    getDao().update(document, Optional.of(SENSOR_NAME));
-    findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
-
-    addAlertComment("add_comment", "New Comment", "test_user", 1526401584951L);
-    // Ensure we have the first comment
+    {
+      Document update = getDao().update(document, Optional.of(SENSOR_NAME));
+      Assert.assertEquals(document, update);
+      findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
+    }
     ArrayList<AlertComment> comments = new ArrayList<>();
-    comments.add(new AlertComment("New Comment", "test_user", 1526401584951L));
-    document.getDocument().put(COMMENTS_FIELD, comments.stream().map(AlertComment::asMap).collect(
-        Collectors.toList()));
-    findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
-
-    addAlertComment("add_comment", "New Comment 2", "test_user_2", 1526401584952L);
-    // Ensure we have the second comment
-    comments.add(new AlertComment("New Comment 2", "test_user_2", 1526401584952L));
-    document.getDocument().put(COMMENTS_FIELD, comments.stream().map(AlertComment::asMap).collect(
-        Collectors.toList()));
-    findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
-
-    removeAlertComment("add_comment", "New Comment 2", "test_user_2", 1526401584952L);
-    // Ensure we only have the first comments
-    comments = new ArrayList<>();
-    comments.add(new AlertComment(commentOne));
-    document.getDocument().put(COMMENTS_FIELD, comments.stream().map(AlertComment::asMap).collect(
-        Collectors.toList()));
-    findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
+    {
+      Document update = addAlertComment("add_comment", "New Comment", "test_user", 1526401584951L);
+      // Ensure we have the first comment
 
-    removeAlertComment("add_comment", "New Comment", "test_user", 1526401584951L);
-    // Ensure we have no comments
-    document.getDocument().remove(COMMENTS_FIELD);
-    findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
+      comments.add(new AlertComment("New Comment", "test_user", 1526401584951L));
+      document.getDocument().put(COMMENTS_FIELD, comments.stream().map(AlertComment::asMap).collect(
+              Collectors.toList()));
+      Assert.assertEquals(document, update);
+      findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
+    }
+    {
+      Document update = addAlertComment("add_comment", "New Comment 2", "test_user_2", 1526401584952L);
+      // Ensure we have the second comment
+      comments.add(new AlertComment("New Comment 2", "test_user_2", 1526401584952L));
+      document.getDocument().put(COMMENTS_FIELD, comments.stream().map(AlertComment::asMap).collect(
+              Collectors.toList()));
+      Assert.assertEquals(document, update);
+      findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
+    }
+    {
+      Document update = removeAlertComment("add_comment", "New Comment 2", "test_user_2", 1526401584952L);
+      // Ensure we only have the first comments
+      comments = new ArrayList<>();
+      comments.add(new AlertComment(commentOne));
+      document.getDocument().put(COMMENTS_FIELD, comments.stream().map(AlertComment::asMap).collect(
+              Collectors.toList()));
+      Assert.assertEquals(document, update);
+      findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
+    }
+    {
+      Document update = removeAlertComment("add_comment", "New Comment", "test_user", 1526401584951L);
+      // Ensure we have no comments
+      document.getDocument().remove(COMMENTS_FIELD);
+      Assert.assertEquals(document, update);
+      findUpdatedDoc(document.getDocument(), "add_comment", SENSOR_NAME);
+    }
   }
 
-  protected void addAlertComment(String guid, String comment, String username, long timestamp)
+  protected Document addAlertComment(String guid, String comment, String username, long timestamp)
       throws IOException {
     CommentAddRemoveRequest request = buildAlertRequest(guid, comment, username, timestamp);
-    getDao().addCommentToAlert(request);
+    return getDao().addCommentToAlert(request);
   }
 
-  protected void removeAlertComment(String guid, String comment, String username, long timestamp)
+  protected Document removeAlertComment(String guid, String comment, String username, long timestamp)
       throws IOException {
     CommentAddRemoveRequest request = buildAlertRequest(guid, comment, username, timestamp);
-    getDao().removeCommentFromAlert(request);
+    return getDao().removeCommentFromAlert(request);
   }
 
   private CommentAddRemoveRequest buildAlertRequest(String guid, String comment, String username,

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/metaalert/MetaAlertIntegrationTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/metaalert/MetaAlertIntegrationTest.java b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/metaalert/MetaAlertIntegrationTest.java
index f754b81..7e28853 100644
--- a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/metaalert/MetaAlertIntegrationTest.java
+++ b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/metaalert/MetaAlertIntegrationTest.java
@@ -278,11 +278,40 @@ public abstract class MetaAlertIntegrationTest {
         }});
         setGroups(Collections.singletonList("group"));
       }};
-      MetaAlertCreateResponse metaAlertCreateResponse = metaDao
+
+      Document actualMetaAlert = metaDao
           .createMetaAlert(metaAlertCreateRequest);
+
+      // Build expected metaAlert after alerts are added
+      Map<String, Object> expectedMetaAlert = new HashMap<>();
+
+      expectedMetaAlert.put(Constants.GUID, actualMetaAlert.getGuid());
+      expectedMetaAlert.put(getSourceTypeField(), METAALERT_TYPE);
+      expectedMetaAlert.put(STATUS_FIELD, MetaAlertStatus.ACTIVE.getStatusString());
+      // Verify the proper alerts were added
+      @SuppressWarnings("unchecked")
+      List<Map<String, Object>> metaAlertAlerts = new ArrayList<>();
+      // Alert 0 is already in the metaalert. Add alerts 1 and 2.
+      Map<String, Object> expectedAlert1 = alerts.get(1);
+      expectedAlert1.put(METAALERT_FIELD, Collections.singletonList(actualMetaAlert.getGuid()));
+      metaAlertAlerts.add(expectedAlert1);
+      Map<String, Object> expectedAlert2 = alerts.get(2);
+      expectedAlert2.put(METAALERT_FIELD, Collections.singletonList(actualMetaAlert.getGuid()));
+      metaAlertAlerts.add(expectedAlert2);
+      expectedMetaAlert.put(ALERT_FIELD, metaAlertAlerts);
+
+      // Verify the counts were properly updated
+      expectedMetaAlert.put("average", 1.5d);
+      expectedMetaAlert.put("min", 1.0d);
+      expectedMetaAlert.put("median", 1.5d);
+      expectedMetaAlert.put("max", 2.0d);
+      expectedMetaAlert.put("count", 2);
+      expectedMetaAlert.put("sum", 3.0d);
+      expectedMetaAlert.put(getThreatTriageField(), 3.0d);
       {
         // Verify metaAlert was created
-        findCreatedDoc(metaAlertCreateResponse.getGuid(), METAALERT_TYPE);
+        assertEquals(expectedMetaAlert, actualMetaAlert.getDocument());
+        findCreatedDoc(actualMetaAlert.getGuid(), METAALERT_TYPE);
       }
       {
         // Verify alert 0 was not updated with metaalert field
@@ -294,14 +323,14 @@ public abstract class MetaAlertIntegrationTest {
         // Verify alert 1 was properly updated with metaalert field
         Map<String, Object> expectedAlert = new HashMap<>(alerts.get(1));
         expectedAlert
-            .put(METAALERT_FIELD, Collections.singletonList(metaAlertCreateResponse.getGuid()));
+            .put(METAALERT_FIELD, Collections.singletonList(actualMetaAlert.getGuid()));
         findUpdatedDoc(expectedAlert, "message_1", SENSOR_NAME);
       }
       {
         // Verify alert 2 was properly updated with metaalert field
         Map<String, Object> expectedAlert = new HashMap<>(alerts.get(2));
         expectedAlert
-            .put(METAALERT_FIELD, Collections.singletonList(metaAlertCreateResponse.getGuid()));
+            .put(METAALERT_FIELD, Collections.singletonList(actualMetaAlert.getGuid()));
         findUpdatedDoc(expectedAlert, "message_2", SENSOR_NAME);
       }
     }
@@ -355,17 +384,19 @@ public abstract class MetaAlertIntegrationTest {
 
     {
       // Verify alerts were successfully added to the meta alert
-      Assert.assertTrue(metaDao.addAlertsToMetaAlert("meta_alert", Arrays
-          .asList(new GetRequest("message_1", SENSOR_NAME),
-              new GetRequest("message_2", SENSOR_NAME))));
+      Document actualMetaAlert = metaDao.addAlertsToMetaAlert("meta_alert", Arrays
+              .asList(new GetRequest("message_1", SENSOR_NAME),
+                      new GetRequest("message_2", SENSOR_NAME)));
+      assertEquals(expectedMetaAlert, actualMetaAlert.getDocument());
       findUpdatedDoc(expectedMetaAlert, "meta_alert", METAALERT_TYPE);
     }
 
     {
       // Verify False when alerts are already in a meta alert and no new alerts are added
-      Assert.assertFalse(metaDao.addAlertsToMetaAlert("meta_alert", Arrays
-          .asList(new GetRequest("message_0", SENSOR_NAME),
-              new GetRequest("message_1", SENSOR_NAME))));
+      Document actualMetaAlert = metaDao.addAlertsToMetaAlert("meta_alert", Arrays
+              .asList(new GetRequest("message_0", SENSOR_NAME),
+                      new GetRequest("message_1", SENSOR_NAME)));
+      assertEquals(expectedMetaAlert, actualMetaAlert.getDocument());
       findUpdatedDoc(expectedMetaAlert, "meta_alert", METAALERT_TYPE);
     }
 
@@ -385,9 +416,10 @@ public abstract class MetaAlertIntegrationTest {
       expectedMetaAlert.put("sum", 6.0d);
       expectedMetaAlert.put(getThreatTriageField(), 6.0d);
 
-      Assert.assertTrue(metaDao.addAlertsToMetaAlert("meta_alert", Arrays
-          .asList(new GetRequest("message_2", SENSOR_NAME),
-              new GetRequest("message_3", SENSOR_NAME))));
+      Document actualMetaAlert = metaDao.addAlertsToMetaAlert("meta_alert", Arrays
+              .asList(new GetRequest("message_2", SENSOR_NAME),
+                      new GetRequest("message_3", SENSOR_NAME)));
+      assertEquals(expectedMetaAlert, actualMetaAlert.getDocument());
       findUpdatedDoc(expectedMetaAlert, "meta_alert", METAALERT_TYPE);
     }
   }
@@ -437,17 +469,19 @@ public abstract class MetaAlertIntegrationTest {
 
     {
       // Verify a list of alerts are removed from a meta alert
-      Assert.assertTrue(metaDao.removeAlertsFromMetaAlert("meta_alert", Arrays
-          .asList(new GetRequest("message_0", SENSOR_NAME),
-              new GetRequest("message_1", SENSOR_NAME))));
+      Document actualMetaAlert = metaDao.removeAlertsFromMetaAlert("meta_alert", Arrays
+              .asList(new GetRequest("message_0", SENSOR_NAME),
+                      new GetRequest("message_1", SENSOR_NAME)));
+      assertEquals(expectedMetaAlert, actualMetaAlert.getDocument());
       findUpdatedDoc(expectedMetaAlert, "meta_alert", METAALERT_TYPE);
     }
 
     {
       // Verify False when alerts are not present in a meta alert and no alerts are removed
-      Assert.assertFalse(metaDao.removeAlertsFromMetaAlert("meta_alert", Arrays
-          .asList(new GetRequest("message_0", SENSOR_NAME),
-              new GetRequest("message_1", SENSOR_NAME))));
+      Document actualMetaAlert = metaDao.removeAlertsFromMetaAlert("meta_alert", Arrays
+              .asList(new GetRequest("message_0", SENSOR_NAME),
+                      new GetRequest("message_1", SENSOR_NAME)));
+      assertEquals(expectedMetaAlert, actualMetaAlert.getDocument());
       findUpdatedDoc(expectedMetaAlert, "meta_alert", METAALERT_TYPE);
     }
 
@@ -466,9 +500,10 @@ public abstract class MetaAlertIntegrationTest {
       expectedMetaAlert.put("sum", 3.0d);
       expectedMetaAlert.put(getThreatTriageField(), 3.0d);
 
-      Assert.assertTrue(metaDao.removeAlertsFromMetaAlert("meta_alert", Arrays
-          .asList(new GetRequest("message_0", SENSOR_NAME),
-              new GetRequest("message_2", SENSOR_NAME))));
+      Document actualMetaAlert = metaDao.removeAlertsFromMetaAlert("meta_alert", Arrays
+              .asList(new GetRequest("message_0", SENSOR_NAME),
+                      new GetRequest("message_2", SENSOR_NAME)));
+      assertEquals(expectedMetaAlert, actualMetaAlert.getDocument());
       findUpdatedDoc(expectedMetaAlert, "meta_alert", METAALERT_TYPE);
     }
 
@@ -587,11 +622,11 @@ public abstract class MetaAlertIntegrationTest {
 
     {
       // Verify status changed to inactive and child alerts are updated
-      Assert.assertTrue(metaDao.updateMetaAlertStatus("meta_alert", MetaAlertStatus.INACTIVE));
-
       Map<String, Object> expectedMetaAlert = new HashMap<>(metaAlert);
       expectedMetaAlert.put(STATUS_FIELD, MetaAlertStatus.INACTIVE.getStatusString());
 
+      Document actualMetaAlert = metaDao.updateMetaAlertStatus("meta_alert", MetaAlertStatus.INACTIVE);
+      Assert.assertEquals(expectedMetaAlert, actualMetaAlert.getDocument());
       findUpdatedDoc(expectedMetaAlert, "meta_alert", METAALERT_TYPE);
 
       for (int i = 0; i < numChildAlerts; ++i) {
@@ -610,11 +645,11 @@ public abstract class MetaAlertIntegrationTest {
 
     {
       // Verify status changed to active and child alerts are updated
-      Assert.assertTrue(metaDao.updateMetaAlertStatus("meta_alert", MetaAlertStatus.ACTIVE));
-
       Map<String, Object> expectedMetaAlert = new HashMap<>(metaAlert);
       expectedMetaAlert.put(STATUS_FIELD, MetaAlertStatus.ACTIVE.getStatusString());
 
+      Document actualMetaAlert = metaDao.updateMetaAlertStatus("meta_alert", MetaAlertStatus.ACTIVE);
+      Assert.assertEquals(expectedMetaAlert, actualMetaAlert.getDocument());
       findUpdatedDoc(expectedMetaAlert, "meta_alert", METAALERT_TYPE);
 
       for (int i = 0; i < numChildAlerts; ++i) {
@@ -629,11 +664,15 @@ public abstract class MetaAlertIntegrationTest {
         // Make sure to handle the guid offset from creation
         findUpdatedDoc(expectedAlert, "message_" + (i + numChildAlerts), SENSOR_NAME);
       }
-
+    }
+    {
       {
         // Verify status changed to current status has no effect
-        Assert.assertFalse(metaDao.updateMetaAlertStatus("meta_alert", MetaAlertStatus.ACTIVE));
+        Map<String, Object> expectedMetaAlert = new HashMap<>(metaAlert);
+        expectedMetaAlert.put(STATUS_FIELD, MetaAlertStatus.ACTIVE.getStatusString());
 
+        Document actualMetaAlert = metaDao.updateMetaAlertStatus("meta_alert", MetaAlertStatus.ACTIVE);
+        Assert.assertEquals(expectedMetaAlert, actualMetaAlert.getDocument());
         findUpdatedDoc(expectedMetaAlert, "meta_alert", METAALERT_TYPE);
 
         for (int i = 0; i < numChildAlerts; ++i) {
@@ -985,6 +1024,30 @@ public abstract class MetaAlertIntegrationTest {
     throw new OriginalNotFoundException("Count not find guids after " + MAX_RETRIES + "tries");
   }
 
+  @SuppressWarnings("unchecked")
+  protected void assertEquals(Map<String, Object> expected, Map<String, Object> actual) {
+    Assert.assertEquals(expected.get(Constants.GUID), actual.get(Constants.GUID));
+    Assert.assertEquals(expected.get(getSourceTypeField()), actual.get(getSourceTypeField()));
+    Double actualThreatTriageField = actual.get(getThreatTriageField()) instanceof Float ?
+            ((Float) actual.get(getThreatTriageField())).doubleValue() : (Double) actual.get(getThreatTriageField());
+    Assert.assertEquals(expected.get(getThreatTriageField()), actualThreatTriageField);
+
+    List<Map<String, Object>> expectedAlerts = (List<Map<String, Object>>) expected.get(ALERT_FIELD);
+    List<Map<String, Object>> actualAlerts = (List<Map<String, Object>>) actual.get(ALERT_FIELD);
+    expectedAlerts.sort(Comparator.comparing(o -> ((String) o.get(Constants.GUID))));
+    actualAlerts.sort(Comparator.comparing(o -> ((String) o.get(Constants.GUID))));
+    Assert.assertEquals(expectedAlerts, actualAlerts);
+    Assert.assertEquals(expected.get(STATUS_FIELD), actual.get(STATUS_FIELD));
+    Assert.assertEquals(expected.get("average"), actual.get("average"));
+    Assert.assertEquals(expected.get("min"), actual.get("min"));
+    Assert.assertEquals(expected.get("median"), actual.get("median"));
+    Assert.assertEquals(expected.get("max"), actual.get("max"));
+    Integer actualCountField = actual.get("count") instanceof Long ? ((Long) actual.get("count")).intValue() :
+            (Integer) actual.get("count");
+    Assert.assertEquals(expected.get("count"), actualCountField);
+    Assert.assertEquals(expected.get("sum"), actual.get("sum"));
+  }
+
   protected List<Map<String, Object>> buildAlerts(int count) {
     List<Map<String, Object>> inputData = new ArrayList<>();
     for (int i = 0; i < count; ++i) {

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/metaalert/lucene/AbstractLuceneMetaAlertUpdateDaoTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/metaalert/lucene/AbstractLuceneMetaAlertUpdateDaoTest.java b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/metaalert/lucene/AbstractLuceneMetaAlertUpdateDaoTest.java
index 5a70636..ec241e4 100644
--- a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/metaalert/lucene/AbstractLuceneMetaAlertUpdateDaoTest.java
+++ b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/dao/metaalert/lucene/AbstractLuceneMetaAlertUpdateDaoTest.java
@@ -54,12 +54,10 @@ import org.apache.metron.indexing.dao.RetrieveLatestDao;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertConfig;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertConstants;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateRequest;
-import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateResponse;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertRetrieveLatestDao;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertStatus;
 import org.apache.metron.indexing.dao.metaalert.MetaScores;
 import org.apache.metron.indexing.dao.search.GetRequest;
-import org.apache.metron.indexing.dao.search.InvalidSearchException;
 import org.apache.metron.indexing.dao.update.CommentAddRemoveRequest;
 import org.apache.metron.indexing.dao.update.Document;
 import org.apache.metron.indexing.dao.update.PatchRequest;
@@ -157,43 +155,39 @@ public class AbstractLuceneMetaAlertUpdateDaoTest {
     }
 
     @Override
-    public void update(Document update, Optional<String> index) {
+    public Document update(Document update, Optional<String> index) {
+      return null;
     }
 
     @Override
-    public void addCommentToAlert(CommentAddRemoveRequest request) {
+    public Document addCommentToAlert(CommentAddRemoveRequest request) {
+      return null;
     }
 
     @Override
-    public void removeCommentFromAlert(CommentAddRemoveRequest request) {
+    public Document removeCommentFromAlert(CommentAddRemoveRequest request) {
+      return null;
     }
 
     @Override
-    public void addCommentToAlert(CommentAddRemoveRequest request, Document latest) {
+    public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest) {
+      return null;
     }
 
     @Override
-    public void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) {
+    public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) {
+      return null;
     }
 
     @Override
-    public void patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request,
+    public Document patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request,
         Optional<Long> timestamp) {
-    }
-
-    @Override
-    public MetaAlertCreateResponse createMetaAlert(MetaAlertCreateRequest request) {
       return null;
     }
 
     @Override
-    public boolean addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests) {
-      return false;
-    }
-
-    @Override
-    public boolean updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status) {
-      return false;
+    public Document createMetaAlert(MetaAlertCreateRequest request) {
+      return null;
     }
   }
 
@@ -762,6 +756,30 @@ public class AbstractLuceneMetaAlertUpdateDaoTest {
     UUID.fromString((String) actualDocument.get(Constants.GUID));
   }
 
+  @Test
+  public void addAlertsToMetaAlertShouldThrowExceptionOnMissingMetaAlert() throws Exception {
+    thrown.expect(IOException.class);
+    thrown.expectMessage("Unable to add alerts to meta alert.  Meta alert with guid some_guid cannot be found.");
+
+    dao.addAlertsToMetaAlert("some_guid", new ArrayList<>());
+  }
+
+  @Test
+  public void removeAlertsFromMetaAlertShouldThrowExceptionOnMissingMetaAlert() throws Exception {
+    thrown.expect(IOException.class);
+    thrown.expectMessage("Unable to remove alerts from meta alert.  Meta alert with guid some_guid cannot be found.");
+
+    dao.removeAlertsFromMetaAlert("some_guid", new ArrayList<>());
+  }
+
+  @Test
+  public void updateMetaAlertStatusShouldThrowExceptionOnMissingMetaAlert() throws Exception {
+    thrown.expect(IOException.class);
+    thrown.expectMessage("Unable to update meta alert status.  Meta alert with guid some_guid cannot be found.");
+
+    dao.updateMetaAlertStatus("some_guid", MetaAlertStatus.INACTIVE);
+  }
+
   // Utility method to manage comparing update maps
   protected boolean updatesMapEquals(Map<Document, Optional<String>> expected,
       Map<Document, Optional<String>> actual) {

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/integration/HBaseDaoIntegrationTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/integration/HBaseDaoIntegrationTest.java b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/integration/HBaseDaoIntegrationTest.java
index 73a9077..8eaa8f4 100644
--- a/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/integration/HBaseDaoIntegrationTest.java
+++ b/metron-platform/metron-indexing/src/test/java/org/apache/metron/indexing/integration/HBaseDaoIntegrationTest.java
@@ -35,14 +35,13 @@ import org.apache.metron.hbase.mock.MockHTable;
 import org.apache.metron.indexing.dao.AccessConfig;
 import org.apache.metron.indexing.dao.HBaseDao;
 import org.apache.metron.indexing.dao.IndexDao;
-import org.apache.metron.indexing.dao.MultiIndexDao;
 import org.apache.metron.indexing.dao.UpdateIntegrationTest;
 import org.apache.metron.indexing.dao.search.AlertComment;
 import org.apache.metron.indexing.dao.search.GetRequest;
 import org.apache.metron.indexing.dao.update.Document;
 import org.junit.After;
 import org.junit.Assert;
-import org.junit.BeforeClass;
+import org.junit.Before;
 import org.junit.Test;
 
 public class HBaseDaoIntegrationTest extends UpdateIntegrationTest  {
@@ -61,8 +60,8 @@ public class HBaseDaoIntegrationTest extends UpdateIntegrationTest  {
           0x54,0x79,0x70,0x65
   };
 
-  @BeforeClass
-  public static void startHBase() throws Exception {
+  @Before
+  public void startHBase() throws Exception {
     AccessConfig accessConfig = new AccessConfig();
     accessConfig.setMaxSearchResults(1000);
     accessConfig.setMaxSearchGroups(1000);

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrDao.java b/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrDao.java
index a840bb4..e6906f3 100644
--- a/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrDao.java
+++ b/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrDao.java
@@ -124,30 +124,30 @@ public class SolrDao implements IndexDao {
   }
 
   @Override
-  public void update(Document update, Optional<String> index) throws IOException {
-    this.solrUpdateDao.update(update, index);
+  public Document update(Document update, Optional<String> index) throws IOException {
+    return this.solrUpdateDao.update(update, index);
   }
 
   @Override
-  public void batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
-    this.solrUpdateDao.batchUpdate(updates);
+  public Map<Document, Optional<String>> batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
+    return this.solrUpdateDao.batchUpdate(updates);
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
-    this.solrUpdateDao.addCommentToAlert(request);
+  public Document addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
+    return this.solrUpdateDao.addCommentToAlert(request);
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
-    this.solrUpdateDao.removeCommentFromAlert(request);
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
+    return this.solrUpdateDao.removeCommentFromAlert(request);
   }
 
   @Override
-  public void patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request,
+  public Document patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request,
       Optional<Long> timestamp)
       throws OriginalNotFoundException, IOException {
-    solrUpdateDao.patch(retrieveLatestDao, request, timestamp);
+    return solrUpdateDao.patch(retrieveLatestDao, request, timestamp);
   }
 
   @Override
@@ -156,15 +156,15 @@ public class SolrDao implements IndexDao {
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request, Document latest)
+  public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest)
       throws IOException {
-    this.solrUpdateDao.addCommentToAlert(request, latest);
+    return this.solrUpdateDao.addCommentToAlert(request, latest);
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest)
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest)
       throws IOException {
-    this.solrUpdateDao.removeCommentFromAlert(request, latest);
+    return this.solrUpdateDao.removeCommentFromAlert(request, latest);
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrMetaAlertDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrMetaAlertDao.java b/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrMetaAlertDao.java
index 4748315..8ef9484 100644
--- a/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrMetaAlertDao.java
+++ b/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrMetaAlertDao.java
@@ -33,7 +33,6 @@ import org.apache.metron.indexing.dao.RetrieveLatestDao;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertConfig;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertConstants;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateRequest;
-import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateResponse;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertDao;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertStatus;
 import org.apache.metron.indexing.dao.search.FieldType;
@@ -190,20 +189,20 @@ public class SolrMetaAlertDao implements MetaAlertDao {
   }
 
   @Override
-  public void update(Document update, Optional<String> index) throws IOException {
-    metaAlertUpdateDao.update(update, index);
+  public Document update(Document update, Optional<String> index) throws IOException {
+    return metaAlertUpdateDao.update(update, index);
   }
 
   @Override
-  public void batchUpdate(Map<Document, Optional<String>> updates) {
-    metaAlertUpdateDao.batchUpdate(updates);
+  public Map<Document, Optional<String>> batchUpdate(Map<Document, Optional<String>> updates) {
+    return metaAlertUpdateDao.batchUpdate(updates);
   }
 
   @Override
-  public void patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request,
+  public Document patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request,
       Optional<Long> timestamp)
       throws OriginalNotFoundException, IOException {
-    metaAlertUpdateDao.patch(retrieveLatestDao, request, timestamp);
+    return metaAlertUpdateDao.patch(retrieveLatestDao, request, timestamp);
   }
 
   @Override
@@ -212,46 +211,46 @@ public class SolrMetaAlertDao implements MetaAlertDao {
   }
 
   @Override
-  public MetaAlertCreateResponse createMetaAlert(MetaAlertCreateRequest request)
+  public Document createMetaAlert(MetaAlertCreateRequest request)
       throws InvalidCreateException, IOException {
     return metaAlertUpdateDao.createMetaAlert(request);
   }
 
   @Override
-  public boolean addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
+  public Document addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
       throws IOException {
     return metaAlertUpdateDao.addAlertsToMetaAlert(metaAlertGuid, alertRequests);
   }
 
   @Override
-  public boolean removeAlertsFromMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
+  public Document removeAlertsFromMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
       throws IOException {
     return metaAlertUpdateDao.removeAlertsFromMetaAlert(metaAlertGuid, alertRequests);
   }
 
   @Override
-  public boolean updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status)
+  public Document updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status)
       throws IOException {
     return metaAlertUpdateDao.updateMetaAlertStatus(metaAlertGuid, status);
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
-    solrDao.addCommentToAlert(request);
+  public Document addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
+    return solrDao.addCommentToAlert(request);
   }
 
-    @Override
-    public void removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
-        solrDao.removeCommentFromAlert(request);
-    }
+  @Override
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
+    return solrDao.removeCommentFromAlert(request);
+  }
 
-    @Override
-    public void addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
-        solrDao.addCommentToAlert(request, latest);
-    }
+  @Override
+  public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
+    return solrDao.addCommentToAlert(request, latest);
+  }
 
-    @Override
-    public void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
-        solrDao.removeCommentFromAlert(request, latest);
-    }
+  @Override
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
+    return solrDao.removeCommentFromAlert(request, latest);
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrMetaAlertUpdateDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrMetaAlertUpdateDao.java b/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrMetaAlertUpdateDao.java
index 132d872..124d4be 100644
--- a/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrMetaAlertUpdateDao.java
+++ b/metron-platform/metron-solr/src/main/java/org/apache/metron/solr/dao/SolrMetaAlertUpdateDao.java
@@ -28,11 +28,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Collectors;
-import org.apache.metron.common.Constants;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertConfig;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertConstants;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateRequest;
-import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateResponse;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertStatus;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertUpdateDao;
 import org.apache.metron.indexing.dao.metaalert.MetaScores;
@@ -71,7 +69,7 @@ public class SolrMetaAlertUpdateDao extends AbstractLuceneMetaAlertUpdateDao imp
   }
 
   @Override
-  public MetaAlertCreateResponse createMetaAlert(MetaAlertCreateRequest request)
+  public Document createMetaAlert(MetaAlertCreateRequest request)
       throws InvalidCreateException, IOException {
     List<GetRequest> alertRequests = request.getAlerts();
     if (request.getAlerts().isEmpty()) {
@@ -120,11 +118,8 @@ public class SolrMetaAlertUpdateDao extends AbstractLuceneMetaAlertUpdateDao imp
       // Kick off any updates.
       update(updates);
 
-      MetaAlertCreateResponse createResponse = new MetaAlertCreateResponse();
-      createResponse.setCreated(true);
-      createResponse.setGuid(metaAlert.getGuid());
       solrClient.commit(METAALERTS_COLLECTION);
-      return createResponse;
+      return metaAlert;
     } catch (IOException | SolrServerException e) {
       throw new InvalidCreateException("Unable to create meta alert", e);
     }
@@ -135,10 +130,11 @@ public class SolrMetaAlertUpdateDao extends AbstractLuceneMetaAlertUpdateDao imp
    * Updates a document in Solr for a given collection.  Collection is not optional for Solr.
    * @param update The update to be run
    * @param collection The index to be updated. Mandatory for Solr
+   * @return The updated document.
    * @throws IOException Thrown when an error occurs during the write.
    */
   @Override
-  public void update(Document update, Optional<String> collection) throws IOException {
+  public Document update(Document update, Optional<String> collection) throws IOException {
     if (MetaAlertConstants.METAALERT_TYPE.equals(update.getSensorType())) {
       // We've been passed an update to the meta alert.
       throw new UnsupportedOperationException("Meta alerts cannot be directly updated");
@@ -181,28 +177,30 @@ public class SolrMetaAlertUpdateDao extends AbstractLuceneMetaAlertUpdateDao imp
     } catch (SolrServerException e) {
       throw new IOException("Unable to update document", e);
     }
+
+    return update;
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
-    getUpdateDao().addCommentToAlert(request);
+  public Document addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
+    return getUpdateDao().addCommentToAlert(request);
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
-    getUpdateDao().removeCommentFromAlert(request);
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
+    return getUpdateDao().removeCommentFromAlert(request);
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request, Document latest)
+  public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest)
       throws IOException {
-    getUpdateDao().addCommentToAlert(request, latest);
+    return getUpdateDao().addCommentToAlert(request, latest);
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest)
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest)
       throws IOException {
-    getUpdateDao().removeCommentFromAlert(request, latest);
+    return getUpdateDao().removeCommentFromAlert(request, latest);
   }
 
   protected boolean replaceAlertInMetaAlert(Document metaAlert, Document alert) {
@@ -215,9 +213,8 @@ public class SolrMetaAlertUpdateDao extends AbstractLuceneMetaAlertUpdateDao imp
   }
 
   @Override
-  public boolean addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
-      throws IOException {
-    boolean success;
+  public Document addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
+      throws IOException, IllegalStateException {
     Document metaAlert = getRetrieveLatestDao()
         .getLatest(metaAlertGuid, MetaAlertConstants.METAALERT_TYPE);
     if (MetaAlertStatus.ACTIVE.getStatusString()
@@ -225,7 +222,6 @@ public class SolrMetaAlertUpdateDao extends AbstractLuceneMetaAlertUpdateDao imp
       Iterable<Document> alerts = getRetrieveLatestDao().getAllLatest(alertRequests);
       Map<Document, Optional<String>> updates = buildAddAlertToMetaAlertUpdates(metaAlert, alerts);
       update(updates);
-      success = updates.size() != 0;
     } else {
       throw new IllegalStateException("Adding alerts to an INACTIVE meta alert is not allowed");
     }
@@ -234,6 +230,6 @@ public class SolrMetaAlertUpdateDao extends AbstractLuceneMetaAlertUpdateDao imp
     } catch (SolrServerException e) {
       throw new IOException("Unable to commit alerts to metaalert: " + metaAlertGuid, e);
     }
-    return success;
+    return metaAlert;
   }
 }


[3/3] metron git commit: METRON-1771 Update REST endpoints to support eventually consistent UI updates (merrimanr) closes apache/metron#1190

Posted by rm...@apache.org.
METRON-1771 Update REST endpoints to support eventually consistent UI updates (merrimanr) closes apache/metron#1190


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

Branch: refs/heads/master
Commit: de533063c26c8a2462dbfff33228a949c4a96c97
Parents: 7e222fa
Author: merrimanr <me...@gmail.com>
Authored: Thu Oct 4 17:16:18 2018 -0500
Committer: rmerriman <me...@gmail.com>
Committed: Thu Oct 4 17:16:18 2018 -0500

----------------------------------------------------------------------
 metron-interface/metron-rest/README.md          |  16 ++
 .../rest/controller/MetaAlertController.java    |  18 +-
 .../rest/controller/UpdateController.java       |  31 ++-
 .../metron/rest/service/MetaAlertService.java   |  12 +-
 .../metron/rest/service/UpdateService.java      |   9 +-
 .../rest/service/impl/MetaAlertServiceImpl.java |  18 +-
 .../rest/service/impl/UpdateServiceImpl.java    |  18 +-
 .../MetaAlertControllerIntegrationTest.java     | 152 +++++++-------
 .../elasticsearch/dao/ElasticsearchDao.java     |  32 +--
 .../dao/ElasticsearchMetaAlertDao.java          |  37 ++--
 .../dao/ElasticsearchMetaAlertUpdateDao.java    |  55 ++---
 .../dao/ElasticsearchUpdateDao.java             |  42 ++--
 .../dao/ElasticsearchMetaAlertDaoTest.java      |  27 ++-
 .../dao/ElasticsearchUpdateDaoTest.java         |  52 +++++
 .../apache/metron/indexing/dao/HBaseDao.java    |  31 +--
 .../metron/indexing/dao/MultiIndexDao.java      | 138 ++++++++-----
 .../dao/metaalert/MetaAlertCreateResponse.java  |  40 ----
 .../dao/metaalert/MetaAlertUpdateDao.java       |  20 +-
 .../AbstractLuceneMetaAlertUpdateDao.java       |  81 +++++++-
 .../metron/indexing/dao/update/UpdateDao.java   |  25 ++-
 .../InMemoryMetaAlertRetrieveLatestDao.java     |  49 +++++
 .../metron/indexing/dao/HBaseDaoTest.java       |  41 ++++
 .../apache/metron/indexing/dao/InMemoryDao.java |  18 +-
 .../indexing/dao/InMemoryMetaAlertDao.java      | 199 +++++--------------
 .../dao/InMemoryMetaAlertUpdateDao.java         |  91 +++++++++
 .../metron/indexing/dao/MultiIndexDaoTest.java  |  96 +++++++++
 .../metron/indexing/dao/UpdateDaoTest.java      |  74 +++++++
 .../indexing/dao/UpdateIntegrationTest.java     | 138 +++++++------
 .../dao/metaalert/MetaAlertIntegrationTest.java | 119 ++++++++---
 .../AbstractLuceneMetaAlertUpdateDaoTest.java   |  56 ++++--
 .../integration/HBaseDaoIntegrationTest.java    |   7 +-
 .../org/apache/metron/solr/dao/SolrDao.java     |  28 +--
 .../metron/solr/dao/SolrMetaAlertDao.java       |  49 +++--
 .../metron/solr/dao/SolrMetaAlertUpdateDao.java |  38 ++--
 .../apache/metron/solr/dao/SolrUpdateDao.java   |  36 ++--
 .../metron/solr/dao/SolrMetaAlertDaoTest.java   |  21 +-
 .../metron/solr/dao/SolrUpdateDaoTest.java      |  19 +-
 37 files changed, 1206 insertions(+), 727 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-interface/metron-rest/README.md
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/README.md b/metron-interface/metron-rest/README.md
index 2c216d1..2ce9522 100644
--- a/metron-interface/metron-rest/README.md
+++ b/metron-interface/metron-rest/README.md
@@ -336,6 +336,8 @@ Request and Response objects are JSON formatted.  The JSON schemas are available
 | [ `GET /api/v1/storm/supervisors`](#get-apiv1stormsupervisors)|
 | [ `PATCH /api/v1/update/patch`](#patch-apiv1updatepatch)|
 | [ `PUT /api/v1/update/replace`](#put-apiv1updatereplace)|
+| [ `POST /api/v1/update/add/comment`](#put-apiv1updateaddcomment)|
+| [ `POST /api/v1/update/remove/comment`](#put-apiv1updateremovecomment)|
 | [ `GET /api/v1/user`](#get-apiv1user)|
 
 ### `POST /api/v1/alerts/ui/escalate`
@@ -963,6 +965,20 @@ Request and Response objects are JSON formatted.  The JSON schemas are available
         ```
   * Returns:
     * 200 - Current user
+    
+### `POST /api/v1/update/add/comment`
+  * Description: Add a comment to an alert
+  * Input:
+    * request - Comment add request
+  * Returns:
+    * 200 - Returns the complete alert document with comments added.
+    
+### `POST /api/v1/update/remove/comment`
+  * Description: Remove a comment from an alert
+  * Input:
+    * request - Comment remove request
+  * Returns:
+    * 200 - Returns the complete alert document with comments removed.
 
 ### `GET /api/v1/user`
   * Description: Retrieves the current user

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/MetaAlertController.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/MetaAlertController.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/MetaAlertController.java
index d42403a..69b1779 100644
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/MetaAlertController.java
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/MetaAlertController.java
@@ -24,8 +24,8 @@ import io.swagger.annotations.ApiResponse;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertStatus;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertAddRemoveRequest;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateRequest;
-import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateResponse;
 import org.apache.metron.indexing.dao.search.SearchResponse;
+import org.apache.metron.indexing.dao.update.Document;
 import org.apache.metron.rest.RestException;
 import org.apache.metron.rest.service.MetaAlertService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -57,9 +57,9 @@ public class MetaAlertController {
   @ApiOperation(value = "Creates a new meta alert from a list of existing alerts.  "
       + "The meta alert status will initially be set to 'ACTIVE' and summary statistics "
       + "will be computed from the list of alerts.  A list of groups included in the request are also added to the meta alert.")
-  @ApiResponse(message = "The GUID of the new meta alert", code = 200)
+  @ApiResponse(message = "Returns the complete document of the created metaalert.", code = 200)
   @RequestMapping(value = "/create", method = RequestMethod.POST)
-  ResponseEntity<MetaAlertCreateResponse> create(
+  ResponseEntity<Document> create(
       @ApiParam(name = "createRequest", value = "Meta alert create request which includes a list of alert "
           + "get requests and a list of custom groups used to annotate a meta alert", required = true)
       @RequestBody  final MetaAlertCreateRequest createRequest
@@ -68,9 +68,9 @@ public class MetaAlertController {
   }
 
   @ApiOperation(value = "Adds an alert to an existing meta alert.  An alert will not be added if it is already contained in a meta alert.")
-  @ApiResponse(message = "Returns 'true' if the alert was added and 'false' if the meta alert did not change.", code = 200)
+  @ApiResponse(message = "Returns the complete metaalert document with the alerts added.", code = 200)
   @RequestMapping(value = "/add/alert", method = RequestMethod.POST)
-  ResponseEntity<Boolean> addAlertsToMetaAlert(
+  ResponseEntity<Document> addAlertsToMetaAlert(
       @ApiParam(name = "metaAlertAddRemoveRequest", value = "Meta alert add request which includes a meta alert GUID and list of alert get requests", required = true)
       @RequestBody  final MetaAlertAddRemoveRequest metaAlertAddRemoveRequest
   ) throws RestException {
@@ -78,9 +78,9 @@ public class MetaAlertController {
   }
 
   @ApiOperation(value = "Removes an alert from an existing meta alert.  If the alert to be removed is not in a meta alert, 'false' will be returned.")
-  @ApiResponse(message = "Returns 'true' if the alert was removed and 'false' if the meta alert did not change.", code = 200)
+  @ApiResponse(message = "Returns the complete metaalert document with the alerts removed.", code = 200)
   @RequestMapping(value = "/remove/alert", method = RequestMethod.POST)
-  ResponseEntity<Boolean> removeAlertsFromMetaAlert(
+  ResponseEntity<Document> removeAlertsFromMetaAlert(
       @ApiParam(name = "metaAlertAddRemoveRequest", value = "Meta alert remove request which includes a meta alert GUID and list of alert get requests", required = true)
       @RequestBody  final MetaAlertAddRemoveRequest metaAlertAddRemoveRequest
   ) throws RestException {
@@ -88,9 +88,9 @@ public class MetaAlertController {
   }
 
   @ApiOperation(value = "Updates the status of a meta alert to either 'ACTIVE' or 'INACTIVE'.")
-  @ApiResponse(message = "Returns 'true' if the status changed and 'false' if it did not.", code = 200)
+  @ApiResponse(message = "Returns the complete metaalert document with the updated status.", code = 200)
   @RequestMapping(value = "/update/status/{guid}/{status}", method = RequestMethod.POST)
-  ResponseEntity<Boolean> updateMetaAlertStatus(
+  ResponseEntity<Document> updateMetaAlertStatus(
       final @ApiParam(name = "guid", value = "Meta alert GUID", required = true)
       @PathVariable String guid,
       final @ApiParam(name = "status", value = "Meta alert status with a value of either 'ACTIVE' or 'INACTIVE'", required = true)

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/UpdateController.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/UpdateController.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/UpdateController.java
index 609442b..5550358 100644
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/UpdateController.java
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/UpdateController.java
@@ -21,6 +21,7 @@ import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import io.swagger.annotations.ApiResponse;
 import org.apache.metron.indexing.dao.update.CommentAddRemoveRequest;
+import org.apache.metron.indexing.dao.update.Document;
 import org.apache.metron.indexing.dao.update.OriginalNotFoundException;
 import org.apache.metron.indexing.dao.update.PatchRequest;
 import org.apache.metron.indexing.dao.update.ReplaceRequest;
@@ -42,52 +43,48 @@ public class UpdateController {
   private UpdateService service;
 
   @ApiOperation(value = "Update a document with a patch")
-  @ApiResponse(message = "Nothing", code = 200)
+  @ApiResponse(message = "Returns the complete patched document.", code = 200)
   @RequestMapping(value = "/patch", method = RequestMethod.PATCH)
-  ResponseEntity<Void> patch(
+  ResponseEntity<Document> patch(
           final @ApiParam(name = "request", value = "Patch request", required = true)
                 @RequestBody
           PatchRequest request
   ) throws RestException {
     try {
-      service.patch(request);
+      return new ResponseEntity<>(service.patch(request), HttpStatus.OK);
     } catch (OriginalNotFoundException e) {
       return new ResponseEntity<>(HttpStatus.NOT_FOUND);
     }
-    return new ResponseEntity<>(HttpStatus.OK);
   }
 
   @ApiOperation(value = "Replace a document with a full replacement")
-  @ApiResponse(message = "Nothing", code = 200)
+  @ApiResponse(message = "Returns the complete replaced document.", code = 200)
   @RequestMapping(value = "/replace", method = RequestMethod.POST)
-  ResponseEntity<Void> replace(
+  ResponseEntity<Document> replace(
           final @ApiParam(name = "request", value = "Replacement request", required = true)
                 @RequestBody
           ReplaceRequest request
   ) throws RestException {
-    service.replace(request);
-    return new ResponseEntity<>(HttpStatus.OK);
+    return new ResponseEntity<>(service.replace(request), HttpStatus.OK);
   }
 
   @ApiOperation(value = "Add a comment to an alert")
-  @ApiResponse(message = "Nothing", code = 200)
+  @ApiResponse(message = "Returns the complete alert document with comments added.", code = 200)
   @RequestMapping(value = "/add/comment", method = RequestMethod.POST)
-  ResponseEntity<Void> addCommentToAlert(
+  ResponseEntity<Document> addCommentToAlert(
       @RequestBody @ApiParam(name = "request", value = "Comment add request", required = true) final
       CommentAddRemoveRequest request
   ) throws RestException {
-    service.addComment(request);
-    return new ResponseEntity<>(HttpStatus.OK);
+    return new ResponseEntity<>(service.addComment(request), HttpStatus.OK);
   }
 
-  @ApiOperation(value = "Remove a comment to an alert")
-  @ApiResponse(message = "Nothing", code = 200)
+  @ApiOperation(value = "Remove a comment from an alert")
+  @ApiResponse(message = "Returns the complete alert document with comments removed.", code = 200)
   @RequestMapping(value = "/remove/comment", method = RequestMethod.POST)
-  ResponseEntity<Void> removeCommentFromAlert(
+  ResponseEntity<Document> removeCommentFromAlert(
       @RequestBody @ApiParam(name = "request", value = "Comment remove request", required = true) final
       CommentAddRemoveRequest request
   ) throws RestException {
-    service.removeComment(request);
-    return new ResponseEntity<>(HttpStatus.OK);
+    return new ResponseEntity<>(service.removeComment(request), HttpStatus.OK);
   }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/MetaAlertService.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/MetaAlertService.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/MetaAlertService.java
index e8abaf3..4ebebb6 100644
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/MetaAlertService.java
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/MetaAlertService.java
@@ -18,24 +18,22 @@
 
 package org.apache.metron.rest.service;
 
-import java.io.IOException;
-import java.util.Collection;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertAddRemoveRequest;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateRequest;
-import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateResponse;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertStatus;
 import org.apache.metron.indexing.dao.search.SearchResponse;
+import org.apache.metron.indexing.dao.update.Document;
 import org.apache.metron.rest.RestException;
 
 public interface MetaAlertService {
 
-  MetaAlertCreateResponse create(MetaAlertCreateRequest createRequest) throws RestException;
+  Document create(MetaAlertCreateRequest createRequest) throws RestException;
 
   SearchResponse getAllMetaAlertsForAlert(String guid) throws RestException;
 
-  boolean addAlertsToMetaAlert(MetaAlertAddRemoveRequest metaAlertAddRemoveRequest) throws RestException;
+  Document addAlertsToMetaAlert(MetaAlertAddRemoveRequest metaAlertAddRemoveRequest) throws RestException;
 
-  boolean removeAlertsFromMetaAlert(MetaAlertAddRemoveRequest metaAlertAddRemoveRequest) throws RestException;
+  Document removeAlertsFromMetaAlert(MetaAlertAddRemoveRequest metaAlertAddRemoveRequest) throws RestException;
 
-  boolean updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status) throws RestException;
+  Document updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status) throws RestException;
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/UpdateService.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/UpdateService.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/UpdateService.java
index bd59f39..19b3485 100644
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/UpdateService.java
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/UpdateService.java
@@ -18,6 +18,7 @@
 package org.apache.metron.rest.service;
 
 import org.apache.metron.indexing.dao.update.CommentAddRemoveRequest;
+import org.apache.metron.indexing.dao.update.Document;
 import org.apache.metron.indexing.dao.update.OriginalNotFoundException;
 import org.apache.metron.indexing.dao.update.PatchRequest;
 import org.apache.metron.indexing.dao.update.ReplaceRequest;
@@ -25,8 +26,8 @@ import org.apache.metron.rest.RestException;
 
 public interface UpdateService {
 
-  void patch(PatchRequest request) throws RestException, OriginalNotFoundException;
-  void replace(ReplaceRequest request) throws RestException;
-  void addComment(CommentAddRemoveRequest request) throws RestException;
-  void removeComment(CommentAddRemoveRequest request) throws RestException;
+  Document patch(PatchRequest request) throws RestException, OriginalNotFoundException;
+  Document replace(ReplaceRequest request) throws RestException;
+  Document addComment(CommentAddRemoveRequest request) throws RestException;
+  Document removeComment(CommentAddRemoveRequest request) throws RestException;
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/MetaAlertServiceImpl.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/MetaAlertServiceImpl.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/MetaAlertServiceImpl.java
index 3f9b3e4..bd8419f 100644
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/MetaAlertServiceImpl.java
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/MetaAlertServiceImpl.java
@@ -23,11 +23,11 @@ import org.apache.metron.indexing.dao.IndexDao;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertDao;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertAddRemoveRequest;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateRequest;
-import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateResponse;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertStatus;
 import org.apache.metron.indexing.dao.search.InvalidCreateException;
 import org.apache.metron.indexing.dao.search.InvalidSearchException;
 import org.apache.metron.indexing.dao.search.SearchResponse;
+import org.apache.metron.indexing.dao.update.Document;
 import org.apache.metron.rest.RestException;
 import org.apache.metron.rest.service.MetaAlertService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -47,7 +47,7 @@ public class MetaAlertServiceImpl implements MetaAlertService {
   }
 
   @Override
-  public MetaAlertCreateResponse create(MetaAlertCreateRequest createRequest) throws RestException {
+  public Document create(MetaAlertCreateRequest createRequest) throws RestException {
     try {
       return dao.createMetaAlert(createRequest);
     } catch (InvalidCreateException | IOException e) {
@@ -65,25 +65,25 @@ public class MetaAlertServiceImpl implements MetaAlertService {
   }
 
   @Override
-  public boolean addAlertsToMetaAlert(MetaAlertAddRemoveRequest metaAlertAddRemoveRequest) throws RestException {
+  public Document addAlertsToMetaAlert(MetaAlertAddRemoveRequest metaAlertAddRemoveRequest) throws RestException {
     try {
       return dao.addAlertsToMetaAlert(metaAlertAddRemoveRequest.getMetaAlertGuid(), metaAlertAddRemoveRequest.getAlerts());
-    } catch (IOException ioe) {
-      throw new RestException(ioe.getMessage(), ioe);
+    } catch (IOException | IllegalStateException e) {
+      throw new RestException(e.getMessage(), e);
     }
   }
 
   @Override
-  public boolean removeAlertsFromMetaAlert(MetaAlertAddRemoveRequest metaAlertAddRemoveRequest) throws RestException {
+  public Document removeAlertsFromMetaAlert(MetaAlertAddRemoveRequest metaAlertAddRemoveRequest) throws RestException {
     try {
       return dao.removeAlertsFromMetaAlert(metaAlertAddRemoveRequest.getMetaAlertGuid(), metaAlertAddRemoveRequest.getAlerts());
-    } catch (IOException ioe) {
-      throw new RestException(ioe.getMessage(), ioe);
+    } catch (IOException | IllegalStateException e) {
+      throw new RestException(e.getMessage(), e);
     }
   }
 
   @Override
-  public boolean updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status)
+  public Document updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status)
       throws RestException {
     try {
       return dao.updateMetaAlertStatus(metaAlertGuid, status);

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/UpdateServiceImpl.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/UpdateServiceImpl.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/UpdateServiceImpl.java
index 49490fd..63dd4c0 100644
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/UpdateServiceImpl.java
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/UpdateServiceImpl.java
@@ -19,6 +19,7 @@ package org.apache.metron.rest.service.impl;
 
 import org.apache.metron.indexing.dao.IndexDao;
 import org.apache.metron.indexing.dao.update.CommentAddRemoveRequest;
+import org.apache.metron.indexing.dao.update.Document;
 import org.apache.metron.indexing.dao.update.OriginalNotFoundException;
 import org.apache.metron.indexing.dao.update.PatchRequest;
 import org.apache.metron.indexing.dao.update.ReplaceRequest;
@@ -43,37 +44,36 @@ public class UpdateServiceImpl implements UpdateService {
 
 
   @Override
-  public void patch(PatchRequest request) throws RestException, OriginalNotFoundException {
+  public Document patch(PatchRequest request) throws RestException, OriginalNotFoundException {
     try {
-      dao.patch(dao, request, Optional.of(System.currentTimeMillis()));
+      return dao.patch(dao, request, Optional.of(System.currentTimeMillis()));
     } catch (Exception e) {
-
       throw new RestException(e.getMessage(), e);
     }
   }
 
   @Override
-  public void replace(ReplaceRequest request) throws RestException {
+  public Document replace(ReplaceRequest request) throws RestException {
     try {
-      dao.replace(request, Optional.of(System.currentTimeMillis()));
+      return dao.replace(request, Optional.of(System.currentTimeMillis()));
     } catch (Exception e) {
       throw new RestException(e.getMessage(), e);
     }
   }
 
   @Override
-  public void addComment(CommentAddRemoveRequest request) throws RestException {
+  public Document addComment(CommentAddRemoveRequest request) throws RestException {
     try {
-      dao.addCommentToAlert(request);
+      return dao.addCommentToAlert(request);
     } catch (Exception e) {
       throw new RestException(e.getMessage(), e);
     }
   }
 
   @Override
-  public void removeComment(CommentAddRemoveRequest request) throws RestException {
+  public Document removeComment(CommentAddRemoveRequest request) throws RestException {
     try {
-      dao.removeCommentFromAlert(request);
+      return dao.removeCommentFromAlert(request);
     } catch (Exception e) {
       throw new RestException(e.getMessage(), e);
     }

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/MetaAlertControllerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/MetaAlertControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/MetaAlertControllerIntegrationTest.java
index b216990..8e3abe5 100644
--- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/MetaAlertControllerIntegrationTest.java
+++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/MetaAlertControllerIntegrationTest.java
@@ -19,6 +19,10 @@
 package org.apache.metron.rest.controller;
 
 import static org.apache.metron.rest.MetronRestConstants.TEST_PROFILE;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.notNullValue;
 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;
@@ -29,15 +33,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 
 import com.google.common.collect.ImmutableMap;
 import java.util.ArrayList;
-import java.util.Arrays;
 import org.adrianwalker.multilinestring.Multiline;
 import org.apache.curator.framework.CuratorFramework;
 import org.apache.metron.common.utils.JSONUtils;
 import org.apache.metron.indexing.dao.InMemoryMetaAlertDao;
 import org.apache.metron.indexing.dao.SearchIntegrationTest;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertAddRemoveRequest;
-import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateRequest;
-import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateResponse;
+import org.apache.metron.indexing.dao.metaalert.MetaAlertConstants;
 import org.apache.metron.indexing.dao.search.GetRequest;
 import org.apache.metron.rest.service.MetaAlertService;
 import org.junit.After;
@@ -96,8 +98,8 @@ public class MetaAlertControllerIntegrationTest extends DaoControllerTest {
 
   /**
    * [
-   *{"guid":"meta_1","metron_alert":[{"guid":"bro_1"}],"average":"5.0","min":"5.0","median":"5.0","max":"5.0","count":"1.0","sum":"5.0"},
-   *{"guid":"meta_2","metron_alert":[{"guid":"bro_1"},{"guid":"bro_2"},{"guid":"snort_1"}],"average":"5.0","min":"0.0","median":"5.0","max":"10.0","count":"3.0","sum":"15.0"}
+   *{"guid":"meta_1","metron_alert":[{"guid":"bro_1", "source.type":"bro"}],"average":"5.0","min":"5.0","median":"5.0","max":"5.0","count":"1.0","sum":"5.0", "status":"active"},
+   *{"guid":"meta_2","metron_alert":[{"guid":"bro_1", "source.type":"bro"},{"guid":"bro_2", "source.type":"bro"},{"guid":"snort_1", "source.type":"snort"}],"average":"5.0","min":"0.0","median":"5.0","max":"10.0","count":"3.0","sum":"15.0"}
    * ]
    */
   @Multiline
@@ -163,58 +165,32 @@ public class MetaAlertControllerIntegrationTest extends DaoControllerTest {
         .andExpect(jsonPath("$.results[0].source.count").value(3.0))
         .andExpect(jsonPath("$.results[1].source.guid").value("meta_1"))
         .andExpect(jsonPath("$.results[1].source.count").value(1.0));
+  }
 
-    result = this.mockMvc.perform(
-        post(metaalertUrl + "/create")
-            .with(httpBasic(user, password)).with(csrf())
-            .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
-            .content(create));
-    result.andExpect(status().isOk());
-
-    // Test that we can find the newly created meta alert by the sub alerts
-    guid = "bro_1";
-    result = this.mockMvc.perform(
-        post(metaalertUrl + "/searchByAlert")
-            .with(httpBasic(user, password)).with(csrf())
-            .contentType(MediaType.parseMediaType("text/plain;charset=UTF-8"))
-            .content(guid));
-    result.andExpect(status().isOk())
-        .andExpect(
-            content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
-        .andExpect(jsonPath("$.total").value(3))
-        .andExpect(jsonPath("$.results[0].source.guid").value("meta_3"))
-        .andExpect(jsonPath("$.results[0].source.count").value(2.0))
-        .andExpect(jsonPath("$.results[1].source.guid").value("meta_2"))
-        .andExpect(jsonPath("$.results[1].source.count").value(3.0))
-        .andExpect(jsonPath("$.results[2].source.guid").value("meta_1"))
-        .andExpect(jsonPath("$.results[2].source.count").value(1.0));
-
-    guid = "snort_2";
-    result = this.mockMvc.perform(
-        post(metaalertUrl + "/searchByAlert")
-            .with(httpBasic(user, password)).with(csrf())
-            .contentType(MediaType.parseMediaType("text/plain;charset=UTF-8"))
-            .content(guid));
+  @Test
+  public void shouldCreateMetaAlert() throws Exception {
+    ResultActions result = this.mockMvc.perform(
+            post(metaalertUrl + "/create")
+                    .with(httpBasic(user, password)).with(csrf())
+                    .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
+                    .content(create));
     result.andExpect(status().isOk())
-        .andExpect(
-            content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8")))
-        .andExpect(jsonPath("$.total").value(1))
-        .andExpect(jsonPath("$.results[0].source.guid").value("meta_3"))
-        .andExpect(jsonPath("$.results[0].source.count").value(2.0));
+            .andExpect(jsonPath("$.guid", notNullValue()))
+            .andExpect(jsonPath("$.timestamp", greaterThan(0L)))
+            .andExpect(jsonPath("$.sensorType").value(MetaAlertConstants.METAALERT_TYPE))
+            .andExpect(jsonPath("$.document.timestamp", greaterThan(0L)))
+            .andExpect(jsonPath("$.document['source.type']").value(MetaAlertConstants.METAALERT_TYPE))
+            .andExpect(jsonPath("$.document.status").value("active"))
+            .andExpect(jsonPath("$.document.groups[0]").value("group_one"))
+            .andExpect(jsonPath("$.document.groups[1]").value("group_two"))
+            .andExpect(jsonPath("$.document.metron_alert[0].guid").value("bro_1"))
+            .andExpect(jsonPath("$.document.metron_alert[1].guid").value("snort_2"));
   }
 
   @Test
   public void shouldAddRemoveAlerts() throws Exception {
-    MetaAlertCreateRequest metaAlertCreateRequest = new MetaAlertCreateRequest();
-    metaAlertCreateRequest.setGroups(Arrays.asList("group_one", "group_two"));
-    metaAlertCreateRequest.setAlerts(new ArrayList<GetRequest>() {{
-      add(new GetRequest("bro_1", "bro", "bro_index_2017.01.01.01"));
-      add(new GetRequest("snort_2", "snort", "snort_index_2017.01.01.01"));
-    }});
-    MetaAlertCreateResponse metaAlertCreateResponse = metaAlertService.create(metaAlertCreateRequest);
-
     MetaAlertAddRemoveRequest addRequest = new MetaAlertAddRemoveRequest();
-    addRequest.setMetaAlertGuid(metaAlertCreateResponse.getGuid());
+    addRequest.setMetaAlertGuid("meta_1");
     addRequest.setAlerts(new ArrayList<GetRequest>() {{
       add(new GetRequest("bro_2", "bro", "bro_index_2017.01.01.01"));
       add(new GetRequest("bro_3", "bro", "bro_index_2017.01.01.01"));
@@ -225,10 +201,17 @@ public class MetaAlertControllerIntegrationTest extends DaoControllerTest {
             .with(httpBasic(user, password)).with(csrf())
             .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
             .content(JSONUtils.INSTANCE.toJSON(addRequest, false)));
-    result.andExpect(status().isOk()).andExpect(content().string("true"));
+    result.andExpect(status().isOk())
+            .andExpect(jsonPath("$.guid").value("meta_1"))
+            .andExpect(jsonPath("$.sensorType").value(MetaAlertConstants.METAALERT_TYPE))
+            .andExpect(jsonPath("$.document.metron_alert[0].guid").value("bro_1"))
+            .andExpect(jsonPath("$.document.metron_alert[1].guid").value("bro_2"))
+            .andExpect(jsonPath("$.document.metron_alert[2].metaalerts").value("meta_1"))
+            .andExpect(jsonPath("$.document.metron_alert[2].guid").value("bro_3"))
+            .andExpect(jsonPath("$.document.metron_alert[2].metaalerts").value("meta_1"));
 
     MetaAlertAddRemoveRequest addDuplicateRequest = new MetaAlertAddRemoveRequest();
-    addDuplicateRequest.setMetaAlertGuid(metaAlertCreateResponse.getGuid());
+    addDuplicateRequest.setMetaAlertGuid("meta_1");
     addDuplicateRequest.setAlerts(new ArrayList<GetRequest>() {{
       add(new GetRequest("bro_1", "bro"));
     }});
@@ -238,10 +221,17 @@ public class MetaAlertControllerIntegrationTest extends DaoControllerTest {
             .with(httpBasic(user, password)).with(csrf())
             .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
             .content(JSONUtils.INSTANCE.toJSON(addDuplicateRequest, false)));
-    result.andExpect(status().isOk()).andExpect(content().string("false"));
+    result.andExpect(status().isOk())
+            .andExpect(jsonPath("$.guid").value("meta_1"))
+            .andExpect(jsonPath("$.sensorType").value(MetaAlertConstants.METAALERT_TYPE))
+            .andExpect(jsonPath("$.document.metron_alert[0].guid").value("bro_1"))
+            .andExpect(jsonPath("$.document.metron_alert[1].guid").value("bro_2"))
+            .andExpect(jsonPath("$.document.metron_alert[2].metaalerts").value("meta_1"))
+            .andExpect(jsonPath("$.document.metron_alert[2].guid").value("bro_3"))
+            .andExpect(jsonPath("$.document.metron_alert[2].metaalerts").value("meta_1"));
 
     MetaAlertAddRemoveRequest removeRequest = new MetaAlertAddRemoveRequest();
-    removeRequest.setMetaAlertGuid(metaAlertCreateResponse.getGuid());
+    removeRequest.setMetaAlertGuid("meta_1");
     removeRequest.setAlerts(new ArrayList<GetRequest>() {{
       add(new GetRequest("bro_2", "bro"));
       add(new GetRequest("bro_3", "bro"));
@@ -252,12 +242,16 @@ public class MetaAlertControllerIntegrationTest extends DaoControllerTest {
             .with(httpBasic(user, password)).with(csrf())
             .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
             .content(JSONUtils.INSTANCE.toJSON(removeRequest, false)));
-    result.andExpect(status().isOk()).andExpect(content().string("true"));
+    result.andExpect(status().isOk())
+            .andExpect(jsonPath("$.guid").value("meta_1"))
+            .andExpect(jsonPath("$.sensorType").value(MetaAlertConstants.METAALERT_TYPE))
+            .andExpect(jsonPath("$.document.metron_alert.*", hasSize(equalTo(1))))
+            .andExpect(jsonPath("$.document.metron_alert[0].guid").value("bro_1"));
 
     MetaAlertAddRemoveRequest removeMissingRequest = new MetaAlertAddRemoveRequest();
-    addRequest.setMetaAlertGuid(metaAlertCreateResponse.getGuid());
+    removeMissingRequest.setMetaAlertGuid("meta_1");
     removeMissingRequest.setAlerts(new ArrayList<GetRequest>() {{
-      add(new GetRequest("bro_1", "bro"));
+      add(new GetRequest("bro_2", "bro"));
     }});
 
     result = this.mockMvc.perform(
@@ -265,31 +259,47 @@ public class MetaAlertControllerIntegrationTest extends DaoControllerTest {
             .with(httpBasic(user, password)).with(csrf())
             .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
             .content(JSONUtils.INSTANCE.toJSON(removeMissingRequest, false)));
-    result.andExpect(status().isOk()).andExpect(content().string("false"));
+    result.andExpect(status().isOk())
+            .andExpect(jsonPath("$.guid").value("meta_1"))
+            .andExpect(jsonPath("$.sensorType").value(MetaAlertConstants.METAALERT_TYPE))
+            .andExpect(jsonPath("$.document.metron_alert.*", hasSize(equalTo(1))))
+            .andExpect(jsonPath("$.document.metron_alert[0].guid").value("bro_1"));
+
+    MetaAlertAddRemoveRequest emptyMetaAlertRequest = new MetaAlertAddRemoveRequest();
+    emptyMetaAlertRequest.setMetaAlertGuid("meta_1");
+    emptyMetaAlertRequest.setAlerts(new ArrayList<GetRequest>() {{
+      add(new GetRequest("bro_1", "bro"));
+    }});
+
+    result = this.mockMvc.perform(
+            post(metaalertUrl + "/remove/alert")
+                    .with(httpBasic(user, password)).with(csrf())
+                    .contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))
+                    .content(JSONUtils.INSTANCE.toJSON(emptyMetaAlertRequest, false)));
+    result.andExpect(status().isInternalServerError())
+            .andExpect(jsonPath("$.message").value("Removing these alerts will result in an empty meta alert.  Empty meta alerts are not allowed."))
+            .andExpect(jsonPath("$.fullMessage").value("IllegalStateException: Removing these alerts will result in an empty meta alert.  Empty meta alerts are not allowed."));
   }
 
   @Test
   public void shouldUpdateStatus() throws Exception {
-    MetaAlertCreateRequest metaAlertCreateRequest = new MetaAlertCreateRequest();
-    metaAlertCreateRequest.setGroups(Arrays.asList("group_one", "group_two"));
-    metaAlertCreateRequest.setAlerts(new ArrayList<GetRequest>() {{
-      add(new GetRequest("bro_1", "bro", "bro_index_2017.01.01.01"));
-      add(new GetRequest("snort_2", "snort", "snort_index_2017.01.01.01"));
-    }});
-
-    MetaAlertCreateResponse metaAlertCreateResponse = metaAlertService.create(metaAlertCreateRequest);
-
     ResultActions result = this.mockMvc.perform(
-        post(metaalertUrl + "/update/status/" + metaAlertCreateResponse.getGuid() + "/inactive")
+        post(metaalertUrl + "/update/status/meta_2/inactive")
             .with(httpBasic(user, password)).with(csrf())
             .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")));
-    result.andExpect(status().isOk()).andExpect(content().string("true"));
+    result.andExpect(status().isOk())
+            .andExpect(jsonPath("$.guid").value("meta_2"))
+            .andExpect(jsonPath("$.sensorType").value(MetaAlertConstants.METAALERT_TYPE))
+            .andExpect(jsonPath("$.document.status").value("inactive"));
 
     result = this.mockMvc.perform(
-        post(metaalertUrl + "/update/status/" + metaAlertCreateResponse.getGuid() + "/inactive")
+        post(metaalertUrl + "/update/status/meta_2/active")
             .with(httpBasic(user, password)).with(csrf())
             .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")));
-    result.andExpect(status().isOk()).andExpect(content().string("false"));
+    result.andExpect(status().isOk())
+            .andExpect(jsonPath("$.guid").value("meta_2"))
+            .andExpect(jsonPath("$.sensorType").value(MetaAlertConstants.METAALERT_TYPE))
+            .andExpect(jsonPath("$.document.status").value("active"));
   }
 
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/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 59f25f0..9f6e1a1 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
@@ -138,34 +138,34 @@ public class ElasticsearchDao implements IndexDao {
   }
 
   @Override
-  public void update(Document update, Optional<String> index) throws IOException {
-    updateDao.update(update, index);
+  public Document update(Document update, Optional<String> index) throws IOException {
+    return updateDao.update(update, index);
   }
 
   @Override
-  public void batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
-    updateDao.batchUpdate(updates);
+  public Map<Document, Optional<String>> batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
+    return updateDao.batchUpdate(updates);
   }
 
   @Override
-  public void patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request, Optional<Long> timestamp)
+  public Document patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request, Optional<Long> timestamp)
       throws OriginalNotFoundException, IOException {
-    updateDao.patch(retrieveLatestDao, request, timestamp);
+    return updateDao.patch(retrieveLatestDao, request, timestamp);
   }
 
   @Override
-  public void replace(ReplaceRequest request, Optional<Long> timestamp) throws IOException {
-    updateDao.replace(request, timestamp);
+  public Document replace(ReplaceRequest request, Optional<Long> timestamp) throws IOException {
+    return updateDao.replace(request, timestamp);
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
-    updateDao.addCommentToAlert(request);
+  public Document addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
+    return updateDao.addCommentToAlert(request);
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
-    updateDao.removeCommentFromAlert(request);
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
+    return updateDao.removeCommentFromAlert(request);
   }
 
   @Override
@@ -179,13 +179,13 @@ public class ElasticsearchDao implements IndexDao {
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
-    this.updateDao.addCommentToAlert(request, latest);
+  public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
+    return this.updateDao.addCommentToAlert(request, latest);
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
-    this.updateDao.removeCommentFromAlert(request, latest);
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
+    return this.updateDao.removeCommentFromAlert(request, latest);
   }
 
   protected Optional<String> getIndexName(String guid, String sensorType) {

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchMetaAlertDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchMetaAlertDao.java b/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchMetaAlertDao.java
index 55123a5..fc0b20c 100644
--- a/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchMetaAlertDao.java
+++ b/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchMetaAlertDao.java
@@ -26,7 +26,6 @@ import org.apache.metron.indexing.dao.RetrieveLatestDao;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertConfig;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertConstants;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateRequest;
-import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateResponse;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertDao;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertStatus;
 import org.apache.metron.indexing.dao.search.FieldType;
@@ -182,25 +181,25 @@ public class ElasticsearchMetaAlertDao implements MetaAlertDao {
   }
 
   @Override
-  public MetaAlertCreateResponse createMetaAlert(MetaAlertCreateRequest request)
+  public Document createMetaAlert(MetaAlertCreateRequest request)
       throws InvalidCreateException, IOException {
     return metaAlertUpdateDao.createMetaAlert(request);
   }
 
   @Override
-  public boolean addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
+  public Document addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
       throws IOException {
     return metaAlertUpdateDao.addAlertsToMetaAlert(metaAlertGuid, alertRequests);
   }
 
   @Override
-  public boolean removeAlertsFromMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
+  public Document removeAlertsFromMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
       throws IOException {
     return metaAlertUpdateDao.removeAlertsFromMetaAlert(metaAlertGuid, alertRequests);
   }
 
   @Override
-  public boolean updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status)
+  public Document updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status)
       throws IOException {
     return metaAlertUpdateDao.updateMetaAlertStatus(metaAlertGuid, status);
   }
@@ -216,40 +215,40 @@ public class ElasticsearchMetaAlertDao implements MetaAlertDao {
   }
 
   @Override
-  public void update(Document update, Optional<String> index) throws IOException {
-    metaAlertUpdateDao.update(update, index);
+  public Document update(Document update, Optional<String> index) throws IOException {
+    return metaAlertUpdateDao.update(update, index);
   }
 
   @Override
-  public void batchUpdate(Map<Document, Optional<String>> updates) {
-    metaAlertUpdateDao.batchUpdate(updates);
+  public Map<Document, Optional<String>> batchUpdate(Map<Document, Optional<String>> updates) {
+    return metaAlertUpdateDao.batchUpdate(updates);
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
-    indexDao.addCommentToAlert(request);
+  public Document addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
+    return indexDao.addCommentToAlert(request);
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
-    indexDao.removeCommentFromAlert(request);
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
+    return indexDao.removeCommentFromAlert(request);
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
-    indexDao.addCommentToAlert(request, latest);
+  public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
+    return indexDao.addCommentToAlert(request, latest);
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
-    indexDao.removeCommentFromAlert(request, latest);
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
+    return indexDao.removeCommentFromAlert(request, latest);
   }
 
   @Override
-  public void patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request,
+  public Document patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request,
       Optional<Long> timestamp)
       throws OriginalNotFoundException, IOException {
-    metaAlertUpdateDao.patch(retrieveLatestDao, request, timestamp);
+    return metaAlertUpdateDao.patch(retrieveLatestDao, request, timestamp);
   }
 
   public void setPageSize(int pageSize) {

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchMetaAlertUpdateDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchMetaAlertUpdateDao.java b/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchMetaAlertUpdateDao.java
index bb79b7a..3b67891 100644
--- a/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchMetaAlertUpdateDao.java
+++ b/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchMetaAlertUpdateDao.java
@@ -30,7 +30,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import org.apache.lucene.search.join.ScoreMode;
 import org.apache.metron.common.Constants;
@@ -38,8 +37,6 @@ import org.apache.metron.elasticsearch.utils.ElasticsearchUtils;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertConfig;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertConstants;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateRequest;
-import org.apache.metron.indexing.dao.metaalert.MetaAlertCreateResponse;
-import org.apache.metron.indexing.dao.metaalert.MetaAlertDao;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertRetrieveLatestDao;
 import org.apache.metron.indexing.dao.metaalert.MetaAlertStatus;
 import org.apache.metron.indexing.dao.metaalert.MetaScores;
@@ -82,7 +79,7 @@ public class ElasticsearchMetaAlertUpdateDao extends AbstractLuceneMetaAlertUpda
 
   @Override
   @SuppressWarnings("unchecked")
-  public MetaAlertCreateResponse createMetaAlert(MetaAlertCreateRequest request)
+  public Document createMetaAlert(MetaAlertCreateRequest request)
       throws InvalidCreateException, IOException {
     List<GetRequest> alertRequests = request.getAlerts();
     if (request.getAlerts().isEmpty()) {
@@ -134,39 +131,14 @@ public class ElasticsearchMetaAlertUpdateDao extends AbstractLuceneMetaAlertUpda
       // Kick off any updates.
       update(updates);
 
-      MetaAlertCreateResponse createResponse = new MetaAlertCreateResponse();
-      createResponse.setCreated(true);
-      createResponse.setGuid(metaAlert.getGuid());
-      return createResponse;
+      return metaAlert;
     } catch (IOException ioe) {
       throw new InvalidCreateException("Unable to create meta alert", ioe);
     }
   }
 
-  /**
-   * Adds alerts to a metaalert, based on a list of GetRequests provided for retrieval.
-   * @param metaAlertGuid The GUID of the metaalert to be given new children.
-   * @param alertRequests GetRequests for the appropriate alerts to add.
-   * @return True if metaalert is modified, false otherwise.
-   */
-  public boolean addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
-      throws IOException {
-
-    Document metaAlert = retrieveLatestDao
-        .getLatest(metaAlertGuid, MetaAlertConstants.METAALERT_TYPE);
-    if (MetaAlertStatus.ACTIVE.getStatusString()
-        .equals(metaAlert.getDocument().get(MetaAlertConstants.STATUS_FIELD))) {
-      Iterable<Document> alerts = retrieveLatestDao.getAllLatest(alertRequests);
-      Map<Document, Optional<String>> updates = buildAddAlertToMetaAlertUpdates(metaAlert, alerts);
-      update(updates);
-      return updates.size() != 0;
-    } else {
-      throw new IllegalStateException("Adding alerts to an INACTIVE meta alert is not allowed");
-    }
-  }
-
   @Override
-  public void update(Document update, Optional<String> index) throws IOException {
+  public Document update(Document update, Optional<String> index) throws IOException {
     if (MetaAlertConstants.METAALERT_TYPE.equals(update.getSensorType())) {
       // We've been passed an update to the meta alert.
       throw new UnsupportedOperationException("Meta alerts cannot be directly updated");
@@ -195,29 +167,31 @@ public class ElasticsearchMetaAlertUpdateDao extends AbstractLuceneMetaAlertUpda
 
       // Run the alert's update
       elasticsearchDao.batchUpdate(updates);
+
+      return update;
     }
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
-    getUpdateDao().addCommentToAlert(request);
+  public Document addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
+    return getUpdateDao().addCommentToAlert(request);
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
-    getUpdateDao().removeCommentFromAlert(request);
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
+    return getUpdateDao().removeCommentFromAlert(request);
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request, Document latest)
+  public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest)
       throws IOException {
-    getUpdateDao().addCommentToAlert(request, latest);
+    return getUpdateDao().addCommentToAlert(request, latest);
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest)
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest)
       throws IOException {
-    getUpdateDao().removeCommentFromAlert(request, latest);
+    return getUpdateDao().removeCommentFromAlert(request, latest);
   }
 
   /**
@@ -243,12 +217,11 @@ public class ElasticsearchMetaAlertUpdateDao extends AbstractLuceneMetaAlertUpda
   }
 
 
-  protected boolean replaceAlertInMetaAlert(Document metaAlert, Document alert) {
+  protected void replaceAlertInMetaAlert(Document metaAlert, Document alert) {
     boolean metaAlertUpdated = removeAlertsFromMetaAlert(metaAlert,
         Collections.singleton(alert.getGuid()));
     if (metaAlertUpdated) {
       addAlertsToMetaAlert(metaAlert, Collections.singleton(alert));
     }
-    return metaAlertUpdated;
   }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchUpdateDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchUpdateDao.java b/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchUpdateDao.java
index f2b08d2..6843ac7 100644
--- a/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchUpdateDao.java
+++ b/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchUpdateDao.java
@@ -60,7 +60,7 @@ public class ElasticsearchUpdateDao implements UpdateDao {
   }
 
   @Override
-  public void update(Document update, Optional<String> index) throws IOException {
+  public Document update(Document update, Optional<String> index) throws IOException {
     String indexPostfix = ElasticsearchUtils
         .getIndexFormat(accessConfig.getGlobalConfigSupplier().get()).format(new Date());
     String sensorType = update.getSensorType();
@@ -79,10 +79,11 @@ public class ElasticsearchUpdateDao implements UpdateDao {
     } catch (Exception e) {
       throw new IOException(e.getMessage(), e);
     }
+    return update;
   }
 
   @Override
-  public void batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
+  public Map<Document, Optional<String>> batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
     String indexPostfix = ElasticsearchUtils
         .getIndexFormat(accessConfig.getGlobalConfigSupplier().get()).format(new Date());
 
@@ -108,20 +109,22 @@ public class ElasticsearchUpdateDao implements UpdateDao {
       throw new IOException(
           "ElasticsearchDao upsert failed: " + bulkResponse.buildFailureMessage());
     }
+    return updates;
   }
 
   @Override
   @SuppressWarnings("unchecked")
-  public void addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
+  public Document addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
     Document latest = retrieveLatestDao.getLatest(request.getGuid(), request.getSensorType());
-    addCommentToAlert(request, latest);
+    return addCommentToAlert(request, latest);
   }
 
   @Override
   @SuppressWarnings("unchecked")
-  public void addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
-    if (latest == null) {
-      return;
+  public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
+    if (latest == null || latest.getDocument() == null) {
+      throw new IOException(String.format("Unable to add comment. Document with guid %s cannot be found.",
+              request.getGuid()));
     }
     List<Map<String, Object>> commentsField = (List<Map<String, Object>>) latest.getDocument()
         .getOrDefault(COMMENTS_FIELD, new ArrayList<>());
@@ -133,25 +136,30 @@ public class ElasticsearchUpdateDao implements UpdateDao {
 
     Document newVersion = new Document(latest);
     newVersion.getDocument().put(COMMENTS_FIELD, originalComments);
-    update(newVersion, Optional.empty());
+    return update(newVersion, Optional.empty());
   }
 
   @Override
   @SuppressWarnings("unchecked")
-  public void removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
     Document latest = retrieveLatestDao.getLatest(request.getGuid(), request.getSensorType());
-    removeCommentFromAlert(request, latest);
+    return removeCommentFromAlert(request, latest);
   }
 
   @Override
   @SuppressWarnings("unchecked")
-  public void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
-    if (latest == null) {
-      return;
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
+    if (latest == null || latest.getDocument() == null) {
+      throw new IOException(String.format("Unable to remove comment. Document with guid %s cannot be found.",
+              request.getGuid()));
     }
-    List<Map<String, Object>> commentsField = (List<Map<String, Object>>) latest.getDocument()
-        .getOrDefault(COMMENTS_FIELD, new ArrayList<>());
-    List<Map<String, Object>> originalComments = new ArrayList<>(commentsField);
+    List<Map<String, Object>> commentMap = (List<Map<String, Object>>) latest.getDocument().get(COMMENTS_FIELD);
+    // Can't remove anything if there's nothing there
+    if (commentMap == null) {
+      throw new IOException(String.format("Unable to remove comment. Document with guid %s has no comments.",
+              request.getGuid()));
+    }
+    List<Map<String, Object>> originalComments = new ArrayList<>(commentMap);
 
     List<AlertComment> alertComments = new ArrayList<>();
     for (Map<String, Object> commentRaw : originalComments) {
@@ -170,7 +178,7 @@ public class ElasticsearchUpdateDao implements UpdateDao {
       newVersion.getDocument().remove(COMMENTS_FIELD);
     }
 
-    update(newVersion, Optional.empty());
+    return update(newVersion, Optional.empty());
   }
 
   protected String getIndexName(Document update, Optional<String> index, String indexPostFix) {

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-elasticsearch/src/test/java/org/apache/metron/elasticsearch/dao/ElasticsearchMetaAlertDaoTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-elasticsearch/src/test/java/org/apache/metron/elasticsearch/dao/ElasticsearchMetaAlertDaoTest.java b/metron-platform/metron-elasticsearch/src/test/java/org/apache/metron/elasticsearch/dao/ElasticsearchMetaAlertDaoTest.java
index b1da2a4..cabb992 100644
--- a/metron-platform/metron-elasticsearch/src/test/java/org/apache/metron/elasticsearch/dao/ElasticsearchMetaAlertDaoTest.java
+++ b/metron-platform/metron-elasticsearch/src/test/java/org/apache/metron/elasticsearch/dao/ElasticsearchMetaAlertDaoTest.java
@@ -19,8 +19,6 @@
 package org.apache.metron.elasticsearch.dao;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -44,13 +42,6 @@ import org.apache.metron.indexing.dao.update.Document;
 import org.elasticsearch.index.IndexNotFoundException;
 import org.junit.Test;
 
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -88,11 +79,13 @@ public class ElasticsearchMetaAlertDaoTest {
       }
 
       @Override
-      public void update(Document update, Optional<String> index) {
+      public Document update(Document update, Optional<String> index) {
+        return update;
       }
 
       @Override
-      public void batchUpdate(Map<Document, Optional<String>> updates) {
+      public Map<Document, Optional<String>> batchUpdate(Map<Document, Optional<String>> updates) {
+        return updates;
       }
 
       @Override
@@ -101,19 +94,23 @@ public class ElasticsearchMetaAlertDaoTest {
       }
 
       @Override
-      public void addCommentToAlert(CommentAddRemoveRequest request) {
+      public Document addCommentToAlert(CommentAddRemoveRequest request) {
+        return null;
       }
 
       @Override
-      public void removeCommentFromAlert(CommentAddRemoveRequest request) {
+      public Document removeCommentFromAlert(CommentAddRemoveRequest request) {
+        return null;
       }
 
       @Override
-      public void addCommentToAlert(CommentAddRemoveRequest request, Document latest) {
+      public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest) {
+        return null;
       }
 
       @Override
-      public void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) {
+      public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) {
+        return null;
       }
     };
     ElasticsearchMetaAlertDao metaAlertDao = new ElasticsearchMetaAlertDao();

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-elasticsearch/src/test/java/org/apache/metron/elasticsearch/dao/ElasticsearchUpdateDaoTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-elasticsearch/src/test/java/org/apache/metron/elasticsearch/dao/ElasticsearchUpdateDaoTest.java b/metron-platform/metron-elasticsearch/src/test/java/org/apache/metron/elasticsearch/dao/ElasticsearchUpdateDaoTest.java
new file mode 100644
index 0000000..3b48a60
--- /dev/null
+++ b/metron-platform/metron-elasticsearch/src/test/java/org/apache/metron/elasticsearch/dao/ElasticsearchUpdateDaoTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.elasticsearch.dao;
+
+import org.apache.metron.indexing.dao.AccessConfig;
+import org.apache.metron.indexing.dao.UpdateDaoTest;
+import org.apache.metron.indexing.dao.update.UpdateDao;
+import org.elasticsearch.client.transport.TransportClient;
+import org.junit.Before;
+
+import static org.mockito.Mockito.mock;
+
+/**
+ * This class returns the ElasticsearchUpdateDao implementation to be used in UpdateDaoTest.  UpdateDaoTest contains a
+ * common set of tests that all Dao implementations must pass.
+ */
+public class ElasticsearchUpdateDaoTest extends UpdateDaoTest {
+
+  private TransportClient client;
+  private AccessConfig accessConfig;
+  private ElasticsearchRetrieveLatestDao retrieveLatestDao;
+  private ElasticsearchUpdateDao updateDao;
+
+  @Before
+  public void setup() {
+    client = mock(TransportClient.class);
+    accessConfig = new AccessConfig();
+    retrieveLatestDao = mock(ElasticsearchRetrieveLatestDao.class);
+    updateDao = new ElasticsearchUpdateDao(client, accessConfig, retrieveLatestDao);
+  }
+
+  @Override
+  public UpdateDao getUpdateDao() {
+    return updateDao;
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/HBaseDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/HBaseDao.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/HBaseDao.java
index 6c646de..71d0544 100644
--- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/HBaseDao.java
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/HBaseDao.java
@@ -238,13 +238,14 @@ public class HBaseDao implements IndexDao {
   }
 
   @Override
-  public synchronized void update(Document update, Optional<String> index) throws IOException {
+  public synchronized Document update(Document update, Optional<String> index) throws IOException {
     Put put = buildPut(update);
     getTableInterface().put(put);
+    return update;
   }
 
   @Override
-  public void batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
+  public Map<Document, Optional<String>> batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
     List<Put> puts = new ArrayList<>();
     for (Map.Entry<Document, Optional<String>> updateEntry : updates.entrySet()) {
       Document update = updateEntry.getKey();
@@ -253,6 +254,7 @@ public class HBaseDao implements IndexDao {
       puts.add(put);
     }
     getTableInterface().put(puts);
+    return updates;
   }
 
   protected Get buildGet(GetRequest getRequest) throws IOException {
@@ -280,16 +282,17 @@ public class HBaseDao implements IndexDao {
 
   @Override
   @SuppressWarnings("unchecked")
-  public void addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
+  public Document addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
     Document latest = getLatest(request.getGuid(), request.getSensorType());
-    addCommentToAlert(request, latest);
+    return addCommentToAlert(request, latest);
   }
 
   @Override
   @SuppressWarnings("unchecked")
-  public void addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
+  public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
     if (latest == null || latest.getDocument() == null) {
-      throw new IOException("Unable to add comment to document that doesn't exist");
+      throw new IOException(String.format("Unable to add comment. Document with guid %s cannot be found.",
+              request.getGuid()));
     }
 
     List<Map<String, Object>> comments = (List<Map<String, Object>>) latest.getDocument()
@@ -309,28 +312,30 @@ public class HBaseDao implements IndexDao {
 
     Document newVersion = new Document(latest);
     newVersion.getDocument().put(COMMENTS_FIELD, commentsMap);
-    update(newVersion, Optional.empty());
+    return update(newVersion, Optional.empty());
   }
 
   @Override
   @SuppressWarnings("unchecked")
-  public void removeCommentFromAlert(CommentAddRemoveRequest request)
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request)
       throws IOException {
     Document latest = getLatest(request.getGuid(), request.getSensorType());
-    removeCommentFromAlert(request, latest);
+    return removeCommentFromAlert(request, latest);
   }
 
   @Override
   @SuppressWarnings("unchecked")
-  public void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest)
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest)
       throws IOException {
     if (latest == null || latest.getDocument() == null) {
-      throw new IOException("Unable to remove comment document that doesn't exist");
+      throw new IOException(String.format("Unable to remove comment. Document with guid %s cannot be found.",
+              request.getGuid()));
     }
     List<Map<String, Object>> commentMap = (List<Map<String, Object>>) latest.getDocument().get(COMMENTS_FIELD);
     // Can't remove anything if there's nothing there
     if (commentMap == null) {
-      return;
+      throw new IOException(String.format("Unable to remove comment. Document with guid %s has no comments.",
+              request.getGuid()));
     }
     List<Map<String, Object>> originalComments = new ArrayList<>(commentMap);
     List<AlertComment> comments = new ArrayList<>();
@@ -349,6 +354,6 @@ public class HBaseDao implements IndexDao {
       newVersion.getDocument().remove(COMMENTS_FIELD);
     }
 
-    update(newVersion, Optional.empty());
+    return update(newVersion, Optional.empty());
   }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/MultiIndexDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/MultiIndexDao.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/MultiIndexDao.java
index 420c775..c3e2108 100644
--- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/MultiIndexDao.java
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/MultiIndexDao.java
@@ -25,7 +25,6 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -58,7 +57,7 @@ public class MultiIndexDao implements IndexDao {
   }
 
   @Override
-  public void update(final Document update, Optional<String> index) throws IOException {
+  public Document update(final Document update, Optional<String> index) throws IOException {
     List<String> exceptions =
     indices.parallelStream().map(dao -> {
       try {
@@ -71,10 +70,11 @@ public class MultiIndexDao implements IndexDao {
     if(exceptions.size() > 0) {
       throw new IOException(Joiner.on("\n").join(exceptions));
     }
+    return update;
   }
 
   @Override
-  public void batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
+  public Map<Document, Optional<String>> batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
     List<String> exceptions =
         indices.parallelStream().map(dao -> {
           try {
@@ -87,6 +87,7 @@ public class MultiIndexDao implements IndexDao {
     if (exceptions.size() > 0) {
       throw new IOException(Joiner.on("\n").join(exceptions));
     }
+    return updates;
   }
 
   @Override
@@ -101,51 +102,62 @@ public class MultiIndexDao implements IndexDao {
   }
 
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
+  public Document addCommentToAlert(CommentAddRemoveRequest request) throws IOException {
     Document latest = getLatest(request.getGuid(), request.getSensorType());
-    addCommentToAlert(request, latest);
+    return addCommentToAlert(request, latest);
   }
 
-
+  /**
+   * Adds comments to an alert.  Updates are written to each Dao in parallel with the assumption that all updates
+   * are identical.  The first update to be applied is returned as the current version of the alert with comments added.
+   * @param request Request to add comments
+   * @param latest The latest version of the alert the comments will be added to.
+   * @return The complete alert document with comments added.
+   * @throws IOException
+   */
   @Override
-  public void addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
-    List<String> exceptions =
-        indices.parallelStream().map(dao -> {
-          try {
-            dao.addCommentToAlert(request, latest);
-            return null;
-          } catch (Throwable e) {
-            return dao.getClass() + ": " + e.getMessage() + "\n" + ExceptionUtils.getStackTrace(e);
-          }
-        }).filter(Objects::nonNull).collect(Collectors.toList());
-    if (exceptions.size() > 0) {
-      throw new IOException(Joiner.on("\n").join(exceptions));
-    }
+  public Document addCommentToAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
+    List<DocumentContainer> output =
+            indices.parallelStream().map(dao -> {
+              try {
+                return new DocumentContainer(dao.addCommentToAlert(request, latest));
+              } catch (Throwable e) {
+                return new DocumentContainer(e);
+              }
+            }).collect(Collectors.toList());
+
+    return getLatestDocument(output);
   }
 
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request) throws IOException {
     Document latest = getLatest(request.getGuid(), request.getSensorType());
-    removeCommentFromAlert(request, latest);
+    return removeCommentFromAlert(request, latest);
   }
 
+  /**
+   * Removes comments from an alert.  Updates are written to each Dao in parallel with the assumption that all updates
+   * are identical.  The first update to be applied is returned as the current version of the alert with comments removed.
+   * @param request Request to remove comments
+   * @param latest The latest version of the alert the comments will be removed from.
+   * @return The complete alert document with comments removed.
+   * @throws IOException
+   */
   @Override
-  public void removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
-    List<String> exceptions =
-        indices.parallelStream().map(dao -> {
-          try {
-            dao.removeCommentFromAlert(request, latest);
-            return null;
-          } catch (Throwable e) {
-            return dao.getClass() + ": " + e.getMessage() + "\n" + ExceptionUtils.getStackTrace(e);
-          }
-        }).filter(Objects::nonNull).collect(Collectors.toList());
-    if (exceptions.size() > 0) {
-      throw new IOException(Joiner.on("\n").join(exceptions));
-    }
+  public Document removeCommentFromAlert(CommentAddRemoveRequest request, Document latest) throws IOException {
+    List<DocumentContainer> output =
+            indices.parallelStream().map(dao -> {
+              try {
+                return new DocumentContainer(dao.removeCommentFromAlert(request, latest));
+              } catch (Throwable e) {
+                return new DocumentContainer(e);
+              }
+            }).collect(Collectors.toList());
+
+    return getLatestDocument(output);
   }
 
-  private static class DocumentContainer {
+  protected static class DocumentContainer {
     private Optional<Document> d = Optional.empty();
     private Optional<Throwable> t = Optional.empty();
     public DocumentContainer(Document d) {
@@ -214,7 +226,6 @@ public class MultiIndexDao implements IndexDao {
 
   @Override
   public Document getLatest(final String guid, String sensorType) throws IOException {
-    Document ret = null;
     List<DocumentContainer> output =
             indices.parallelStream().map(dao -> {
       try {
@@ -224,25 +235,7 @@ public class MultiIndexDao implements IndexDao {
       }
     }).collect(Collectors.toList());
 
-    List<String> error = new ArrayList<>();
-    for(DocumentContainer dc : output) {
-      if(dc.getException().isPresent()) {
-        Throwable e = dc.getException().get();
-        error.add(e.getMessage() + "\n" + ExceptionUtils.getStackTrace(e));
-      }
-      else {
-        if(dc.getDocument().isPresent()) {
-          Document d = dc.getDocument().get();
-          if(ret == null || ret.getTimestamp() < d.getTimestamp()) {
-            ret = d;
-          }
-        }
-      }
-    }
-    if(error.size() > 0) {
-      throw new IOException(Joiner.on("\n").join(error));
-    }
-    return ret;
+    return getLatestDocument(output);
   }
 
   @Override
@@ -282,4 +275,39 @@ public class MultiIndexDao implements IndexDao {
   public List<IndexDao> getIndices() {
     return indices;
   }
+
+  /**
+   * Returns the most recent {@link Document} from a list of {@link DocumentContainer}s.
+   *
+   * @param documentContainers A list of containers; each retrieved from a separate index.
+   * @return The latest {@link Document} found.
+   * @throws IOException If any of the {@link DocumentContainer}s contain an exception.
+   */
+  private Document getLatestDocument(List<DocumentContainer> documentContainers) throws IOException {
+    Document latestDocument = null;
+    List<String> error = new ArrayList<>();
+
+    for(DocumentContainer dc : documentContainers) {
+      if(dc.getException().isPresent()) {
+        // collect each exception; multiple can occur, one in each index
+        Throwable e = dc.getException().get();
+        error.add(e.getMessage() + "\n" + ExceptionUtils.getStackTrace(e));
+
+      } else if(dc.getDocument().isPresent()) {
+        Document d = dc.getDocument().get();
+        // is this the latest document so far?
+        if(latestDocument == null || latestDocument.getTimestamp() < d.getTimestamp()) {
+          latestDocument = d;
+        }
+
+      } else {
+        // no document was found in the index
+      }
+    }
+    if(error.size() > 0) {
+      // report all of the errors encountered
+      throw new IOException(Joiner.on("\n").join(error));
+    }
+    return latestDocument;
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertCreateResponse.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertCreateResponse.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertCreateResponse.java
deleted file mode 100644
index 0bdf332..0000000
--- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertCreateResponse.java
+++ /dev/null
@@ -1,40 +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.indexing.dao.metaalert;
-
-public class MetaAlertCreateResponse {
-  private boolean created;
-  private String guid;
-
-  public boolean isCreated() {
-    return created;
-  }
-
-  public void setCreated(boolean created) {
-    this.created = created;
-  }
-
-  public String getGuid() {
-    return guid;
-  }
-
-  public void setGuid(String guid) {
-    this.guid = guid;
-  }
-}

http://git-wip-us.apache.org/repos/asf/metron/blob/de533063/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertUpdateDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertUpdateDao.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertUpdateDao.java
index f4374b4..4b22656 100644
--- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertUpdateDao.java
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertUpdateDao.java
@@ -59,31 +59,31 @@ public interface MetaAlertUpdateDao extends UpdateDao {
    * retrieved using the DAO abstractions.
    *
    * @param request A request object containing get requests for alerts to be added and a list of groups
-   * @return A response indicating success or failure along with the GUID of the new meta alert
+   * @return The complete document of the created metaalert.
    * @throws InvalidCreateException If a malformed create request is provided
    * @throws IOException If a problem occurs during communication
    */
-  MetaAlertCreateResponse createMetaAlert(MetaAlertCreateRequest request)
+  Document createMetaAlert(MetaAlertCreateRequest request)
       throws InvalidCreateException, IOException;
 
   /**
    * Adds alerts to a metaalert, based on a list of GetRequests provided for retrieval.
    * @param metaAlertGuid The GUID of the metaalert to be given new children.
    * @param alertRequests GetRequests for the appropriate alerts to add.
-   * @return True if metaalert is modified, false otherwise.
+   * @return The complete metaalert document with the alerts added.
    */
-  boolean addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
-      throws IOException;
+  Document addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
+      throws IOException, IllegalStateException;
 
   /**
    * Removes alerts from a metaalert
    * @param metaAlertGuid The metaalert guid to be affected.
    * @param alertRequests A list of GetReqests that will provide the alerts to remove
-   * @return True if there are updates, false otherwise
+   * @return The complete metaalert document with the alerts removed.
    * @throws IOException If an error is thrown during retrieal.
    */
-  boolean removeAlertsFromMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
-      throws IOException;
+  Document removeAlertsFromMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
+      throws IOException, IllegalStateException;
 
   /**
    * Removes a metaalert link from a given alert. An nonexistent link performs no change.
@@ -115,10 +115,10 @@ public interface MetaAlertUpdateDao extends UpdateDao {
    *
    * @param metaAlertGuid The GUID of the meta alert
    * @param status A status value of 'active' or 'inactive'
-   * @return True or false depending on if the status was changed
+   * @return The complete metaalert document with the updated status.
    * @throws IOException if an error occurs during the update.
    */
-  boolean updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status)
+  Document updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status)
       throws IOException;
 
   /**