/**
  * Executes a target flow. If execution is failed except on {@link ExecutionPhase#SETUP setup},
  * {@link ExecutionPhase#FINALIZE finalize}, or {@link ExecutionPhase#CLEANUP cleanup}, then the
  * {@code finalize} phase will be executed for recovery before execution is aborted.
  *
  * @param batchId target batch ID
  * @param flowId target flow ID
  * @param executionId target execution ID
  * @throws InterruptedException if interrupted during this execution
  * @throws IOException if failed to execute target flow
  * @throws IllegalArgumentException if some parameters were {@code null}
  */
 public void executeFlow(String batchId, String flowId, String executionId)
     throws InterruptedException, IOException {
   if (batchId == null) {
     throw new IllegalArgumentException("batchId must not be null"); // $NON-NLS-1$
   }
   if (flowId == null) {
     throw new IllegalArgumentException("flowId must not be null"); // $NON-NLS-1$
   }
   if (executionId == null) {
     throw new IllegalArgumentException("executionId must not be null"); // $NON-NLS-1$
   }
   FlowScript flow = script.findFlow(flowId);
   if (flow == null) {
     throw new IllegalArgumentException(
         MessageFormat.format(
             "Flow is undefined: batchId={0}, flowId={1}, executionId={2}",
             batchId, flowId, executionId));
   }
   ExecutionLock lock = acquireExecutionLock(batchId);
   try {
     lock.beginFlow(flowId, executionId);
     executeFlow(batchId, flow, executionId);
     lock.endFlow(flowId, executionId);
   } finally {
     lock.close();
   }
 }
 /**
  * Executes a target flow. If execution is failed except on {@link ExecutionPhase#SETUP setup},
  * {@link ExecutionPhase#FINALIZE finalize}, or {@link ExecutionPhase#CLEANUP cleanup}, then the
  * {@code finalize} phase will be executed for recovery before the target flow execution is
  * aborted. Execution ID for each flow will automatically generated.
  *
  * @param batchId target batch ID
  * @throws InterruptedException if interrupted during this execution
  * @throws IOException if failed to execute target batch
  * @throws IllegalArgumentException if some parameters were {@code null}
  */
 public void executeBatch(String batchId) throws InterruptedException, IOException {
   if (batchId == null) {
     throw new IllegalArgumentException("batchId must not be null"); // $NON-NLS-1$
   }
   ExecutorService executor = createJobflowExecutor(batchId);
   YSLOG.info("I01000", batchId);
   long start = System.currentTimeMillis();
   try {
     ExecutionLock lock = acquireExecutionLock(batchId);
     try {
       BatchScheduler batchScheduler = new BatchScheduler(batchId, script, lock, executor);
       batchScheduler.run();
     } finally {
       lock.close();
     }
     YSLOG.info("I01001", batchId);
   } catch (IOException e) {
     YSLOG.error(e, "E01001", batchId);
     throw e;
   } catch (InterruptedException e) {
     YSLOG.warn(e, "W01001", batchId);
     throw e;
   } finally {
     long end = System.currentTimeMillis();
     YSLOG.info("I01999", batchId, end - start);
   }
 }
  /**
   * Lock provider for batch.
   *
   * @throws Exception if failed
   */
  @Test
  public void batch() throws Exception {
    Map<String, String> conf = new HashMap<>();
    conf.put(BasicLockProvider.KEY_DIRECTORY, folder.getRoot().getAbsolutePath());
    conf.put(ExecutionLockProvider.KEY_SCOPE, ExecutionLock.Scope.BATCH.getSymbol());

    ServiceProfile<ExecutionLockProvider> profile =
        new ServiceProfile<>(
            "testing",
            BasicLockProvider.class,
            conf,
            ProfileContext.system(getClass().getClassLoader()));
    ExecutionLockProvider instance1 = profile.newInstance();
    ExecutionLockProvider instance2 = profile.newInstance();

    try (ExecutionLock lock = instance1.newInstance("batch1")) {
      lock.beginFlow("flow1", "exec1");
      try {
        instance2.newInstance("batch1");
        fail("cannot run same batch");
      } catch (IOException e) {
        // ok.
      }
      try (ExecutionLock other = instance2.newInstance("batch2")) {
        // can acquire any flow/exec lock
        other.beginFlow("flow2", "exec1");
        other.endFlow("flow2", "exec1");
        other.beginFlow("flow1", "exec2");
        other.endFlow("flow1", "exec2");
        other.beginFlow("flow2", "exec2");
        other.endFlow("flow2", "exec2");
      }
    }
  }
  /**
   * Missing directory config.
   *
   * @throws Exception if failed
   */
  @Test
  public void missing_directory() throws Exception {
    File lockDir = folder.newFolder("missing");
    Assume.assumeThat(lockDir.delete(), is(true));

    Map<String, String> conf = new HashMap<>();
    conf.put(BasicLockProvider.KEY_DIRECTORY, lockDir.getAbsolutePath());

    ServiceProfile<ExecutionLockProvider> profile =
        new ServiceProfile<>(
            "testing",
            BasicLockProvider.class,
            conf,
            ProfileContext.system(getClass().getClassLoader()));
    ExecutionLockProvider instance = profile.newInstance();
    try (ExecutionLock lock = instance.newInstance("batch")) {
      lock.beginFlow("a", "b");
      lock.endFlow("a", "b");
    }
  }
 /**
  * Executes a target phase.
  *
  * @param context the current context
  * @throws InterruptedException if interrupted during this execution
  * @throws IOException if failed to execute target phase
  * @throws IllegalArgumentException if some parameters were {@code null}
  * @since 0.2.5
  */
 public void executePhase(ExecutionContext context) throws InterruptedException, IOException {
   if (context == null) {
     throw new IllegalArgumentException("context must not be null"); // $NON-NLS-1$
   }
   FlowScript flow = script.findFlow(context.getFlowId());
   if (flow == null) {
     throw new IllegalArgumentException(
         MessageFormat.format(
             "Flow is undefined: batchId={0}, flowId={1}, executionId={2}",
             context.getBatchId(), context.getFlowId(), context.getExecutionId()));
   }
   Set<ExecutionScript> executions = flow.getScripts().get(context.getPhase());
   ExecutionLock lock = acquireExecutionLock(context.getBatchId());
   try {
     lock.beginFlow(context.getFlowId(), context.getExecutionId());
     executePhase(context, executions);
     lock.endFlow(context.getFlowId(), context.getExecutionId());
   } finally {
     lock.close();
   }
 }
  /**
   * Simple testing.
   *
   * @throws Exception if failed
   */
  @Test
  public void simple() throws Exception {
    File lockDir = folder.getRoot();
    int start = lockDir.list().length;

    Map<String, String> conf = new HashMap<>();
    conf.put(BasicLockProvider.KEY_DIRECTORY, lockDir.getAbsolutePath());

    ServiceProfile<ExecutionLockProvider> profile =
        new ServiceProfile<>(
            "testing",
            BasicLockProvider.class,
            conf,
            ProfileContext.system(getClass().getClassLoader()));
    ExecutionLockProvider instance = profile.newInstance();
    try (ExecutionLock lock = instance.newInstance("batch")) {
      lock.beginFlow("flow", "exec");
      assertThat(lockDir.list().length, is(greaterThan(start)));
    }
    assertThat(lockDir.list().length, is(start));
  }
  /**
   * Configure using variable.
   *
   * @throws Exception if failed
   */
  @Test
  public void with_variable() throws Exception {
    File lockDir = folder.getRoot();
    int start = lockDir.list().length;
    VariableResolver var =
        new VariableResolver(Collections.singletonMap("LOCATION", lockDir.getAbsolutePath()));
    Map<String, String> conf = new HashMap<>();
    conf.put(BasicLockProvider.KEY_DIRECTORY, "${LOCATION}");

    ServiceProfile<ExecutionLockProvider> profile =
        new ServiceProfile<>(
            "testing",
            BasicLockProvider.class,
            conf,
            new ProfileContext(getClass().getClassLoader(), var));
    ExecutionLockProvider instance = profile.newInstance();
    try (ExecutionLock lock = instance.newInstance("batch")) {
      lock.beginFlow("flow", "exec");
      assertThat(lockDir.list().length, is(greaterThan(start)));
    }
    assertThat(lockDir.list().length, is(start));
  }