/*
 * 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.flume.sink.elasticsearch;

import java.io.IOException;

import org.apache.commons.lang.time.FastDateFormat;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.conf.ComponentConfiguration;
import org.apache.flume.conf.Configurable;
import org.apache.flume.conf.ConfigurableComponent;
import org.apache.flume.formatter.output.BucketPath;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.client.Client;

import com.google.common.annotations.VisibleForTesting;

/**
 * Abstract base class for custom implementations of
 * {@link ElasticSearchIndexRequestBuilderFactory}.
 */
public abstract class AbstractElasticSearchIndexRequestBuilderFactory
    implements ElasticSearchIndexRequestBuilderFactory {

  /**
   * {@link FastDateFormat} to use for index names
   *   in {@link #getIndexName(String, long)}
   */
  protected final FastDateFormat fastDateFormat;

  /**
   * Constructor for subclasses
   * @param fastDateFormat {@link FastDateFormat} to use for index names
   */
  protected AbstractElasticSearchIndexRequestBuilderFactory(FastDateFormat fastDateFormat) {
    this.fastDateFormat = fastDateFormat;
  }

  /**
   * @see Configurable
   */
  @Override
  public abstract void configure(Context arg0);

  /**
   * @see ConfigurableComponent
   */
  @Override
  public abstract void configure(ComponentConfiguration arg0);

  /**
   * Creates and prepares an {@link IndexRequestBuilder} from the supplied
   * {@link Client} via delegation to the subclass-hook template methods
   * {@link #getIndexName(String, long)} and
   * {@link #prepareIndexRequest(IndexRequestBuilder, String, String, Event)}
   */
  @Override
  public IndexRequestBuilder createIndexRequest(Client client,
        String indexPrefix, String indexType, Event event) throws IOException {
    IndexRequestBuilder request = prepareIndex(client);
    String realIndexPrefix = BucketPath.escapeString(indexPrefix, event.getHeaders());
    String realIndexType = BucketPath.escapeString(indexType, event.getHeaders());

    TimestampedEvent timestampedEvent = new TimestampedEvent(event);
    long timestamp = timestampedEvent.getTimestamp();

    String indexName = getIndexName(realIndexPrefix, timestamp);
    prepareIndexRequest(request, indexName, realIndexType, timestampedEvent);
    return request;
  }

  @VisibleForTesting
  IndexRequestBuilder prepareIndex(Client client) {
    return client.prepareIndex();
  }

  /**
   * Gets the name of the index to use for an index request
   * @param indexPrefix
   *          Prefix of index name to use -- as configured on the sink
   * @param timestamp
   *          timestamp (millis) to format / use
   * @return index name of the form 'indexPrefix-formattedTimestamp'
   */
  protected String getIndexName(String indexPrefix, long timestamp) {
    return new StringBuilder(indexPrefix).append('-')
      .append(fastDateFormat.format(timestamp)).toString();
  }

  /**
   * Prepares an ElasticSearch {@link IndexRequestBuilder} instance
   * @param indexRequest
   *          The (empty) ElasticSearch {@link IndexRequestBuilder} to prepare
   * @param indexName
   *          Index name to use -- as per {@link #getIndexName(String, long)}
   * @param indexType
   *          Index type to use -- as configured on the sink
   * @param event
   *          Flume event to serialize and add to index request
   * @throws IOException
   *           If an error occurs e.g. during serialization
  */
  protected abstract void prepareIndexRequest(
      IndexRequestBuilder indexRequest, String indexName,
      String indexType, Event event) throws IOException;

}