You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metron.apache.org by le...@apache.org on 2018/05/23 14:34:32 UTC

[5/7] metron git commit: METRON-1421 Create a SolrMetaAlertDao (justinleet) closes apache/metron#970

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/IndexDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/IndexDao.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/IndexDao.java
index fe546bd..4187428 100644
--- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/IndexDao.java
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/IndexDao.java
@@ -17,151 +17,18 @@
  */
 package org.apache.metron.indexing.dao;
 
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-import org.apache.metron.common.utils.JSONUtils;
-import org.apache.metron.indexing.dao.search.FieldType;
-import org.apache.metron.indexing.dao.search.GetRequest;
-import org.apache.metron.indexing.dao.search.GroupRequest;
-import org.apache.metron.indexing.dao.search.GroupResponse;
-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.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.apache.metron.indexing.dao.search.SearchDao;
+import org.apache.metron.indexing.dao.update.UpdateDao;
 
 /**
  * The IndexDao provides a common interface for retrieving and storing data in a variety of persistent stores.
  * Document reads and writes require a GUID and sensor type with an index being optional.
  */
-public interface IndexDao {
-
-  /**
-   * Return search response based on the search request
-   *
-   * @param searchRequest
-   * @return
-   * @throws InvalidSearchException
-   */
-  SearchResponse search(SearchRequest searchRequest) throws InvalidSearchException;
-
-  GroupResponse group(GroupRequest groupRequest) throws InvalidSearchException;
+public interface IndexDao extends UpdateDao, SearchDao, RetrieveLatestDao, ColumnMetadataDao {
 
   /**
    * Initialize the DAO with the AccessConfig object.
-   * @param config
+   * @param config The config to use for initialization
    */
   void init(AccessConfig config);
-
-  /**
-   * Return the latest version of a document given the GUID and the sensor type.
-   *
-   * @param guid The GUID for the document
-   * @param sensorType The sensor type of the document
-   * @return The Document matching or null if not available.
-   * @throws IOException
-   */
-  Document getLatest(String guid, String sensorType) throws IOException;
-
-  /**
-   * Return a list of the latest versions of documents given a list of GUIDs and sensor types.
-   *
-   * @param getRequests A list of get requests for documents
-   * @return A list of documents matching or an empty list in not available.
-   * @throws IOException
-   */
-  Iterable<Document> getAllLatest(List<GetRequest> getRequests) throws IOException;
-
-  /**
-   * Return the latest version of a document given a GetRequest.
-   * @param request The GetRequest which indicates the GUID and sensor type.
-   * @return Optionally the document (dependent upon existence in the index).
-   * @throws IOException
-   */
-  default Optional<Map<String, Object>> getLatestResult(GetRequest request) throws IOException {
-    Document ret = getLatest(request.getGuid(), request.getSensorType());
-    if(ret == null) {
-      return Optional.empty();
-    }
-    else {
-      return Optional.ofNullable(ret.getDocument());
-    }
-  }
-
-  /**
-   * Update a given Document and optionally the index where the document exists.  This is a full update,
-   * meaning the current document will be replaced if it exists or a new document will be created if it does
-   * not exist.  Partial updates are not supported in this method.
-   *
-   * @param update The document to replace from the index.
-   * @param index The index where the document lives.
-   * @throws IOException
-   */
-  void 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.
-   * @throws IOException
-   */
-  void batchUpdate(Map<Document, Optional<String>> updates) throws IOException;
-
-  /**
-   * Update a document in an index given a JSON Patch (see RFC 6902 at 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.
-   * @throws OriginalNotFoundException If the original is not found, then it cannot be patched.
-   * @throws IOException
-   */
-  default void patch( PatchRequest request
-                    , Optional<Long> timestamp
-                    ) throws OriginalNotFoundException, IOException {
-    Document d = getPatchedDocument(request, timestamp);
-    update(d, Optional.ofNullable(request.getIndex()));
-  }
-
-  default Document getPatchedDocument(PatchRequest request
-      , Optional<Long> timestamp
-  ) throws OriginalNotFoundException, IOException {
-    Map<String, Object> latest = request.getSource();
-    if(latest == null) {
-      Document latestDoc = getLatest(request.getGuid(), request.getSensorType());
-      if(latestDoc != null && latestDoc.getDocument() != null) {
-        latest = latestDoc.getDocument();
-      }
-      else {
-        throw new OriginalNotFoundException("Unable to patch an document that doesn't exist and isn't specified.");
-      }
-    }
-    Map<String, Object> updated = JSONUtils.INSTANCE.applyPatch(request.getPatch(), latest);
-    return new Document(updated
-            , request.getGuid()
-            , request.getSensorType()
-            , timestamp.orElse(System.currentTimeMillis()));
-  }
-
-  /**
-   * 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.
-   * @throws IOException
-   */
-  default void 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()));
-  }
-
-  Map<String, FieldType> getColumnMetadata(List<String> indices) throws IOException;
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/MetaAlertDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/MetaAlertDao.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/MetaAlertDao.java
deleted file mode 100644
index 4530d2a..0000000
--- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/MetaAlertDao.java
+++ /dev/null
@@ -1,154 +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;
-
-import java.util.List;
-import java.util.Optional;
-import java.io.IOException;
-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.GetRequest;
-import org.apache.metron.indexing.dao.search.InvalidCreateException;
-import org.apache.metron.indexing.dao.search.InvalidSearchException;
-import org.apache.metron.indexing.dao.search.SearchResponse;
-
-/**
- * The MetaAlertDao exposes methods for interacting with meta alerts.  Meta alerts are objects that contain
- * alerts and summary statistics based on the scores of these alerts.  Meta alerts are returned in searches
- * just as alerts are and match based on the field values of child alerts.  If a child alert matches a search
- * the meta alert will be returned while the original child alert will not.  A meta alert also contains a
- * status field that controls it's inclusion in search results and a groups field that can be used to track
- * the groups a meta alert was created from.
- *
- * The structure of a meta alert is as follows:
- * {
- *   "guid": "meta alert guid",
- *   "timestamp": timestamp,
- *   "source:type": "metaalert",
- *   "alerts": [ array of child alerts ],
- *   "status": "active or inactive",
- *   "groups": [ array of group names ],
- *   "average": 10,
- *   "max": 10,
- *   "threat:triage:score": 30,
- *   "count": 3,
- *   "sum": 30,
- *   "min": 10,
- *   "median": 10
- * }
- *
- * A child alert that has been added to a meta alert will store the meta alert GUID in a "metaalerts" field.
- * This field is an array of meta alert GUIDs, meaning a child alert can be contained in multiple meta alerts.
- * Any update to a child alert will trigger an update to the meta alert so that the alert inside a meta alert
- * and the original alert will be kept in sync.
- *
- * Other fields can be added to a meta alert through the patch method on the IndexDao interface.  However, attempts
- * to directly change the "alerts" or "status" field will result in an exception.
- */
-public interface MetaAlertDao extends IndexDao {
-
-  String METAALERTS_INDEX = "metaalert_index";
-  String METAALERT_TYPE = "metaalert";
-  String METAALERT_FIELD = "metaalerts";
-  String METAALERT_DOC = METAALERT_TYPE + "_doc";
-  String THREAT_FIELD_DEFAULT = "threat:triage:score";
-  String THREAT_SORT_DEFAULT = "sum";
-  String ALERT_FIELD = "alert";
-  String STATUS_FIELD = "status";
-  String GROUPS_FIELD = "groups";
-
-  /**
-   * Given an alert GUID, retrieve all associated meta alerts.
-   * @param guid The alert GUID to be searched for
-   * @return All meta alerts with a child alert having the GUID
-   * @throws InvalidSearchException If a problem occurs with the search
-   */
-  SearchResponse getAllMetaAlertsForAlert(String guid) throws InvalidSearchException;
-
-  /**
-   * Creates a meta alert from a list of child alerts.  The most recent version of each child alert is
-   * 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
-   * @throws InvalidCreateException If a malformed create request is provided
-   * @throws IOException If a problem occurs during communication
-   */
-  MetaAlertCreateResponse createMetaAlert(MetaAlertCreateRequest request)
-      throws InvalidCreateException, IOException;
-
-
-  /**
-   * Adds a list of alerts to an existing meta alert.  This will add each alert object to the "alerts" array in the meta alert
-   * and also add the meta alert GUID to each child alert's "metaalerts" array.  After alerts have been added the
-   * meta alert scores are recalculated.  Any alerts already in the meta alert are skipped and no updates are
-   * performed if all of the alerts are already in the meta alert.  The most recent version of each child alert is
-   * retrieved using the DAO abstractions.  Alerts cannot be added to an 'inactive' meta alert.
-   *
-   * @param metaAlertGuid The meta alert GUID
-   * @param getRequests Get requests for alerts to be added
-   * @return True or false depending on if any alerts were added
-   * @throws IOException
-   */
-  boolean addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> getRequests) throws IOException;
-
-  /**
-   * Removes a list of alerts from an existing meta alert.  This will remove each alert object from the "alerts" array in the meta alert
-   * and also remove the meta alert GUID from each child alert's "metaalerts" array.  After alerts have been removed the
-   * meta alert scores are recalculated.  Any alerts not contained in the meta alert are skipped and no updates are
-   * performed if no alerts can be found in the meta alert.  Alerts cannot be removed from an 'inactive' meta alert.
-   *
-   * @param metaAlertGuid The meta alert GUID
-   * @param getRequests Get requests for alerts to be removed
-   * @return True or false depending on if any alerts were removed
-   * @throws IOException
-   */
-  boolean removeAlertsFromMetaAlert(String metaAlertGuid, List<GetRequest> getRequests) throws IOException;
-
-  /**
-   * The meta alert status field can be set to either 'active' or 'inactive' and will control whether or not meta alerts
-   * (and child alerts) appear in search results.  An 'active' status will cause meta alerts to appear in search
-   * results instead of it's child alerts and an 'inactive' status will suppress the meta alert from search results
-   * with child alerts appearing in search results as normal.  A change to 'inactive' will cause the meta alert GUID to
-   * be removed from all it's child alert's "metaalerts" field.  A change back to 'active' will have the opposite effect.
-   *
-   * @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
-   * @throws IOException
-   */
-  boolean updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status) throws IOException;
-
-  /**
-   * Initializes a Meta Alert DAO with default "sum" meta alert threat sorting.
-   * @param indexDao The DAO to wrap for our queries.
-   */
-  default void init(IndexDao indexDao) {
-    init(indexDao, Optional.empty());
-  }
-
-  /**
-   * Initializes a Meta Alert DAO.
-   * @param indexDao The DAO to wrap for our queries
-   * @param threatSort The aggregation to use as the threat field. E.g. "sum", "median", etc.
-   *     null is "sum"
-   */
-  void init(IndexDao indexDao, Optional<String> threatSort);
-}

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/RetrieveLatestDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/RetrieveLatestDao.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/RetrieveLatestDao.java
new file mode 100644
index 0000000..caf754c
--- /dev/null
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/RetrieveLatestDao.java
@@ -0,0 +1,67 @@
+/*
+ * 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 java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.metron.indexing.dao.search.GetRequest;
+import org.apache.metron.indexing.dao.update.Document;
+
+/**
+ * An base interface for other DAOs to extend.  All DAOs are expected to be able to retrieve
+ * Documents they've stored.
+ */
+public interface RetrieveLatestDao {
+
+  /**
+   * Return the latest version of a document given the GUID and the sensor type.
+   *
+   * @param guid The GUID for the document
+   * @param sensorType The sensor type of the document
+   * @return The Document matching or null if not available.
+   * @throws IOException If an error occurs retrieving the latest document.
+   */
+  Document getLatest(String guid, String sensorType) throws IOException;
+
+  /**
+   * Return a list of the latest versions of documents given a list of GUIDs and sensor types.
+   *
+   * @param getRequests A list of get requests for documents
+   * @return A list of documents matching or an empty list in not available.
+   * @throws IOException If an error occurs retrieving the latest documents.
+   */
+  Iterable<Document> getAllLatest(List<GetRequest> getRequests) throws IOException;
+
+  /**
+   * Return the latest version of a document given a GetRequest.
+   * @param request The GetRequest which indicates the GUID and sensor type.
+   * @return Optionally the document (dependent upon existence in the index).
+   * @throws IOException If an error occurs while retrieving the document.
+   */
+  default Optional<Map<String, Object>> getLatestResult(GetRequest request) throws IOException {
+    Document ret = getLatest(request.getGuid(), request.getSensorType());
+    if (ret == null) {
+      return Optional.empty();
+    } else {
+      return Optional.ofNullable(ret.getDocument());
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/DeferredMetaAlertIndexDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/DeferredMetaAlertIndexDao.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/DeferredMetaAlertIndexDao.java
new file mode 100644
index 0000000..1e5e723
--- /dev/null
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/DeferredMetaAlertIndexDao.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+import org.apache.metron.indexing.dao.IndexDao;
+
+/**
+ * Interface for a DAO that is allowed to defer to a child Index DAO in order to perform tasks.
+ * An example is metaalerts deferring to a base DAO.
+ */
+public interface DeferredMetaAlertIndexDao {
+
+  IndexDao getIndexDao();
+
+  String getMetAlertSensorName();
+
+  String getMetaAlertIndex();
+
+  default String getThreatTriageField() {
+    return MetaAlertConstants.THREAT_FIELD_DEFAULT;
+  }
+
+  default String getThreatSort() {
+    return MetaAlertConstants.THREAT_SORT_DEFAULT;
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertAddRemoveRequest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertAddRemoveRequest.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertAddRemoveRequest.java
index 6183d37..a14749b 100644
--- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertAddRemoveRequest.java
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertAddRemoveRequest.java
@@ -14,7 +14,6 @@
  */
 package org.apache.metron.indexing.dao.metaalert;
 
-import java.util.Collection;
 import java.util.List;
 import org.apache.metron.indexing.dao.search.GetRequest;
 

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertConfig.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertConfig.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertConfig.java
new file mode 100644
index 0000000..9254425
--- /dev/null
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertConfig.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.metaalert;
+
+public class MetaAlertConfig {
+  private String metaAlertIndex;
+  private String threatTriageField;
+  private String threatSort;
+  private String sourceTypeField;
+
+  /**
+   * Simple object for storing and retrieving configs, primarily to make passing all the info to
+   * the sub DAOs easier.
+   * @param metaAlertIndex The metaalert index or collection we're using
+   * @param threatTriageField The threat triage field's name
+   * @param threatSort The sorting operation on the threat triage field
+   * @param sourceTypeField The source type field
+   */
+  public MetaAlertConfig(String metaAlertIndex, String threatTriageField,
+      String threatSort, String sourceTypeField) {
+    this.metaAlertIndex = metaAlertIndex;
+    this.threatTriageField = threatTriageField;
+    this.threatSort = threatSort;
+    this.sourceTypeField = sourceTypeField;
+  }
+
+  public String getMetaAlertIndex() {
+    return metaAlertIndex;
+  }
+
+  public void setMetaAlertIndex(String metaAlertIndex) {
+    this.metaAlertIndex = metaAlertIndex;
+  }
+
+  public String getThreatTriageField() {
+    return threatTriageField;
+  }
+
+  public void setThreatTriageField(String threatTriageField) {
+    this.threatTriageField = threatTriageField;
+  }
+
+  public String getThreatSort() {
+    return threatSort;
+  }
+
+  public void setThreatSort(String threatSort) {
+    this.threatSort = threatSort;
+  }
+
+  public String getSourceTypeField() {
+    return sourceTypeField;
+  }
+
+  public void setSourceTypeField(String sourceTypeField) {
+    this.sourceTypeField = sourceTypeField;
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertConstants.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertConstants.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertConstants.java
new file mode 100644
index 0000000..a055db5
--- /dev/null
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertConstants.java
@@ -0,0 +1,30 @@
+/*
+ * 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 MetaAlertConstants {
+  public static String METAALERT_TYPE = "metaalert";
+  public static String METAALERT_FIELD = "metaalerts";
+  public static String METAALERT_DOC = METAALERT_TYPE + "_doc";
+  public static String THREAT_FIELD_DEFAULT = "threat:triage:score";
+  public static String THREAT_SORT_DEFAULT = "sum";
+  public static String ALERT_FIELD = "alert";
+  public static String STATUS_FIELD = "status";
+  public static String GROUPS_FIELD = "groups";
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertDao.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertDao.java
new file mode 100644
index 0000000..c9e6711
--- /dev/null
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertDao.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+import java.util.Optional;
+import org.apache.metron.indexing.dao.IndexDao;
+
+/**
+ * The MetaAlertDao exposes methods for interacting with meta alerts.  Meta alerts are objects that contain
+ * alerts and summary statistics based on the scores of these alerts.  Meta alerts are returned in searches
+ * just as alerts are and match based on the field values of child alerts.  If a child alert matches a search
+ * the meta alert will be returned while the original child alert will not.  A meta alert also contains a
+ * status field that controls it's inclusion in search results and a groups field that can be used to track
+ * the groups a meta alert was created from.
+ *
+ * </p>
+ * The structure of a meta alert is as follows:
+ * {
+ *   "guid": "meta alert guid",
+ *   "timestamp": timestamp,
+ *   "source:type": "metaalert",
+ *   "alerts": [ array of child alerts ],
+ *   "status": "active or inactive",
+ *   "groups": [ array of group names ],
+ *   "average": 10,
+ *   "max": 10,
+ *   "threat:triage:score": 30,
+ *   "count": 3,
+ *   "sum": 30,
+ *   "min": 10,
+ *   "median": 10
+ * }
+ *
+ * </p>
+ * A child alert that has been added to a meta alert will store the meta alert GUID in a "metaalerts" field.
+ * This field is an array of meta alert GUIDs, meaning a child alert can be contained in multiple meta alerts.
+ * Any update to a child alert will trigger an update to the meta alert so that the alert inside a meta alert
+ * and the original alert will be kept in sync.
+ *
+ * </p>
+ * Other fields can be added to a meta alert through the patch method on the IndexDao interface.  However, attempts
+ * to directly change the "alerts" or "status" field will result in an exception.
+ */
+public interface MetaAlertDao extends MetaAlertSearchDao, MetaAlertUpdateDao, IndexDao {
+
+  /**
+   * Initializes a Meta Alert DAO with default "sum" meta alert threat sorting.
+   * @param indexDao The DAO to wrap for our queries.
+   */
+  default void init(IndexDao indexDao) {
+    init(indexDao, Optional.empty());
+  }
+
+  /**
+   * Initializes a Meta Alert DAO.
+   * @param indexDao The DAO to wrap for our queries
+   * @param threatSort The aggregation to use as the threat field. E.g. "sum", "median", etc.
+   *     null is "sum"
+   */
+  void init(IndexDao indexDao, Optional<String> threatSort);
+}

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

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertSearchDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertSearchDao.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertSearchDao.java
new file mode 100644
index 0000000..e8b9f26
--- /dev/null
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertSearchDao.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import org.apache.metron.indexing.dao.search.InvalidSearchException;
+import org.apache.metron.indexing.dao.search.SearchDao;
+import org.apache.metron.indexing.dao.search.SearchResponse;
+
+public interface MetaAlertSearchDao extends SearchDao {
+
+  /**
+   * Given an alert GUID, retrieve all associated meta alerts.
+   * @param guid The alert GUID to be searched for
+   * @return All meta alerts with a child alert having the GUID
+   * @throws InvalidSearchException If a problem occurs with the search
+   */
+  SearchResponse getAllMetaAlertsForAlert(String guid) throws InvalidSearchException;
+
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/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
new file mode 100644
index 0000000..f4374b4
--- /dev/null
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaAlertUpdateDao.java
@@ -0,0 +1,146 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.apache.metron.indexing.dao.search.GetRequest;
+import org.apache.metron.indexing.dao.search.InvalidCreateException;
+import org.apache.metron.indexing.dao.update.Document;
+import org.apache.metron.indexing.dao.update.PatchRequest;
+import org.apache.metron.indexing.dao.update.UpdateDao;
+
+public interface MetaAlertUpdateDao extends UpdateDao {
+
+  String STATUS_PATH = "/" + MetaAlertConstants.STATUS_FIELD;
+  String ALERT_PATH = "/" + MetaAlertConstants.ALERT_FIELD;
+
+  /**
+   * Determines if a given patch request is allowed or not. By default patching the 'alert' or
+   * 'status' fields are not allowed, because they should be updated via the specific methods.
+   * @param request The patch request to examine
+   * @return True if patch can be performed, false otherwise
+   */
+  default boolean isPatchAllowed(PatchRequest request) {
+    if (request.getPatch() != null && !request.getPatch().isEmpty()) {
+      for (Map<String, Object> patch : request.getPatch()) {
+        Object pathObj = patch.get("path");
+        if (pathObj != null && pathObj instanceof String) {
+          String path = (String) pathObj;
+          if (STATUS_PATH.equals(path) || ALERT_PATH.equals(path)) {
+            return false;
+          }
+        }
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Creates a meta alert from a list of child alerts.  The most recent version of each child alert is
+   * 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
+   * @throws InvalidCreateException If a malformed create request is provided
+   * @throws IOException If a problem occurs during communication
+   */
+  MetaAlertCreateResponse 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.
+   */
+  boolean addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
+      throws IOException;
+
+  /**
+   * 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
+   * @throws IOException If an error is thrown during retrieal.
+   */
+  boolean removeAlertsFromMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
+      throws IOException;
+
+  /**
+   * Removes a metaalert link from a given alert. An nonexistent link performs no change.
+   * @param metaAlertGuid The metaalert GUID to link.
+   * @param alert The alert to be linked to.
+   * @return True if the alert changed, false otherwise.
+   */
+  default boolean removeMetaAlertFromAlert(String metaAlertGuid, Document alert) {
+    List<String> metaAlertField = new ArrayList<>();
+    @SuppressWarnings("unchecked")
+    List<String> alertField = (List<String>) alert.getDocument()
+        .get(MetaAlertConstants.METAALERT_FIELD);
+    if (alertField != null) {
+      metaAlertField.addAll(alertField);
+    }
+    boolean metaAlertRemoved = metaAlertField.remove(metaAlertGuid);
+    if (metaAlertRemoved) {
+      alert.getDocument().put(MetaAlertConstants.METAALERT_FIELD, metaAlertField);
+    }
+    return metaAlertRemoved;
+  }
+
+  /**
+   * The meta alert status field can be set to either 'active' or 'inactive' and will control whether or not meta alerts
+   * (and child alerts) appear in search results.  An 'active' status will cause meta alerts to appear in search
+   * results instead of it's child alerts and an 'inactive' status will suppress the meta alert from search results
+   * with child alerts appearing in search results as normal.  A change to 'inactive' will cause the meta alert GUID to
+   * be removed from all it's child alert's "metaalerts" field.  A change back to 'active' will have the opposite effect.
+   *
+   * @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
+   * @throws IOException if an error occurs during the update.
+   */
+  boolean updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status)
+      throws IOException;
+
+  /**
+   * Adds a metaalert link to a provided alert Document.  Adding an existing link does no change.
+   * @param metaAlertGuid The GUID to be added.
+   * @param alert The alert we're adding the link to.
+   * @return True if the alert is modified, false if not.
+   */
+  default boolean addMetaAlertToAlert(String metaAlertGuid, Document alert) {
+    List<String> metaAlertField = new ArrayList<>();
+    @SuppressWarnings("unchecked")
+    List<String> alertField = (List<String>) alert.getDocument()
+        .get(MetaAlertConstants.METAALERT_FIELD);
+    if (alertField != null) {
+      metaAlertField.addAll(alertField);
+    }
+
+    boolean metaAlertAdded = !metaAlertField.contains(metaAlertGuid);
+    if (metaAlertAdded) {
+      metaAlertField.add(metaAlertGuid);
+      alert.getDocument().put(MetaAlertConstants.METAALERT_FIELD, metaAlertField);
+    }
+    return metaAlertAdded;
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaScores.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaScores.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaScores.java
index 07285d6..55b1aa0 100644
--- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaScores.java
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/MetaScores.java
@@ -18,12 +18,14 @@
 
 package org.apache.metron.indexing.dao.metaalert;
 
-import org.apache.commons.math3.stat.descriptive.rank.Median;
-
+import java.util.ArrayList;
 import java.util.DoubleSummaryStatistics;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import org.apache.commons.math3.stat.descriptive.rank.Median;
+import org.apache.metron.indexing.dao.update.Document;
+import org.apache.metron.stellar.common.utils.ConversionUtils;
 
 public class MetaScores {
 
@@ -52,4 +54,50 @@ public class MetaScores {
   public Map<String, Object> getMetaScores() {
     return metaScores;
   }
+
+  /**
+   * Calculate the meta alert scores for a Document. The scores are placed directly in the provided
+   * document.
+   * @param metaAlert The Document containing scores
+   */
+  @SuppressWarnings("unchecked")
+  public static void calculateMetaScores(Document metaAlert, String threatTriageField,
+      String threatSort) {
+    MetaScores metaScores = new MetaScores(new ArrayList<>());
+    List<Object> alertsRaw = ((List<Object>) metaAlert.getDocument()
+        .get(MetaAlertConstants.ALERT_FIELD));
+    if (alertsRaw != null && !alertsRaw.isEmpty()) {
+      ArrayList<Double> scores = new ArrayList<>();
+      for (Object alertRaw : alertsRaw) {
+        Map<String, Object> alert = (Map<String, Object>) alertRaw;
+        Double scoreNum = parseThreatField(alert.get(threatTriageField));
+        if (scoreNum != null) {
+          scores.add(scoreNum);
+        }
+      }
+      metaScores = new MetaScores(scores);
+    }
+
+    // add a summary (max, min, avg, ...) of all the threat scores from the child alerts
+    metaAlert.getDocument().putAll(metaScores.getMetaScores());
+
+    // add the overall threat score for the metaalert; one of the summary aggregations as defined
+    // by `threatSort`
+    Object threatScore = metaScores.getMetaScores().get(threatSort);
+
+    // add the threat score as a float; type needs to match the threat score field from each of
+    // the sensor indices
+    metaAlert.getDocument()
+        .put(threatTriageField, ConversionUtils.convert(threatScore, Float.class));
+  }
+
+  protected static Double parseThreatField(Object threatRaw) {
+    Double threat = null;
+    if (threatRaw instanceof Number) {
+      threat = ((Number) threatRaw).doubleValue();
+    } else if (threatRaw instanceof String) {
+      threat = Double.parseDouble((String) threatRaw);
+    }
+    return threat;
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/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
new file mode 100644
index 0000000..b47d648
--- /dev/null
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/metaalert/lucene/AbstractLuceneMetaAlertUpdateDao.java
@@ -0,0 +1,334 @@
+/*
+ * 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.lucene;
+
+import static org.apache.metron.common.Constants.GUID;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+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.stream.Collectors;
+import org.apache.metron.common.Constants;
+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.MetaAlertRetrieveLatestDao;
+import org.apache.metron.indexing.dao.metaalert.MetaAlertStatus;
+import org.apache.metron.indexing.dao.metaalert.MetaAlertUpdateDao;
+import org.apache.metron.indexing.dao.metaalert.MetaScores;
+import org.apache.metron.indexing.dao.search.GetRequest;
+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;
+
+public abstract class AbstractLuceneMetaAlertUpdateDao implements MetaAlertUpdateDao {
+
+  private UpdateDao updateDao;
+  private MetaAlertRetrieveLatestDao retrieveLatestDao;
+  private MetaAlertConfig config;
+
+  protected AbstractLuceneMetaAlertUpdateDao(
+      UpdateDao updateDao,
+      MetaAlertRetrieveLatestDao retrieveLatestDao,
+      MetaAlertConfig config) {
+    this.updateDao = updateDao;
+    this.retrieveLatestDao = retrieveLatestDao;
+    this.config = config;
+  }
+
+  public UpdateDao getUpdateDao() {
+    return updateDao;
+  }
+
+  public MetaAlertRetrieveLatestDao getRetrieveLatestDao() {
+    return retrieveLatestDao;
+  }
+
+  public MetaAlertConfig getConfig() {
+    return config;
+  }
+
+  /**
+   * Performs a patch operation on a document based on the result of @{link #isPatchAllowed(PatchRequest)}
+   *
+   * @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.
+   * @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,
+      Optional<Long> timestamp)
+      throws OriginalNotFoundException, IOException {
+    if (isPatchAllowed(request)) {
+      updateDao.patch(retrieveLatestDao, request, timestamp);
+    } else {
+      throw new IllegalArgumentException(
+          "Meta alert patches are not allowed for /alert or /status paths.  "
+              + "Please use the add/remove alert or update status functions instead.");
+    }
+  }
+
+  @Override
+  public void batchUpdate(Map<Document, Optional<String>> updates) {
+    throw new UnsupportedOperationException("Meta alerts do not allow for bulk updates");
+  }
+
+  /**
+   * Build the Document representing a meta alert to be created.
+   * @param alerts The Elasticsearch results for the meta alerts child documents
+   * @param groups The groups used to create this meta alert
+   * @return A Document representing the new meta alert
+   */
+  protected Document buildCreateDocument(Iterable<Document> alerts, List<String> groups,
+      String alertField) {
+    // Need to create a Document from the multiget. Scores will be calculated later
+    Map<String, Object> metaSource = new HashMap<>();
+    List<Map<String, Object>> alertList = new ArrayList<>();
+    for (Document alert : alerts) {
+      alertList.add(alert.getDocument());
+    }
+    metaSource.put(alertField, alertList);
+
+    // Add any meta fields
+    String guid = UUID.randomUUID().toString();
+    metaSource.put(GUID, guid);
+    metaSource.put(Constants.Fields.TIMESTAMP.getName(), System.currentTimeMillis());
+    metaSource.put(MetaAlertConstants.GROUPS_FIELD, groups);
+    metaSource.put(MetaAlertConstants.STATUS_FIELD, MetaAlertStatus.ACTIVE.getStatusString());
+
+    return new Document(metaSource, guid, MetaAlertConstants.METAALERT_TYPE,
+        System.currentTimeMillis());
+  }
+
+  /**
+   * Builds the set of updates when alerts are removed from a meta alert
+   * @param metaAlert The meta alert to remove alerts from
+   * @param alerts The alert Documents to be removed
+   * @return The updates to be run
+   * @throws IOException If an error is thrown.
+   */
+  @SuppressWarnings("unchecked")
+  protected Map<Document, Optional<String>> buildRemoveAlertsFromMetaAlert(Document metaAlert,
+      Iterable<Document> alerts)
+      throws IOException {
+    Map<Document, Optional<String>> updates = new HashMap<>();
+
+    List<String> alertGuids = new ArrayList<>();
+    for (Document alert : alerts) {
+      alertGuids.add(alert.getGuid());
+    }
+    List<Map<String, Object>> alertsBefore = new ArrayList<>();
+    Map<String, Object> documentBefore = metaAlert.getDocument();
+    if (documentBefore.containsKey(MetaAlertConstants.ALERT_FIELD)) {
+      alertsBefore
+          .addAll((List<Map<String, Object>>) documentBefore.get(MetaAlertConstants.ALERT_FIELD));
+    }
+    boolean metaAlertUpdated = removeAlertsFromMetaAlert(metaAlert, alertGuids);
+    if (metaAlertUpdated) {
+      List<Map<String, Object>> alertsAfter = (List<Map<String, Object>>) metaAlert.getDocument()
+          .get(MetaAlertConstants.ALERT_FIELD);
+      if (alertsAfter.size() < alertsBefore.size() && alertsAfter.size() == 0) {
+        throw new IllegalStateException("Removing these alerts will result in an empty meta alert.  Empty meta alerts are not allowed.");
+      }
+      MetaScores
+          .calculateMetaScores(metaAlert, config.getThreatTriageField(), config.getThreatSort());
+      updates.put(metaAlert, Optional.of(config.getMetaAlertIndex()));
+      for (Document alert : alerts) {
+        if (removeMetaAlertFromAlert(metaAlert.getGuid(), alert)) {
+          updates.put(alert, Optional.empty());
+        }
+      }
+    }
+    return updates;
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public boolean removeAlertsFromMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests)
+      throws IOException {
+    Document metaAlert = retrieveLatestDao
+        .getLatest(metaAlertGuid, MetaAlertConstants.METAALERT_TYPE);
+    if (metaAlert == null) {
+      return false;
+    }
+    if (MetaAlertStatus.ACTIVE.getStatusString()
+        .equals(metaAlert.getDocument().get(MetaAlertConstants.STATUS_FIELD))) {
+      Iterable<Document> alerts = retrieveLatestDao.getAllLatest(alertRequests);
+      Map<Document, Optional<String>> updates = buildRemoveAlertsFromMetaAlert(metaAlert, alerts);
+      update(updates);
+      return updates.size() != 0;
+    } else {
+      throw new IllegalStateException("Removing alerts from an INACTIVE meta alert is not allowed");
+    }
+  }
+
+  /**
+   * Removes a given set of alerts from a given alert. AlertGuids that are not found are ignored.
+   * @param metaAlert The metaalert to be mutated.
+   * @param alertGuids The alerts to remove from the metaaelrt.
+   * @return True if the metaAlert changed, false otherwise.
+   */
+  protected boolean removeAlertsFromMetaAlert(Document metaAlert, Collection<String> alertGuids) {
+    // If we don't have child alerts or nothing is being removed, immediately return false.
+    if (!metaAlert.getDocument().containsKey(MetaAlertConstants.ALERT_FIELD)
+        || alertGuids.size() == 0) {
+      return false;
+    }
+
+    @SuppressWarnings("unchecked")
+    List<Map<String, Object>> currentAlerts = (List<Map<String, Object>>) metaAlert.getDocument()
+        .get(MetaAlertConstants.ALERT_FIELD);
+    int previousSize = currentAlerts.size();
+    // Only remove an alert if it is in the meta alert
+    currentAlerts.removeIf(currentAlert -> alertGuids.contains(currentAlert.get(GUID)));
+    return currentAlerts.size() != previousSize;
+  }
+
+  @Override
+  public boolean updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status)
+      throws IOException {
+    Document metaAlert = retrieveLatestDao
+        .getLatest(metaAlertGuid, MetaAlertConstants.METAALERT_TYPE);
+    String currentStatus = (String) metaAlert.getDocument().get(MetaAlertConstants.STATUS_FIELD);
+    boolean metaAlertUpdated = !status.getStatusString().equals(currentStatus);
+    if (metaAlertUpdated) {
+      List<GetRequest> getRequests = new ArrayList<>();
+      @SuppressWarnings("unchecked")
+      List<Map<String, Object>> currentAlerts = (List<Map<String, Object>>) metaAlert.getDocument()
+          .get(MetaAlertConstants.ALERT_FIELD);
+      currentAlerts.stream()
+          .forEach(currentAlert -> getRequests.add(new GetRequest((String) currentAlert.get(GUID),
+              (String) currentAlert.get(config.getSourceTypeField()))));
+      Iterable<Document> alerts = retrieveLatestDao.getAllLatest(getRequests);
+      Map<Document, Optional<String>> updates = buildStatusChangeUpdates(metaAlert, alerts, status);
+      update(updates);
+    }
+    return metaAlertUpdated;
+  }
+
+  /**
+   * Given a Metaalert and a status change, builds the set of updates to be run.
+   * @param metaAlert The metaalert to have status changed
+   * @param alerts The alerts to change status for
+   * @param status The status to change to
+   * @return The updates to be run
+   */
+  protected Map<Document, Optional<String>> buildStatusChangeUpdates(Document metaAlert,
+      Iterable<Document> alerts,
+      MetaAlertStatus status) {
+    metaAlert.getDocument().put(MetaAlertConstants.STATUS_FIELD, status.getStatusString());
+
+    Map<Document, Optional<String>> updates = new HashMap<>();
+    updates.put(metaAlert, Optional.of(config.getMetaAlertIndex()));
+
+    for (Document alert : alerts) {
+      boolean metaAlertAdded = false;
+      boolean metaAlertRemoved = false;
+      // If we're making it active add add the meta alert guid for every alert.
+      if (MetaAlertStatus.ACTIVE.equals(status)) {
+        metaAlertAdded = addMetaAlertToAlert(metaAlert.getGuid(), alert);
+      }
+      // If we're making it inactive, remove the meta alert guid from every alert.
+      if (MetaAlertStatus.INACTIVE.equals(status)) {
+        metaAlertRemoved = removeMetaAlertFromAlert(metaAlert.getGuid(), alert);
+      }
+      if (metaAlertAdded || metaAlertRemoved) {
+        updates.put(alert, Optional.empty());
+      }
+    }
+    return updates;
+  }
+
+  /**
+   * Builds the updates to be run based on a given metaalert and a set of new alerts for the it.
+   * @param metaAlert The base metaalert we're building updates for
+   * @param alerts The alerts being added
+   * @return The set of resulting updates.
+   */
+  protected Map<Document, Optional<String>> buildAddAlertToMetaAlertUpdates(Document metaAlert,
+      Iterable<Document> alerts) {
+    Map<Document, Optional<String>> updates = new HashMap<>();
+    boolean metaAlertUpdated = addAlertsToMetaAlert(metaAlert, alerts);
+    if (metaAlertUpdated) {
+      MetaScores
+          .calculateMetaScores(metaAlert, config.getThreatTriageField(), config.getThreatSort());
+      updates.put(metaAlert, Optional.of(config.getMetaAlertIndex()));
+      for (Document alert : alerts) {
+        if (addMetaAlertToAlert(metaAlert.getGuid(), alert)) {
+          updates.put(alert, Optional.empty());
+        }
+      }
+    }
+    return updates;
+  }
+
+  /**
+   * Adds the provided alerts to a given metaalert.
+   * @param metaAlert The metaalert to be given new children.
+   * @param alerts The alerts to be added as children
+   * @return True if metaalert is modified, false otherwise.
+   */
+  protected boolean addAlertsToMetaAlert(Document metaAlert, Iterable<Document> alerts) {
+    boolean alertAdded = false;
+    @SuppressWarnings("unchecked")
+    List<Map<String, Object>> currentAlerts = (List<Map<String, Object>>) metaAlert.getDocument()
+        .get(MetaAlertConstants.ALERT_FIELD);
+    if (currentAlerts == null) {
+      currentAlerts = new ArrayList<>();
+      metaAlert.getDocument().put(MetaAlertConstants.ALERT_FIELD, currentAlerts);
+    }
+    Set<String> currentAlertGuids = currentAlerts.stream().map(currentAlert ->
+        (String) currentAlert.get(GUID)).collect(Collectors.toSet());
+    for (Document alert : alerts) {
+      String alertGuid = alert.getGuid();
+      // Only add an alert if it isn't already in the meta alert
+      if (!currentAlertGuids.contains(alertGuid)) {
+        currentAlerts.add(alert.getDocument());
+        alertAdded = true;
+      }
+    }
+    return alertAdded;
+  }
+
+  /**
+   * Calls the single update variant if there's only one update, otherwise calls batch.
+   * MetaAlerts may defer to an implementation specific IndexDao.
+   * @param updates The list of updates to run
+   * @throws IOException If there's an update error
+   */
+  protected void update(Map<Document, Optional<String>> updates)
+      throws IOException {
+    if (updates.size() == 1) {
+      Entry<Document, Optional<String>> singleUpdate = updates.entrySet().iterator().next();
+      updateDao.update(singleUpdate.getKey(), singleUpdate.getValue());
+    } else if (updates.size() > 1) {
+      updateDao.batchUpdate(updates);
+    } // else we have no updates, so don't do anything
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchDao.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchDao.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchDao.java
index eee91ae..582f1ef 100644
--- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchDao.java
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchDao.java
@@ -17,18 +17,22 @@
  */
 package org.apache.metron.indexing.dao.search;
 
-import java.io.IOException;
-import java.util.List;
-import org.apache.metron.indexing.dao.update.Document;
-
 public interface SearchDao {
 
+  /**
+   * Return search response based on the search request
+   *
+   * @param searchRequest The request defining the search parameters.
+   * @return A response containing the results of the search.
+   * @throws InvalidSearchException If the search request is malformed.
+   */
   SearchResponse search(SearchRequest searchRequest) throws InvalidSearchException;
 
+  /**
+   * Return group response based on the group request
+   * @param groupRequest The request defining the grouping parameters.
+   * @return A response containing the results of the grouping operation.
+   * @throws InvalidSearchException If the grouping request is malformed.
+   */
   GroupResponse group(GroupRequest groupRequest) throws InvalidSearchException;
-
-  Document getLatest(String guid, String sensorType) throws IOException;
-
-  Iterable<Document> getAllLatest(List<GetRequest> getRequests) throws IOException;
-
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchResponse.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchResponse.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchResponse.java
index 5b0b006..b4dfab7 100644
--- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchResponse.java
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchResponse.java
@@ -18,7 +18,6 @@
 package org.apache.metron.indexing.dao.search;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -84,4 +83,13 @@ public class SearchResponse {
     result = 31 * result + (getFacetCounts() != null ? getFacetCounts().hashCode() : 0);
     return result;
   }
+
+  @Override
+  public String toString() {
+    return "SearchResponse{" +
+        "total=" + total +
+        ", results=" + results +
+        ", facetCounts=" + facetCounts +
+        '}';
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/update/PatchUtil.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/update/PatchUtil.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/update/PatchUtil.java
new file mode 100644
index 0000000..5a4ef27
--- /dev/null
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/update/PatchUtil.java
@@ -0,0 +1,50 @@
+/*
+ * 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.update;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.metron.common.utils.JSONUtils;
+import org.apache.metron.indexing.dao.RetrieveLatestDao;
+
+public class PatchUtil {
+
+  public static Document getPatchedDocument(
+      RetrieveLatestDao retrieveLatestDao,
+      PatchRequest request
+      , Optional<Long> timestamp
+  ) throws OriginalNotFoundException, IOException {
+    Map<String, Object> latest = request.getSource();
+    if (latest == null) {
+      Document latestDoc = retrieveLatestDao.getLatest(request.getGuid(), request.getSensorType());
+      if (latestDoc != null && latestDoc.getDocument() != null) {
+        latest = latestDoc.getDocument();
+      } else {
+        throw new OriginalNotFoundException(
+            "Unable to patch an document that doesn't exist and isn't specified.");
+      }
+    }
+    Map<String, Object> updated = JSONUtils.INSTANCE.applyPatch(request.getPatch(), latest);
+    return new Document(updated
+        , request.getGuid()
+        , request.getSensorType()
+        , timestamp.orElse(System.currentTimeMillis()));
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/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 ca21b62..6f136ea 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
@@ -20,11 +20,58 @@ package org.apache.metron.indexing.dao.update;
 import java.io.IOException;
 import java.util.Map;
 import java.util.Optional;
+import org.apache.metron.indexing.dao.RetrieveLatestDao;
 
 public interface UpdateDao {
 
+  /**
+   * Update a given Document and optionally the index where the document exists.  This is a full
+   * update, meaning the current document will be replaced if it exists or a new document will be
+   * created if it does not exist.  Partial updates are not supported in this method.
+   *
+   * @param update The document to replace from the index.
+   * @param index The index where the document lives.
+   * @throws IOException If an error occurs during the update.
+   */
   void 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.
+   * @throws IOException If an error occurs during the updates.
+   */
   void batchUpdate(Map<Document, Optional<String>> updates) throws IOException;
 
+  /**
+   * Update a document in an index given a JSON Patch (see RFC 6902 at
+   * 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.
+   * @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
+      , Optional<Long> timestamp
+  ) throws OriginalNotFoundException, IOException {
+    Document d = PatchUtil.getPatchedDocument(retrieveLatestDao, request, timestamp);
+    update(d, Optional.ofNullable(request.getIndex()));
+  }
+
+
+  /**
+   * 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.
+   * @throws IOException If an error occurs during replacement.
+   */
+  default void 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()));
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/util/IndexingCacheUtil.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/util/IndexingCacheUtil.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/util/IndexingCacheUtil.java
new file mode 100644
index 0000000..3ff3a20
--- /dev/null
+++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/util/IndexingCacheUtil.java
@@ -0,0 +1,35 @@
+/*
+ * 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.util;
+
+import java.util.Map;
+import java.util.function.Function;
+import org.apache.metron.common.configuration.IndexingConfigurations;
+import org.apache.metron.common.zookeeper.ConfigurationsCache;
+
+public class IndexingCacheUtil {
+  public static Function<String, String> getIndexLookupFunction(ConfigurationsCache cache) {
+    return sensorType -> {
+      IndexingConfigurations indexingConfigs = cache.get( IndexingConfigurations.class);
+      Map<String, Object> indexingSensorConfigs = indexingConfigs.getSensorIndexingConfig(sensorType);
+      String indexingTopic = (String) indexingSensorConfigs.get(IndexingConfigurations.INDEX_CONF);
+      return indexingTopic != null ? indexingTopic : sensorType;
+    };
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/49f851e0/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 baa5416..803d320 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
@@ -32,8 +32,10 @@ import java.util.Optional;
 import java.util.stream.Collectors;
 import org.adrianwalker.multilinestring.Multiline;
 import org.apache.metron.common.utils.JSONUtils;
+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.metaalert.MetaScores;
 import org.apache.metron.indexing.dao.search.FieldType;
@@ -57,6 +59,7 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
   public static Map<String, Collection<String>> METAALERT_STORE = new HashMap<>();
 
   private IndexDao indexDao;
+  private int pageSize = 10;
 
   /**
    * {
@@ -96,6 +99,7 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
     // Ignore threatSort for test.
   }
 
+
   @Override
   public Document getLatest(String guid, String sensorType) throws IOException {
     return indexDao.getLatest(guid, sensorType);
@@ -112,7 +116,7 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
   }
 
   @Override
-  public void batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
+  public void batchUpdate(Map<Document, Optional<String>> updates) {
     throw new UnsupportedOperationException("InMemoryMetaAlertDao can't do bulk updates");
   }
 
@@ -128,9 +132,10 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
   }
 
   @Override
-  public void patch(PatchRequest request, Optional<Long> timestamp)
+  public void patch(RetrieveLatestDao retrieveLatestDao, PatchRequest request,
+      Optional<Long> timestamp)
       throws OriginalNotFoundException, IOException {
-    indexDao.patch(request, timestamp);
+    indexDao.patch(retrieveLatestDao, request, timestamp);
   }
 
   @Override
@@ -153,7 +158,7 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
   @SuppressWarnings("unchecked")
   @Override
   public MetaAlertCreateResponse createMetaAlert(MetaAlertCreateRequest request)
-      throws InvalidCreateException, IOException {
+      throws InvalidCreateException {
     List<GetRequest> alertRequests = request.getAlerts();
     if (alertRequests.isEmpty()) {
       MetaAlertCreateResponse response = new MetaAlertCreateResponse();
@@ -162,12 +167,13 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
     }
     // Build meta alert json.  Give it a reasonable GUID
     JSONObject metaAlert = new JSONObject();
-    String metaAlertGuid = "meta_" + (InMemoryDao.BACKING_STORE.get(MetaAlertDao.METAALERTS_INDEX).size() + 1);
+    String metaAlertGuid =
+        "meta_" + (InMemoryDao.BACKING_STORE.get(getMetaAlertIndex()).size() + 1);
     metaAlert.put(GUID, metaAlertGuid);
 
     JSONArray groupsArray = new JSONArray();
     groupsArray.addAll(request.getGroups());
-    metaAlert.put(MetaAlertDao.GROUPS_FIELD, groupsArray);
+    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.
@@ -183,7 +189,8 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
         List<SearchResult> searchResults = searchResponse.getResults();
         if (searchResults.size() > 1) {
           throw new InvalidCreateException(
-              "Found more than one result for: " + alertRequest.getGuid() + ". Values: " + searchResults
+              "Found more than one result for: " + alertRequest.getGuid() + ". Values: "
+                  + searchResults
           );
         }
 
@@ -191,7 +198,9 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
           SearchResult result = searchResults.get(0);
           alertArray.add(result.getSource());
           Double threatScore = Double
-              .parseDouble(result.getSource().getOrDefault(THREAT_FIELD_DEFAULT, "0").toString());
+              .parseDouble(
+                  result.getSource().getOrDefault(MetaAlertConstants.THREAT_FIELD_DEFAULT, "0")
+                      .toString());
 
           threatScores.add(threatScore);
         }
@@ -201,12 +210,12 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
       alertGuids.add(alertRequest.getGuid());
     }
 
-    metaAlert.put(MetaAlertDao.ALERT_FIELD, alertArray);
+    metaAlert.put(MetaAlertConstants.ALERT_FIELD, alertArray);
     metaAlert.putAll(new MetaScores(threatScores).getMetaScores());
-    metaAlert.put(STATUS_FIELD, MetaAlertStatus.ACTIVE.getStatusString());
+    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(MetaAlertDao.METAALERTS_INDEX).add(metaAlert.toJSONString());
+    InMemoryDao.BACKING_STORE.get(getMetaAlertIndex()).add(metaAlert.toJSONString());
 
     METAALERT_STORE.put(metaAlertGuid, new HashSet<>(alertGuids));
 
@@ -217,12 +226,13 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
   }
 
   @Override
-  public boolean addAlertsToMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests) throws IOException {
+  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());
+    Collection<String> alertGuids = alertRequests.stream().map(GetRequest::getGuid)
+        .collect(Collectors.toSet());
     boolean added = currentAlertGuids.addAll(alertGuids);
     if (added) {
       METAALERT_STORE.put(metaAlertGuid, currentAlertGuids);
@@ -231,12 +241,13 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
   }
 
   @Override
-  public boolean removeAlertsFromMetaAlert(String metaAlertGuid, List<GetRequest> alertRequests) throws IOException {
+  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());
+    Collection<String> alertGuids = alertRequests.stream().map(GetRequest::getGuid)
+        .collect(Collectors.toSet());
     boolean removed = currentAlertGuids.removeAll(alertGuids);
     if (removed) {
       METAALERT_STORE.put(metaAlertGuid, currentAlertGuids);
@@ -249,16 +260,17 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
   public boolean updateMetaAlertStatus(String metaAlertGuid, MetaAlertStatus status)
       throws IOException {
     boolean statusChanged = false;
-    List<String> metaAlerts = InMemoryDao.BACKING_STORE.get(MetaAlertDao.METAALERTS_INDEX);
-    for (String metaAlert: metaAlerts) {
+    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(STATUS_FIELD));
+        statusChanged = !status.getStatusString()
+            .equals(metaAlertJSON.get(MetaAlertConstants.STATUS_FIELD));
         if (statusChanged) {
-          metaAlertJSON.put(STATUS_FIELD, status.getStatusString());
+          metaAlertJSON.put(MetaAlertConstants.STATUS_FIELD, status.getStatusString());
           metaAlerts.remove(metaAlert);
           metaAlerts.add(metaAlertJSON.toJSONString());
-          InMemoryDao.BACKING_STORE.put(MetaAlertDao.METAALERTS_INDEX, metaAlerts);
+          InMemoryDao.BACKING_STORE.put(getMetaAlertIndex(), metaAlerts);
         }
         break;
       }
@@ -266,9 +278,24 @@ public class InMemoryMetaAlertDao implements MetaAlertDao {
     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";
+  }
+
   public static void clear() {
     InMemoryDao.clear();
     METAALERT_STORE.clear();
   }
-
 }