private void handleIndexOperation(final ElasticsearchIndexEvent elasticsearchIndexEvent)
      throws IndexDocNotFoundException {

    Preconditions.checkNotNull(elasticsearchIndexEvent, "elasticsearchIndexEvent cannot be null");

    final UUID messageId = elasticsearchIndexEvent.getIndexBatchId();
    Preconditions.checkNotNull(messageId, "messageId must not be null");

    final String message = esMapPersistence.getString(messageId.toString());

    final IndexOperationMessage indexOperationMessage;
    if (message == null) {

      // provide some time back pressure before performing a quorum read
      if (queueFig.getQuorumFallback()
          && System.currentTimeMillis()
              > elasticsearchIndexEvent.getCreationTime() + queueFig.getLocalQuorumTimeout()) {

        if (logger.isDebugEnabled()) {
          logger.debug("ES batch with id {} not found, reading with strong consistency", messageId);
        }

        final String highConsistency =
            esMapPersistence.getStringHighConsistency(messageId.toString());
        if (highConsistency == null) {

          throw new RuntimeException(
              "ES batch with id " + messageId + " not found when reading with strong consistency");
        }

        indexOperationMessage =
            ObjectJsonSerializer.INSTANCE.fromString(highConsistency, IndexOperationMessage.class);

      } else {

        throw new IndexDocNotFoundException(elasticsearchIndexEvent.getIndexBatchId());
      }

    } else {

      indexOperationMessage =
          ObjectJsonSerializer.INSTANCE.fromString(message, IndexOperationMessage.class);
    }

    // don't let this continue if there's nothing to index
    if (indexOperationMessage == null || indexOperationMessage.isEmpty()) {
      throw new RuntimeException(
          "IndexOperationMessage cannot be null or empty after retrieving from map persistence");
    }

    // always do a check to ensure the indexes are initialized for the index requests
    initializeEntityIndexes(indexOperationMessage);

    // send it to to be indexed
    indexProducer.put(indexOperationMessage).toBlocking().last();
  }
  /**
   * Get the index response from the jobId
   *
   * @param jobId
   * @return
   */
  private ReIndexStatus getIndexResponse(final String jobId) {

    final String stringStatus = mapManager.getString(jobId + MAP_STATUS_KEY);

    if (stringStatus == null) {
      return new ReIndexStatus(jobId, Status.UNKNOWN, 0, 0);
    }

    final Status status = Status.valueOf(stringStatus);

    final long processedCount = mapManager.getLong(jobId + MAP_COUNT_KEY);
    final long lastUpdated = mapManager.getLong(jobId + MAP_COUNT_KEY);

    return new ReIndexStatus(jobId, status, processedCount, lastUpdated);
  }
  /**
   * Queue up an indexOperationMessage for multi region execution
   *
   * @param indexOperationMessage
   */
  public void queueIndexOperationMessage(final IndexOperationMessage indexOperationMessage) {

    // don't try to produce something with nothing
    if (indexOperationMessage == null || indexOperationMessage.isEmpty()) {
      return;
    }

    final String jsonValue = ObjectJsonSerializer.INSTANCE.toString(indexOperationMessage);

    final UUID newMessageId = UUIDGenerator.newTimeUUID();

    final int expirationTimeInSeconds =
        (int) TimeUnit.MILLISECONDS.toSeconds(indexProcessorFig.getIndexMessageTtl());

    // write to the map in ES
    esMapPersistence.putString(newMessageId.toString(), jsonValue, expirationTimeInSeconds);

    // now queue up the index message

    final ElasticsearchIndexEvent elasticsearchIndexEvent =
        new ElasticsearchIndexEvent(queueFig.getPrimaryRegion(), newMessageId);

    // send to the topic so all regions index the batch

    offerTopic(elasticsearchIndexEvent);
  }
  /**
   * Write our state meta data into cassandra so everyone can see it
   *
   * @param jobId
   * @param status
   * @param processedCount
   * @param lastUpdated
   */
  private void writeStateMeta(
      final String jobId, final Status status, final long processedCount, final long lastUpdated) {

    if (logger.isDebugEnabled()) {
      logger.debug(
          "Flushing state for jobId {}, status {}, processedCount {}, lastUpdated {}",
          jobId,
          status,
          processedCount,
          lastUpdated);
    }

    mapManager.putString(jobId + MAP_STATUS_KEY, status.name());
    mapManager.putLong(jobId + MAP_COUNT_KEY, processedCount);
    mapManager.putLong(jobId + MAP_UPDATED_KEY, lastUpdated);
  }
  /** Write the cursor state to the map in cassandra */
  private void writeCursorState(final String jobId, final EdgeScope edge) {

    final JsonNode node =
        EdgeScopeSerializer.INSTANCE.toNode(CursorSerializerUtil.getMapper(), edge);

    final String serializedState = CursorSerializerUtil.asString(node);

    mapManager.putString(jobId + MAP_CURSOR_KEY, serializedState, INDEX_TTL);
  }
  /**
   * 4. Delete all entity documents out of elasticsearch. 5. Compact Graph so that it deletes the
   * marked values. 6. Delete entity from cassandra using the map manager.
   */
  private Id deleteAsync(MapManager mapManager, ApplicationScope applicationScope, Id entityId) {
    try {
      // Step 4 && 5

      if (!skipIndexingForType(entityId.getType(), applicationScope)) {

        asyncEventService.queueEntityDelete(applicationScope, entityId);
      }
      // Step 6
      // delete from our UUID index
      mapManager.delete(entityId.getUuid().toString());
      return entityId;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  /** Swap our cursor for an optional edgescope */
  private Optional<EdgeScope> parseCursor(final Optional<String> cursor) {

    if (!cursor.isPresent()) {
      return Optional.absent();
    }

    // get our cursor
    final String persistedCursor = mapManager.getString(cursor.get());

    if (persistedCursor == null) {
      return Optional.absent();
    }

    final JsonNode node = CursorSerializerUtil.fromString(persistedCursor);

    final EdgeScope edgeScope =
        EdgeScopeSerializer.INSTANCE.fromJsonNode(node, CursorSerializerUtil.getMapper());

    return Optional.of(edgeScope);
  }