private void returnCanceledRunToQueue(ExecutionState executionStateToCancel) {
    // set the context and return the run to the queue. It will be handled on "finishFlow"
    // (QueueEventListener).
    Execution executionObj =
        executionSerializationUtil.objFromBytes(executionStateToCancel.getExecutionObject());
    if (executionObj == null) {
      logger.error(
          "Run Object is null. Execution Id = "
              + executionStateToCancel.getExecutionId()
              + "; Branch Id = "
              + executionStateToCancel.getBranchId());
      return;
    }
    executionObj.getSystemContext().setFlowTerminationType(ExecutionStatus.CANCELED);
    executionObj.setPosition(null);

    // just in case - we shouldn't need it, because the Execution is back to the queue as
    // "Terminated"
    executionStateToCancel.setStatus(ExecutionStatus.PENDING_CANCEL);
    // clean the DB field
    executionStateToCancel.setExecutionObject(null);

    // return execution to queue, as "Terminated"
    queueDispatcherService.dispatch(
        String.valueOf(executionObj.getExecutionId()),
        executionObj.getGroupName(),
        ExecStatus.TERMINATED,
        executionMessageConverter.createPayload(executionObj));
  }
 private ExecutionState createExecutionState(ExecutionStatus status) {
   ExecutionState executionState = new ExecutionState();
   executionState.setStatus(status);
   executionState.setExecutionId(123L);
   executionState.setBranchId(UUID.randomUUID().toString());
   executionStateRepository.saveAndFlush(executionState);
   return executionState;
 }
  @Test
  public void testFindExecutionIdByStatuses() {
    ExecutionState canceledExecutionState = createExecutionState(ExecutionStatus.CANCELED);
    ExecutionState completedExecutionState = createExecutionState(ExecutionStatus.COMPLETED);
    createExecutionState(ExecutionStatus.PENDING_CANCEL);

    List<Long> executionStates =
        executionStateRepository.findExecutionIdByStatuses(
            Arrays.asList(ExecutionStatus.CANCELED, ExecutionStatus.COMPLETED));

    assertThat(executionStates)
        .containsExactly(
            canceledExecutionState.getExecutionId(), completedExecutionState.getExecutionId());
  }
  @Override
  @Transactional
  public ExecutionActionResult requestCancelExecution(Long executionId) {
    if (logger.isDebugEnabled()) {
      logger.debug("Cancelling Execution Id: " + executionId);
    }
    if (executionId == null) {
      throw new IllegalArgumentException(
          "Null is not allowed as input of execution id in cancelExecution()");
    }

    ExecutionState executionStateToCancel =
        executionStateService.readByExecutionIdAndBranchId(executionId, EMPTY_BRANCH);
    // we must have such execution in the table - the record is created on execution triggering
    if (executionStateToCancel == null) {
      String errMsg = "Failed to cancel execution. Execution id: " + executionId + ".";
      logger.error(errMsg);
      return ExecutionActionResult.FAILED_NOT_FOUND;
    }

    ExecutionStatus status = executionStateToCancel.getStatus();

    if (status.equals(ExecutionStatus.CANCELED) || status.equals(ExecutionStatus.PENDING_CANCEL)) {
      return ExecutionActionResult.FAILED_ALREADY_CANCELED_OR_PENDING_CANCELLATION;
    }

    // it's possible to cancel only running or paused executions.
    // If it's running - set to pending-cancel, and the ExecutionServiceImpl will handle it and
    // extract it from the queue.
    // If it's paused - sometimes needs to handle its branches (if such exists).
    if (status.equals(ExecutionStatus.RUNNING)) {
      executionStateToCancel.setStatus(ExecutionStatus.PENDING_CANCEL);
    } else if (status.equals(ExecutionStatus.PAUSED)) {
      cancelPausedRun(executionStateToCancel);
    } else {
      String errMsg =
          "Failed to cancel execution. Execution id: "
              + executionId
              + ". Execution is in status: "
              + executionStateToCancel.getStatus().name();
      logger.error(errMsg);
      return ExecutionActionResult.getExecutionActionResult(status);
    }

    return ExecutionActionResult.SUCCESS;
  }
  // Cancel paused run according to its branches state
  //      If the run has branches, (it can be branch-paused / user-paused / no-workers-in-group) -
  // then it's a 'virtual' pause, and we should cancel the paused branches. Then, the run itself
  // will be canceled as well.
  //      If it doesn't - just cancel it straight away - extract the Run Object, set its context
  // accordingly and put into the queue.
  private void cancelPausedRun(ExecutionState executionStateToCancel) {
    final List<ExecutionState> branches =
        executionStateService.readByExecutionId(executionStateToCancel.getExecutionId());

    // If the parent is paused because one of the branches is paused, OR, it was paused by the user
    // / no-workers-in-group, but has branches that were not finished (and thus, were paused) -
    // The parent itself will return to the queue after all the branches are ended (due to this
    // cancellation), and then it'll be canceled as well.
    if (branches.size()
        > 1) { // more than 1 means that it has paused branches (branches is at least 1 - the
               // parent)
      for (ExecutionState branch : branches) {
        if (!EMPTY_BRANCH.equals(branch.getBranchId())) { // exclude the base execution
          returnCanceledRunToQueue(branch);
          executionStateService.deleteExecutionState(branch.getExecutionId(), branch.getBranchId());
        }
      }
      executionStateToCancel.setStatus(
          ExecutionStatus
              .PENDING_CANCEL); // when the parent will return to queue - should have the correct
                                // status
    } else {
      returnCanceledRunToQueue(executionStateToCancel);
    }
  }