/**
   * Tests if a {@link Component}'s execution is not considered if the {@link ComponentExecutor} got
   * cancelled before the actual run was performed.
   *
   * @throws ComponentExecutionException on unexpected error
   * @throws ComponentException on unexpected error
   * @throws InterruptedException on unexpected interruption
   * @throws ExecutionException on unexpected interruption
   */
  @Test(timeout = TEST_TIMEOUT_500_MSEC)
  public void testOnCancelledBeforeExecuteByConsLimWasCalled()
      throws ComponentException, ComponentExecutionException, InterruptedException,
          ExecutionException {
    final ComponentExecutionRelatedInstances compExeRelatedInstancesStub =
        createComponentExecutionRelatedInstancesStub();
    final ComponentExecutor compExecutor =
        new ComponentExecutor(compExeRelatedInstancesStub, ComponentExecutionType.ProcessInputs);
    Future<Boolean> cancelTask =
        ConcurrencyUtils.getAsyncTaskService()
            .submit(
                new Callable<Boolean>() {

                  @Override
                  public Boolean call() throws Exception {
                    compExecutor.onCancelled();
                    boolean isComponentKnownCanceled =
                        compExeRelatedInstancesStub.compExeRelatedStates.isComponentCancelled.get();
                    return isComponentKnownCanceled;
                  }
                });
    assertFalse(cancelTask.get());
    compExecutor.executeByConsideringLimitations();
    assertTrue(compExeRelatedInstancesStub.compExeRelatedStates.isComponentCancelled.get());
  }
  /** OSGi-DS lifecycle method. Starts background logging of accumulated log lines. */
  public void activate() {
    enabled = (System.getProperty(SYSTEM_PROPERTY_FOR_ACTIVATION) != null);
    if (!enabled) {
      log.debug("Combined workflow console log is disabled");
      return;
    }

    // TODO improve filename, locking/uniqueness etc.
    String logFileName = StringUtils.format("console.combined.%d.log", System.currentTimeMillis());
    File outputDir = configurationService.getConfigurablePath(ConfigurablePathId.PROFILE_OUTPUT);
    logFile = new File(outputDir, logFileName);
    try {
      fileWriter = new BufferedWriter(new FileWriter(logFile));
      // TODO use the thread pool instead?
      backgroundWriterTask = new BackgroundLogWriterTask(fileWriter, Thread.MIN_PRIORITY);
      backgroundTaskFuture =
          ConcurrencyUtils.getAsyncTaskService()
              .submit(backgroundWriterTask, "Common ConsoleRow log " + logFile.getAbsolutePath());
      log.debug(
          "Logging combined workflow console output to "
              + logFileName
              + " (NOTE: may not capture all output yet)"); // TODO 5.0
    } catch (IOException e) {
      log.error("Failed to set up background console logging to " + logFileName, e);
    }
  }
  /**
   * Tests if waiting for execution permission is interrupted if component is cancelled.
   *
   * @throws ComponentExecutionException on unexpected error
   * @throws ComponentException on unexpected error
   * @throws InterruptedException on unexpected interruption
   * @throws ExecutionException on unexpected interruption
   */
  @Test(timeout = TEST_TIMEOUT_500_MSEC)
  public void testWaitingForExecutionPermissionOnCancelled()
      throws ComponentException, ComponentExecutionException, InterruptedException,
          ExecutionException {
    ComponentExecutionPermitsService compExePermitsService =
        createComponentExecutionPermitServiceMock(false);
    final ComponentExecutionRelatedInstances compExeRelatedInstancesStub =
        createComponentExecutionRelatedInstancesStub();
    final ComponentExecutor compExecutor =
        new ComponentExecutor(compExeRelatedInstancesStub, ComponentExecutionType.ProcessInputs);
    compExecutor.bindComponentExecutionPermitsService(compExePermitsService);
    final int delayMsec = 100;
    ConcurrencyUtils.getAsyncTaskService()
        .scheduleAfterDelay(
            new Runnable() {

              @TaskDescription("Delayed cancel call")
              @Override
              public void run() {
                compExecutor.onCancelled();
              }
            },
            delayMsec);
    compExecutor.acquireExecutionPermission();
    EasyMock.verify(compExePermitsService);
    assertTrue(compExeRelatedInstancesStub.compExeRelatedStates.isComponentCancelled.get());
  }
  /**
   * Tests if a {@link Component}'s execution is not considered if the {@link ComponentExecutor} got
   * cancelled before the actual run was performed.
   *
   * @throws ComponentExecutionException on unexpected error
   * @throws ComponentException on unexpected error
   * @throws InterruptedException on unexpected interruption
   * @throws ExecutionException on unexpected interruption
   */
  @Test(timeout = TEST_TIMEOUT_500_MSEC)
  public void testOnCancelledDuringExecuteByConsLimWasCalled()
      throws ComponentException, ComponentExecutionException, InterruptedException,
          ExecutionException {
    ComponentExecutionPermitsService compExePermitsService =
        createComponentExecutionPermitServiceMock();
    final ComponentExecutionRelatedInstances compExeRelatedInstancesStub =
        createComponentExecutionRelatedInstancesStub();
    final ComponentExecutor compExecutor =
        new ComponentExecutor(compExeRelatedInstancesStub, ComponentExecutionType.ProcessInputs);
    compExecutor.bindComponentExecutionPermitsService(compExePermitsService);
    compExecutor.acquireExecutionPermission();
    Future<Boolean> cancelTask =
        ConcurrencyUtils.getAsyncTaskService()
            .submit(
                new Callable<Boolean>() {

                  @Override
                  public Boolean call() throws Exception {
                    compExecutor.onCancelled();
                    return compExeRelatedInstancesStub.compExeRelatedStates.isComponentCancelled
                        .get();
                  }
                });
    assertFalse(cancelTask.get());
    compExecutor.performExecutionAndReleasePermission();
    EasyMock.verify(compExePermitsService);
    assertTrue(compExeRelatedInstancesStub.compExeRelatedStates.isComponentCancelled.get());
  }
  /**
   * Tests canceling of {@link Component#processInputs()} in failure case, that means {@link
   * Component#processInputs()} doesn't return within expected amount of time.
   *
   * @throws ComponentExecutionException on unexpected error
   * @throws ComponentException on unexpected error
   * @throws InterruptedException on unexpected error
   * @throws ExecutionException on unexpected error
   */
  @Test(timeout = TEST_TIMEOUT_500_MSEC)
  public void testCancelProcessingInputsFailure()
      throws ComponentExecutionException, ComponentException, InterruptedException,
          ExecutionException {

    ComponentExecutionRelatedInstances compExeRelatedInstancesStub =
        createComponentExecutionRelatedInstancesStub();

    final CountDownLatch processInputsCalledLatch = new CountDownLatch(1);
    Component compStub =
        new ComponentDefaultStub.Default() {

          private int processInputsCount = 0;

          private int onProcessInputsInterruptedCount = 0;

          @Override
          public void processInputs() throws ComponentException {
            if (processInputsCount > 0) {
              fail("'processInputs' is expected to be called only once");
            }
            processInputsCalledLatch.countDown();
            final CountDownLatch dummyLatch = new CountDownLatch(1);
            while (true) {
              try {
                dummyLatch.await();
              } catch (InterruptedException e) {
                // ignore for test purposes
                e = null;
              }
            }
          }

          @Override
          public void onProcessInputsInterrupted(ThreadHandler executingThreadHandler) {
            if (onProcessInputsInterruptedCount > 0) {
              fail("'onProcessInputsInterrupted' is expected to be called only once");
            }
          }
        };
    compExeRelatedInstancesStub.component.set(compStub);

    ComponentStateMachine compStateMachineMock =
        EasyMock.createStrictMock(ComponentStateMachine.class);
    Capture<ComponentStateMachineEvent> compStateMachineEventCapture = new Capture<>();
    compStateMachineMock.postEvent(EasyMock.capture(compStateMachineEventCapture));
    EasyMock.expectLastCall();
    EasyMock.replay(compStateMachineMock);
    compExeRelatedInstancesStub.compStateMachine = compStateMachineMock;

    ComponentExecutionStorageBridge compExeStorageBridgeMock =
        createComponentExecutionStorageBridgeRunFailure(compExeRelatedInstancesStub);
    compExeRelatedInstancesStub.compExeStorageBridge = compExeStorageBridgeMock;

    ComponentContextBridge compCtxBridgeMock =
        EasyMock.createStrictMock(ComponentContextBridge.class);
    EasyMock.expect(compCtxBridgeMock.getEndpointDatumsForExecution())
        .andStubReturn(new HashMap<String, EndpointDatum>());
    EasyMock.replay(compCtxBridgeMock);
    compExeRelatedInstancesStub.compCtxBridge = compCtxBridgeMock;

    compExeRelatedInstancesStub.compExeScheduler = createComponentExecutionSchedulerMock(false);

    Capture<ConsoleRow.Type> consoleRowTypeCapture = new Capture<>();
    Capture<String> logMessageCapture = new Capture<>();
    ConsoleRowsSender consoleRowsSenderMock =
        createConsoleRowsSenderMock(consoleRowTypeCapture, logMessageCapture);
    compExeRelatedInstancesStub.consoleRowsSender = consoleRowsSenderMock;

    ComponentExecutionStatsService compExeStatsServiceMock =
        createComponentExecutionStatsServiceMock(compExeRelatedInstancesStub);

    final ComponentExecutor compExecutor =
        new ComponentExecutor(compExeRelatedInstancesStub, ComponentExecutionType.ProcessInputs);
    compExecutor.bindComponentExecutionPermitsService(createComponentExecutionPermitServiceMock());
    compExecutor.bindComponentExecutionStatsService(compExeStatsServiceMock);
    ComponentExecutor.waitIntervalAfterCacelledCalledMSec = WAIT_INTERVAL_100_MSEC;

    final AtomicReference<Exception> expectedExceptionRef = new AtomicReference<Exception>(null);

    final CountDownLatch executedLatch = new CountDownLatch(1);
    final Future<?> executeTask =
        ConcurrencyUtils.getAsyncTaskService()
            .submit(
                new Runnable() {

                  @Override
                  public void run() {
                    try {
                      compExecutor.executeByConsideringLimitations();
                    } catch (ComponentException | ComponentExecutionException e) {
                      expectedExceptionRef.set(e);
                    }
                    executedLatch.countDown();
                  }
                });
    processInputsCalledLatch.await();
    compExecutor.onCancelled();
    executeTask.cancel(true);

    executedLatch.await();

    assertNotNull(expectedExceptionRef.get());
    assertTrue(expectedExceptionRef.get() instanceof ComponentException);
    assertFailureHandling(
        consoleRowTypeCapture,
        logMessageCapture,
        (ComponentException) expectedExceptionRef.get(),
        "didn't terminate in time");

    assertTrue(compExeRelatedInstancesStub.compExeRelatedStates.isComponentCancelled.get());
    EasyMock.verify(compStateMachineMock);
    assertEquals(
        ComponentStateMachineEventType.RUNNING, compStateMachineEventCapture.getValue().getType());
    assertEquals(
        ComponentState.PROCESSING_INPUTS,
        compStateMachineEventCapture.getValue().getNewComponentState());
    EasyMock.verify(compExeStorageBridgeMock);
    EasyMock.verify(consoleRowsSenderMock);
  }
  /**
   * Tests canceling of {@link Component#processInputs()} in success case, that means {@link
   * Component#processInputs()} returns within expected amount of time.
   *
   * @throws ComponentExecutionException on unexpected error
   * @throws ComponentException on unexpected error
   * @throws InterruptedException on unexpected error
   * @throws ExecutionException on unexpected error
   */
  @Test(timeout = TEST_TIMEOUT_500_MSEC)
  public void testCancelProcessingInputsSuccess()
      throws ComponentExecutionException, ComponentException, InterruptedException,
          ExecutionException {
    ComponentExecutionRelatedInstances compExeRelatedInstancesStub =
        createComponentExecutionRelatedInstancesStub();

    final AtomicReference<CountDownLatch> countDownLatchRef =
        new AtomicReference<CountDownLatch>(new CountDownLatch(1));
    Component compStub =
        new ComponentDefaultStub.Default() {

          private int processInputsCount = 0;

          private int onProcessInputsInterruptedCount = 0;

          @Override
          public void processInputs() throws ComponentException {
            if (processInputsCount > 0) {
              fail("'processInputs' is expected to be called only once");
            }
            try {
              countDownLatchRef.get().await();
            } catch (InterruptedException e) {
              fail("unexpected InterruptedException");
            }
          }

          @Override
          public void onProcessInputsInterrupted(ThreadHandler executingThreadHandler) {
            if (onProcessInputsInterruptedCount > 0) {
              fail("'onProcessInputsInterrupted' is expected to be called only once");
            }
            countDownLatchRef.get().countDown();
          }
        };
    compExeRelatedInstancesStub.component.set(compStub);

    Capture<ComponentStateMachineEvent> compStateMachineEventCapture = new Capture<>();
    ComponentStateMachine compStateMachineMock =
        createComponentStateMachineEventForRuns(compStateMachineEventCapture);
    compExeRelatedInstancesStub.compStateMachine = compStateMachineMock;

    ComponentExecutionStorageBridge compExeStorageBridgeMock =
        createComponentExecutionStorageBridgeRunSuccess(compExeRelatedInstancesStub);
    compExeRelatedInstancesStub.compExeStorageBridge = compExeStorageBridgeMock;

    ComponentContextBridge compCtxBridgeMock =
        EasyMock.createStrictMock(ComponentContextBridge.class);
    EasyMock.expect(compCtxBridgeMock.getEndpointDatumsForExecution())
        .andStubReturn(new HashMap<String, EndpointDatum>());
    EasyMock.replay(compCtxBridgeMock);
    compExeRelatedInstancesStub.compCtxBridge = compCtxBridgeMock;

    compExeRelatedInstancesStub.compExeScheduler = createComponentExecutionSchedulerMock(false);

    ConsoleRowsSender consoleRowsSenderMock = createConsoleRowsSenderMock(null, null);
    compExeRelatedInstancesStub.consoleRowsSender = consoleRowsSenderMock;

    ComponentExecutionStatsService compExeStatsServiceMock =
        createComponentExecutionStatsServiceMock(compExeRelatedInstancesStub);

    final ComponentExecutor compExecutor =
        new ComponentExecutor(compExeRelatedInstancesStub, ComponentExecutionType.ProcessInputs);
    compExecutor.bindComponentExecutionPermitsService(createComponentExecutionPermitServiceMock());
    compExecutor.bindComponentExecutionStatsService(compExeStatsServiceMock);

    final int delayMsec = 100;
    ConcurrencyUtils.getAsyncTaskService()
        .scheduleAfterDelay(
            new Runnable() {

              @TaskDescription("Delayed cancel call")
              @Override
              public void run() {
                compExecutor.onCancelled();
              }
            },
            delayMsec);

    compExecutor.executeByConsideringLimitations();

    assertTrue(compExeRelatedInstancesStub.compExeRelatedStates.isComponentCancelled.get());
    EasyMock.verify(compStateMachineMock);
    assertEquals(
        ComponentStateMachineEventType.RUNNING, compStateMachineEventCapture.getValue().getType());
    assertEquals(
        ComponentState.PROCESSING_INPUTS,
        compStateMachineEventCapture.getValue().getNewComponentState());
    EasyMock.verify(compExeStorageBridgeMock);
    EasyMock.verify(consoleRowsSenderMock);
  }
 public AbstractStateMachine(S initialState) {
   this.currentState = initialState;
   this.eventQueue =
       ConcurrencyUtils.getFactory()
           .createAsyncOrderedExecutionQueue(AsyncCallbackExceptionPolicy.LOG_AND_PROCEED);
 }