/**
  * Depending on the behavior of the service, it's shutdown method may be called in order to
  * perform house keeping tasks such as closing files and other depended services.
  */
 public void shutdown() {
   try {
     feedJobManager.savePhaseStats(context);
     doPhaseShutdown();
     FeedFileReader reader = context.getFeedFileReader();
     reader.close();
   } catch (IOException e) {
     logger.warn("*** Unable to close feed file, " + context.getFeedFileName());
   }
 }
/**
 * Author: Hycel Taylor
 *
 * <p>The StandardFeedLifeCycle is the most common feed life cycle service. It can process most feed
 * file configurations that use that standard phase configuration where data records are represented
 * within the feed file as single rows.
 *
 * <p>The standard phase configuration requires the following elements be defined within a given
 * feed file configuration document.
 *
 * <ul>
 *   <li>preEventPhases
 *   <li>batchEventPhases
 *   <li>postEventPhases
 * </ul>
 *
 * Only the batchEventPhase must have at least one phase element definition.
 *
 * <p>The format of the feed file doesn't matter as long as the resulting data record is represented
 * as a single row of data. This means it can process feeds formated as, delimited, fixed, XML and
 * custom binary.
 */
public class EventPhaseLifeCycleService extends MainLifeCycleService {
  public enum EVENT_PHASE {
    preEventPhases,
    postEventPhases,
    batchEventPhases,
    preFeedFilePhases
  }

  private Splitter detailSplitter;
  private EventPhasesLoader phaseConfigLoader;
  private CheckpointService checkpointService;

  private List<Phase> preFilePhaseList;
  private List<Phase> preEventPhaseList;
  private List<Phase> batchPhaseList;
  private List<Phase> postEventPhaseList;

  private static Log logger = Log.getInstance(EventPhaseLifeCycleService.class);

  public EventPhaseLifeCycleService() {}

  /**
   * The life cycle of the set of detail phases are traversed using the detail splitter.
   *
   * <p>
   *
   * <ul>
   *   <li>Injection - required
   * </ul>
   *
   * @param detailSplitter reference to the detail splitter.
   */
  public void setDetailSplitter(Splitter detailSplitter) {
    this.detailSplitter = detailSplitter;
  }

  /**
   * The PhaseConfigLoader parses and instantiates all phases.
   *
   * <p>
   *
   * <ul>
   *   <li>Injection - required
   * </ul>
   *
   * @param phaseConfigLoader reference to phase configuration loader.
   */
  public void setPhaseConfigLoader(EventPhasesLoader phaseConfigLoader) {
    this.phaseConfigLoader = phaseConfigLoader;
  }

  /**
   * Determines whether checkpoint operations are performed. If not set, not check point operations
   * will be performed.
   *
   * <p>
   *
   * <ul>
   *   <li>Injection - optional
   * </ul>
   *
   * @param checkpointService reference to a class that extends CheckpointService.
   */
  public void setCheckpointService(CheckpointService checkpointService) {
    this.checkpointService = checkpointService;
  }

  /**
   * Stores the name of the given service as it is defined in the feed configuration document.
   *
   * @return the name of the service as it is defined in the feed configuration document.
   */
  public String name() {
    return this.getClass().getSimpleName();
  }

  /**
   * Initialize service by peforming the following tasks:
   *
   * <ul>
   *   <li>load the feed file
   *   <li>instantiate the row description loader
   *   <li>load the splitters
   * </ul>
   */
  public void initialize() {
    initPhases();
  }

  /** Initialize preFile, preEventPhases, batchPhases and postEventPhases. */
  protected void initPhases() {
    preFilePhaseList =
        getPhases(phaseConfigLoader.getPhaseInfoList(EVENT_PHASE.preFeedFilePhases.name()));
    context.setPreFeedFilePhases(preFilePhaseList);

    preEventPhaseList =
        getPhases(phaseConfigLoader.getPhaseInfoList(EVENT_PHASE.preEventPhases.name()));
    batchPhaseList =
        getPhases(phaseConfigLoader.getPhaseInfoList(EVENT_PHASE.batchEventPhases.name()));
    postEventPhaseList =
        getPhases(phaseConfigLoader.getPhaseInfoList(EVENT_PHASE.postEventPhases.name()));
  }

  /**
   * This method should contain the set of instructions necessary to perform the tasks of the given
   * service. All exceptions are handled here by catching Exception and calling method,
   * handleFailure().
   */
  public void execute() throws FeedErrorException {
    final FeedJob feedJob = context.getFeedJob();

    try {
      doPreEventPhases();
      doBatchEventPhases();
      doPostEventPhases();

      feedJobManager.moveToCompleted(feedJob, context);
    } catch (Exception e) {
      handleFailure(feedJob, e);
    } finally {
      shutdown();
    }
  }

  /**
   * Depending on the behavior of the service, it's shutdown method may be called in order to
   * perform house keeping tasks such as closing files and other depended services.
   */
  public void shutdown() {
    try {
      feedJobManager.savePhaseStats(context);
      doPhaseShutdown();
      FeedFileReader reader = context.getFeedFileReader();
      reader.close();
    } catch (IOException e) {
      logger.warn("*** Unable to close feed file, " + context.getFeedFileName());
    }
  }

  /** Perform shutdown operations on all phases. */
  protected void doPhaseShutdown() {
    shutdownPhases(postEventPhaseList);
    shutdownPhases(batchPhaseList);
    shutdownPhases(preEventPhaseList);
    shutdownPhases(preFilePhaseList);
  }

  /** Iterates and executes the list of preEventPhases. */
  private void doPreEventPhases() {
    final List<Phase> phases = preEventPhaseList;

    for (Phase phase : phases) {
      context.setCurrentPhaseId(phase.getName());
      feedJobManager.startPhaseStats(phase, context);
      phase.execute();
      feedJobManager.endPhaseStats(phase, context);
    }
  }

  /** Iterates and executes the list of batchEventPhases. */
  private void doBatchEventPhases() {
    // Must be called after initialization and before anything else.
    detailSplitter.prePhaseExecute();

    // Get checkpoint from context after checkpoint phase.initialize().
    if (checkpointService != null) {
      traversDetailWithCheckpoint(checkpointService.getCheckpoint());
    } else {
      traversDetailWithOutCheckpoint();
    }
  }

  protected void traversDetailWithOutCheckpoint() {
    final List<Phase> phases = batchPhaseList;

    while (detailSplitter.hasNextRow()) {
      for (Phase phase : phases) {
        context.setCurrentPhaseId(phase.getName());
        feedJobManager.startPhaseStats(phase, context);
        phase.execute();
        feedJobManager.endPhaseStats(phase, context);
      }
    }
  }

  protected void traversDetailWithCheckpoint(FeedCheckpoint checkpoint) {
    final List<Phase> phases = batchPhaseList;

    // Determine if a checkpoint has been created and if so, move to it.
    Boolean movingToCheckpoint = performCheckpoint(detailSplitter, checkpoint);

    while (detailSplitter.hasNextRow()) {
      for (Phase phase : phases) {
        // Bypass phases processing when restart from a checkpoint.
        if (!movingToCheckpoint) {
          context.setCurrentPhaseId(phase.getName());
          feedJobManager.startPhaseStats(phase, context);
          phase.execute();
          feedJobManager.endPhaseStats(phase, context);
        }

        // Checkpoint restart has be reached.
        if (movingToCheckpoint && checkpoint.getPhaseId().equals(phase.getName())) {
          movingToCheckpoint = false;
        }
      }
    }
  }

  /**
   * Checks to see if there is a check point. If so, and the checkpoint phaseId does not equal,
   * NO_PHASE_ID, call the necessary opertions to move to the current checkpoint.
   *
   * @param splitter the splitter to see if it implments the interface CheckpointHandler.
   * @param checkpoint the checkpoint object associated with the given feed file.
   * @return true if a move to check point was initiated.
   */
  private Boolean performCheckpoint(Splitter splitter, FeedCheckpoint checkpoint) {
    // Move to checkpoint if splitter is instance of checkpoint
    if (splitter instanceof Checkpointable) {
      // Determine if a checkpoint has been created.
      if (checkpoint != null && !checkpoint.getPhaseId().equals(CheckpointHelper.NO_PHASE_ID)) {
        if (checkpointService != null) checkpointService.beforeCheckpoint();

        ((Checkpointable) splitter).moveToCheckpoint(checkpoint);

        if (checkpointService != null) checkpointService.afterCheckpoint();

        return true;
      }
    }
    return false;
  }

  /** Iterates and executes the list of postEventPhases. */
  private void doPostEventPhases() {
    final List<Phase> phases = postEventPhaseList;

    for (Phase phase : phases) {
      context.setCurrentPhaseId(phase.getName());
      feedJobManager.startPhaseStats(phase, context);
      phase.execute();
      feedJobManager.endPhaseStats(phase, context);
    }
  }
}