content: /*
   * Copyright 2010-2025 Hyland Software, Inc. and its affiliates.
   *
   * Licensed 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.activiti.engine.impl.persistence.entity;
  
  import java.util.ArrayList;
  import java.util.Calendar;
  import java.util.Collection;
  import java.util.Date;
  import java.util.GregorianCalendar;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Optional;
  import org.activiti.bpmn.model.HasExecutionListeners;
  import org.activiti.bpmn.model.Process;
  import org.activiti.bpmn.model.SequenceFlow;
  import org.activiti.bpmn.model.UserTask;
  import org.activiti.engine.ActivitiObjectNotFoundException;
  import org.activiti.engine.delegate.ExecutionListener;
  import org.activiti.engine.delegate.event.ActivitiEventType;
  import org.activiti.engine.delegate.event.ActivitiProcessCancelledEvent;
  import org.activiti.engine.delegate.event.impl.ActivitiEventBuilder;
  import org.activiti.engine.history.DeleteReason;
  import org.activiti.engine.impl.ExecutionQueryImpl;
  import org.activiti.engine.impl.Page;
  import org.activiti.engine.impl.ProcessInstanceQueryImpl;
  import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
  import org.activiti.engine.impl.context.Context;
  import org.activiti.engine.impl.identity.Authentication;
  import org.activiti.engine.impl.persistence.CountingExecutionEntity;
  import org.activiti.engine.impl.persistence.entity.data.DataManager;
  import org.activiti.engine.impl.persistence.entity.data.ExecutionDataManager;
  import org.activiti.engine.impl.util.CollectionUtil;
  import org.activiti.engine.impl.util.ProcessDefinitionUtil;
  import org.activiti.engine.repository.ProcessDefinition;
  import org.activiti.engine.runtime.Execution;
  import org.activiti.engine.runtime.ProcessInstance;
  import org.activiti.engine.task.IdentityLinkType;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
  
  
   */
  public class ExecutionEntityManagerImpl
      extends AbstractEntityManager<ExecutionEntity>
      implements ExecutionEntityManager {
  
      private static final Logger logger = LoggerFactory.getLogger(ExecutionEntityManagerImpl.class);
  
      protected ExecutionDataManager executionDataManager;
  
      public ExecutionEntityManagerImpl(
          ProcessEngineConfigurationImpl processEngineConfiguration,
          ExecutionDataManager executionDataManager
      ) {
          super(processEngineConfiguration);
          this.executionDataManager = executionDataManager;
      }
  
      @Override
      protected DataManager<ExecutionEntity> getDataManager() {
          return executionDataManager;
      }
  
      // Overriding the default delete methods to set the 'isDeleted' flag
  
      @Override
      public void delete(ExecutionEntity entity) {
          delete(entity, true);
      }
  
      @Override
      public void delete(ExecutionEntity entity, boolean fireDeleteEvent) {
          super.delete(entity, fireDeleteEvent);
          entity.setDeleted(true);
      }
  
      // FIND METHODS
  
      @Override
      public ExecutionEntity findSubProcessInstanceBySuperExecutionId(String superExecutionId) {
          return executionDataManager.findSubProcessInstanceBySuperExecutionId(superExecutionId);
      }
  
      @Override
      public List<ExecutionEntity> findChildExecutionsByParentExecutionId(String parentExecutionId) {
          return executionDataManager.findChildExecutionsByParentExecutionId(parentExecutionId);
      }
  
      @Override
      public List<ExecutionEntity> findChildExecutionsByProcessInstanceId(String processInstanceId) {
          return executionDataManager.findChildExecutionsByProcessInstanceId(processInstanceId);
      }
  
      @Override
      public List<ExecutionEntity> findExecutionsByParentExecutionAndActivityIds(
          final String parentExecutionId,
          final Collection<String> activityIds
      ) {
          return executionDataManager.findExecutionsByParentExecutionAndActivityIds(parentExecutionId, activityIds);
      }
  
      @Override
      public long findExecutionCountByQueryCriteria(ExecutionQueryImpl executionQuery) {
          return executionDataManager.findExecutionCountByQueryCriteria(executionQuery);
      }
  
      @Override
      public List<ExecutionEntity> findExecutionsByQueryCriteria(ExecutionQueryImpl executionQuery, Page page) {
          return executionDataManager.findExecutionsByQueryCriteria(executionQuery, page);
      }
  
      @Override
      public long findProcessInstanceCountByQueryCriteria(ProcessInstanceQueryImpl executionQuery) {
          return executionDataManager.findProcessInstanceCountByQueryCriteria(executionQuery);
      }
  
      @Override
      public List<ProcessInstance> findProcessInstanceByQueryCriteria(ProcessInstanceQueryImpl executionQuery) {
          return executionDataManager.findProcessInstanceByQueryCriteria(executionQuery);
      }
  
      @Override
      public ExecutionEntity findByRootProcessInstanceId(String rootProcessInstanceId) {
          List<ExecutionEntity> executions = executionDataManager.findExecutionsByRootProcessInstanceId(
              rootProcessInstanceId
          );
          return processExecutionTree(rootProcessInstanceId, executions);
      }
  
      /**
       * Processes a collection of {@link ExecutionEntity} instances, which form on execution tree.
       * All the executions share the same rootProcessInstanceId (which is provided).
       * The return value will be the root {@link ExecutionEntity} instance, with all child {@link ExecutionEntity}
       * instances populated and set using the {@link ExecutionEntity} instances from the provided collections
       */
      protected ExecutionEntity processExecutionTree(String rootProcessInstanceId, List<ExecutionEntity> executions) {
          ExecutionEntity rootExecution = null;
  
          // Collect executions
          Map<String, ExecutionEntity> executionMap = new HashMap<String, ExecutionEntity>(executions.size());
          for (ExecutionEntity executionEntity : executions) {
              if (executionEntity.getId().equals(rootProcessInstanceId)) {
                  rootExecution = executionEntity;
              }
              executionMap.put(executionEntity.getId(), executionEntity);
          }
  
          // Set relationships
          for (ExecutionEntity executionEntity : executions) {
              // Root process instance relationship
              if (executionEntity.getRootProcessInstanceId() != null) {
                  executionEntity.setRootProcessInstance(executionMap.get(executionEntity.getRootProcessInstanceId()));
              }
  
              // Process instance relationship
              if (executionEntity.getProcessInstanceId() != null) {
                  executionEntity.setProcessInstance(executionMap.get(executionEntity.getProcessInstanceId()));
              }
  
              // Parent - child relationship
              if (executionEntity.getParentId() != null) {
                  ExecutionEntity parentExecutionEntity = executionMap.get(executionEntity.getParentId());
                  executionEntity.setParent(parentExecutionEntity);
                  parentExecutionEntity.addChildExecution(executionEntity);
              }
  
              // Super - sub execution relationship
              if (executionEntity.getSuperExecution() != null) {
                  ExecutionEntity superExecutionEntity = executionMap.get(executionEntity.getSuperExecutionId());
                  executionEntity.setSuperExecution(superExecutionEntity);
                  superExecutionEntity.setSubProcessInstance(executionEntity);
              }
          }
          return rootExecution;
      }
  
      @Override
      public List<ProcessInstance> findProcessInstanceAndVariablesByQueryCriteria(
          ProcessInstanceQueryImpl executionQuery
      ) {
          return executionDataManager.findProcessInstanceAndVariablesByQueryCriteria(executionQuery);
      }
  
      @Override
      public Collection<ExecutionEntity> findInactiveExecutionsByProcessInstanceId(final String processInstanceId) {
          return executionDataManager.findInactiveExecutionsByProcessInstanceId(processInstanceId);
      }
  
      @Override
      public Collection<ExecutionEntity> findInactiveExecutionsByActivityIdAndProcessInstanceId(
          final String activityId,
          final String processInstanceId
      ) {
          return executionDataManager.findInactiveExecutionsByActivityIdAndProcessInstanceId(
              activityId,
              processInstanceId
          );
      }
  
      @Override
      public List<Execution> findExecutionsByNativeQuery(
          Map<String, Object> parameterMap,
          int firstResult,
          int maxResults
      ) {
          return executionDataManager.findExecutionsByNativeQuery(parameterMap, firstResult, maxResults);
      }
  
      @Override
      public List<ProcessInstance> findProcessInstanceByNativeQuery(
          Map<String, Object> parameterMap,
          int firstResult,
          int maxResults
      ) {
          return executionDataManager.findProcessInstanceByNativeQuery(parameterMap, firstResult, maxResults);
      }
  
      @Override
      public long findExecutionCountByNativeQuery(Map<String, Object> parameterMap) {
          return executionDataManager.findExecutionCountByNativeQuery(parameterMap);
      }
  
      // CREATE METHODS
  
      @Override
      public ExecutionEntity createProcessInstanceExecution(
          ProcessDefinition processDefinition,
          String businessKey,
          String tenantId,
          String initiatorVariableName
      ) {
          ExecutionEntity processInstanceExecution = executionDataManager.create();
  
          if (isExecutionRelatedEntityCountEnabledGlobally()) {
              ((CountingExecutionEntity) processInstanceExecution).setCountEnabled(true);
          }
  
          processInstanceExecution.setProcessDefinitionId(processDefinition.getId());
          processInstanceExecution.setProcessDefinitionKey(processDefinition.getKey());
          processInstanceExecution.setProcessDefinitionName(processDefinition.getName());
          processInstanceExecution.setProcessDefinitionVersion(processDefinition.getVersion());
          processInstanceExecution.setAppVersion(processDefinition.getAppVersion());
          processInstanceExecution.setBusinessKey(businessKey);
          processInstanceExecution.setScope(true); // process instance is always a scope for all child executions
  
          // Inherit tenant id (if any)
          if (tenantId != null) {
              processInstanceExecution.setTenantId(tenantId);
          }
  
          String authenticatedUserId = Authentication.getAuthenticatedUserId();
  
          processInstanceExecution.setStartUserId(authenticatedUserId);
  
          // Store in database
          insert(processInstanceExecution, false);
  
          if (initiatorVariableName != null) {
              processInstanceExecution.setVariable(initiatorVariableName, authenticatedUserId);
          }
  
          // Need to be after insert, cause we need the id
          processInstanceExecution.setProcessInstanceId(processInstanceExecution.getId());
          processInstanceExecution.setRootProcessInstanceId(processInstanceExecution.getId());
          if (authenticatedUserId != null) {
              getIdentityLinkEntityManager().addIdentityLink(
                  processInstanceExecution,
                  authenticatedUserId,
                  null,
                  IdentityLinkType.STARTER
              );
          }
  
          // Fire events
          if (getEventDispatcher().isEnabled()) {
              getEventDispatcher().dispatchEvent(
                  ActivitiEventBuilder.createEntityEvent(ActivitiEventType.ENTITY_CREATED, processInstanceExecution)
              );
          }
  
          return processInstanceExecution;
      }
  
      public ExecutionEntity updateProcessInstanceStartDate(ExecutionEntity processInstanceExecution) {
          processInstanceExecution.setStartTime(Context.getProcessEngineConfiguration().getClock().getCurrentTime());
          return update(processInstanceExecution);
      }
  
      /**
       * Creates a new execution. properties processDefinition, processInstance and activity will be initialized.
       */
      @Override
      public ExecutionEntity createChildExecution(ExecutionEntity parentExecutionEntity) {
          ExecutionEntity childExecution = executionDataManager.create();
          inheritCommonProperties(parentExecutionEntity, childExecution);
          childExecution.setParent(parentExecutionEntity);
          childExecution.setProcessDefinitionId(parentExecutionEntity.getProcessDefinitionId());
          childExecution.setProcessDefinitionKey(parentExecutionEntity.getProcessDefinitionKey());
          childExecution.setProcessInstanceId(
              parentExecutionEntity.getProcessInstanceId() != null
                  ? parentExecutionEntity.getProcessInstanceId()
                  : parentExecutionEntity.getId()
          );
          childExecution.setParentProcessInstanceId(parentExecutionEntity.getParentProcessInstanceId());
          childExecution.setAppVersion(parentExecutionEntity.getAppVersion());
          childExecution.setScope(false);
  
          // manage the bidirectional parent-child relation
          parentExecutionEntity.addChildExecution(childExecution);
  
          // Insert the child execution
          insert(childExecution, false);
  
          if (logger.isDebugEnabled()) {
              logger.debug("Child execution {} created with parent {}", childExecution, parentExecutionEntity.getId());
          }
  
          if (getEventDispatcher().isEnabled()) {
              getEventDispatcher().dispatchEvent(
                  ActivitiEventBuilder.createEntityEvent(ActivitiEventType.ENTITY_CREATED, childExecution)
              );
              getEventDispatcher().dispatchEvent(
                  ActivitiEventBuilder.createEntityEvent(ActivitiEventType.ENTITY_INITIALIZED, childExecution)
              );
          }
  
          return childExecution;
      }
  
      private String getStartUserId(ExecutionEntity executionEntity) {
          return Optional.ofNullable(executionEntity)
              .map(ExecutionEntity::getStartUserId)
              .or(() -> Optional.ofNullable(executionEntity).map(ExecutionEntity::getParent).map(this::getStartUserId))
              .orElse(null);
      }
  
      @Override
      public ExecutionEntity createSubprocessInstance(
          ProcessDefinition processDefinition,
          ExecutionEntity superExecutionEntity,
          String businessKey
      ) {
          ExecutionEntity subProcessInstance = executionDataManager.create();
          inheritCommonProperties(superExecutionEntity, subProcessInstance);
          subProcessInstance.setProcessDefinitionId(processDefinition.getId());
          subProcessInstance.setProcessDefinitionKey(processDefinition.getKey());
          subProcessInstance.setProcessDefinitionName(processDefinition.getName());
          subProcessInstance.setProcessDefinitionVersion(processDefinition.getVersion());
          subProcessInstance.setName(superExecutionEntity.getProcessInstance().getName());
          subProcessInstance.setSuperExecution(superExecutionEntity);
          subProcessInstance.setRootProcessInstanceId(superExecutionEntity.getRootProcessInstanceId());
          subProcessInstance.setScope(true); // process instance is always a scope for all child executions
          subProcessInstance.setBusinessKey(businessKey);
          subProcessInstance.setAppVersion(processDefinition.getAppVersion());
          String authenticatedUserId = Optional.ofNullable(Authentication.getAuthenticatedUserId()).orElseGet(() ->
              getStartUserId(superExecutionEntity)
          );
          subProcessInstance.setStartUserId(authenticatedUserId);
  
          // Store in database
          insert(subProcessInstance, false);
  
          if (authenticatedUserId != null) {
              getIdentityLinkEntityManager().addIdentityLink(
                  subProcessInstance,
                  authenticatedUserId,
                  null,
                  IdentityLinkType.STARTER
              );
          }
  
          if (logger.isDebugEnabled()) {
              logger.debug(
                  "Child execution {} created with super execution {}",
                  subProcessInstance,
                  superExecutionEntity.getId()
              );
          }
  
          subProcessInstance.setProcessInstanceId(subProcessInstance.getId());
          superExecutionEntity.setSubProcessInstance(subProcessInstance);
  
          if (
              Context.getProcessEngineConfiguration() != null &&
              Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled()
          ) {
              Context.getProcessEngineConfiguration()
                  .getEventDispatcher()
                  .dispatchEvent(
                      ActivitiEventBuilder.createEntityEvent(ActivitiEventType.ENTITY_CREATED, subProcessInstance)
                  );
          }
  
          return subProcessInstance;
      }
  
      protected void inheritCommonProperties(ExecutionEntity parentExecutionEntity, ExecutionEntity childExecution) {
          // Inherits the 'count' feature from the parent.
          // If the parent was not 'counting', we can't make the child 'counting' again.
          if (parentExecutionEntity instanceof CountingExecutionEntity) {
              CountingExecutionEntity countingParentExecutionEntity = (CountingExecutionEntity) parentExecutionEntity;
              ((CountingExecutionEntity) childExecution).setCountEnabled(countingParentExecutionEntity.isCountEnabled());
          }
  
          childExecution.setRootProcessInstanceId(parentExecutionEntity.getRootProcessInstanceId());
          childExecution.setActive(true);
          childExecution.setStartTime(processEngineConfiguration.getClock().getCurrentTime());
  
          if (parentExecutionEntity.getTenantId() != null) {
              childExecution.setTenantId(parentExecutionEntity.getTenantId());
          }
      }
  
      // UPDATE METHODS
  
      @Override
      public void updateExecutionTenantIdForDeployment(String deploymentId, String newTenantId) {
          executionDataManager.updateExecutionTenantIdForDeployment(deploymentId, newTenantId);
      }
  
      // DELETE METHODS
  
      @Override
      public void deleteProcessInstancesByProcessDefinition(
          String processDefinitionId,
          String deleteReason,
          boolean cascade
      ) {
          List<String> processInstanceIds = executionDataManager.findProcessInstanceIdsByProcessDefinitionId(
              processDefinitionId
          );
  
          for (String processInstanceId : processInstanceIds) {
              deleteProcessInstance(processInstanceId, deleteReason, cascade);
          }
  
          if (cascade) {
              getHistoricProcessInstanceEntityManager().deleteHistoricProcessInstanceByProcessDefinitionId(
                  processDefinitionId
              );
          }
      }
  
      @Override
      public void deleteProcessInstance(String processInstanceId, String deleteReason, boolean cascade) {
          ExecutionEntity execution = findById(processInstanceId);
  
          if (execution == null) {
              throw new ActivitiObjectNotFoundException(
                  "No process instance found for id '" + processInstanceId + "'",
                  ProcessInstance.class
              );
          }
  
          deleteProcessInstanceCascade(execution, deleteReason, cascade);
      }
  
      protected void deleteProcessInstanceCascade(ExecutionEntity execution, String deleteReason, boolean deleteHistory) {
          // fill default reason if none provided
          if (deleteReason == null) {
              deleteReason = DeleteReason.PROCESS_INSTANCE_DELETED;
          }
  
          for (ExecutionEntity subExecutionEntity : execution.getExecutions()) {
              if (subExecutionEntity.isMultiInstanceRoot()) {
                  for (ExecutionEntity miExecutionEntity : subExecutionEntity.getExecutions()) {
                      if (miExecutionEntity.getSubProcessInstance() != null) {
                          deleteProcessInstanceCascade(
                              miExecutionEntity.getSubProcessInstance(),
                              deleteReason,
                              deleteHistory
                          );
                      } else {
                          for (ExecutionEntity miSubExecutionEntity : miExecutionEntity.getExecutions()) {
                              if (miSubExecutionEntity.getSubProcessInstance() != null) {
                                  deleteProcessInstanceCascade(
                                      miSubExecutionEntity.getSubProcessInstance(),
                                      deleteReason,
                                      deleteHistory
                                  );
                              }
                          }
                      }
                  }
              } else if (subExecutionEntity.getSubProcessInstance() != null) {
                  deleteProcessInstanceCascade(subExecutionEntity.getSubProcessInstance(), deleteReason, deleteHistory);
              }
          }
  
          getTaskEntityManager().deleteTasksByProcessInstanceId(execution.getId(), deleteReason, deleteHistory);
  
          dispatchProcessCancelledEvent(execution, deleteReason);
  
          // delete the execution BEFORE we delete the history, otherwise we will
          // produce orphan HistoricVariableInstance instances
  
          ExecutionEntity processInstanceExecutionEntity = execution.getProcessInstance();
          if (processInstanceExecutionEntity == null) {
              return;
          }
  
          // Execute execution listeners for process end.
          Process process = ProcessDefinitionUtil.getProcess(processInstanceExecutionEntity.getProcessDefinitionId());
          if (process != null && CollectionUtil.isNotEmpty(process.getExecutionListeners())) {
              executeExecutionListeners(process, processInstanceExecutionEntity, ExecutionListener.EVENTNAME_END);
          }
  
          List<ExecutionEntity> childExecutions = collectChildren(execution.getProcessInstance());
          for (int i = childExecutions.size() - 1; i >= 0; i--) {
              ExecutionEntity childExecutionEntity = childExecutions.get(i);
              deleteExecutionAndRelatedData(childExecutionEntity, deleteReason);
          }
  
          deleteExecutionAndRelatedData(execution, deleteReason);
  
          if (deleteHistory) {
              getHistoricProcessInstanceEntityManager().delete(execution.getId());
          }
  
          getHistoryManager().recordProcessInstanceEnd(processInstanceExecutionEntity.getId(), deleteReason, null);
          processInstanceExecutionEntity.setDeleted(true);
      }
  
      private void dispatchProcessCancelledEvent(ExecutionEntity execution, String deleteReason) {
          if (getEventDispatcher().isEnabled()) {
              ProcessInstance processInstance = execution.getProcessInstance();
              ActivitiProcessCancelledEvent processCancelledEvent = ActivitiEventBuilder.createProcessCancelledEvent(
                  processInstance,
                  deleteReason
              );
              getEventDispatcher().dispatchEvent(processCancelledEvent);
          }
      }
  
      /**
       * Executes the execution listeners defined on the given element, with the given event type,
       * and passing the provided execution to the {@link ExecutionListener} instances.
       */
      protected void executeExecutionListeners(
          HasExecutionListeners elementWithExecutionListeners,
          ExecutionEntity executionEntity,
          String eventType
      ) {
          getProcessEngineConfiguration()
              .getListenerNotificationHelper()
              .executeExecutionListeners(elementWithExecutionListeners, executionEntity, eventType);
      }
  
      @Override
      public void deleteExecutionAndRelatedData(ExecutionEntity executionEntity, String deleteReason) {
          getHistoryManager().recordActivityEnd(executionEntity, deleteReason);
          deleteDataForExecution(executionEntity, deleteReason);
          delete(executionEntity);
      }
  
      @Override
      public void cancelExecutionAndRelatedData(ExecutionEntity executionEntity, String deleteReason) {
          getHistoryManager().recordActivityEnd(executionEntity, deleteReason);
          cancelDataForExecution(executionEntity, deleteReason);
          delete(executionEntity);
      }
  
      @Override
      public void deleteProcessInstanceExecutionEntity(
          String processInstanceId,
          String currentFlowElementId,
          String deleteReason,
          boolean cascade,
          boolean cancel
      ) {
          ExecutionEntity processInstanceEntity = findById(processInstanceId);
  
          if (processInstanceEntity == null) {
              throw new ActivitiObjectNotFoundException(
                  "No process instance found for id '" + processInstanceId + "'",
                  ProcessInstance.class
              );
          }
  
          if (processInstanceEntity.isDeleted()) {
              return;
          }
  
          // Call activities
          for (ExecutionEntity subExecutionEntity : processInstanceEntity.getExecutions()) {
              if (subExecutionEntity.getSubProcessInstance() != null && !subExecutionEntity.isEnded()) {
                  deleteProcessInstanceCascade(subExecutionEntity.getSubProcessInstance(), deleteReason, cascade);
              }
          }
  
          // delete event scope executions
          for (ExecutionEntity childExecution : processInstanceEntity.getExecutions()) {
              if (childExecution.isEventScope()) {
                  deleteExecutionAndRelatedData(childExecution, null);
              }
          }
  
          if (cancel) {
              cancelChildExecutions(processInstanceEntity, deleteReason);
              cancelExecutionAndRelatedData(processInstanceEntity, deleteReason);
          } else {
              deleteChildExecutions(processInstanceEntity, deleteReason);
              deleteExecutionAndRelatedData(processInstanceEntity, deleteReason);
          }
  
          if (getEventDispatcher().isEnabled()) {
              if (!cancel) {
                  getEventDispatcher().dispatchEvent(
                      ActivitiEventBuilder.createEntityEvent(ActivitiEventType.PROCESS_COMPLETED, processInstanceEntity)
                  );
              } else {
                  getEventDispatcher().dispatchEvent(
                      ActivitiEventBuilder.createProcessCancelledEvent(processInstanceEntity, deleteReason)
                  );
              }
          }
  
          // TODO: what about delete reason?
          getHistoryManager().recordProcessInstanceEnd(processInstanceEntity.getId(), deleteReason, currentFlowElementId);
          processInstanceEntity.setDeleted(true);
      }
  
      @Override
      public void deleteChildExecutions(ExecutionEntity executionEntity, String deleteReason) {
          // The children of an execution for a tree. For correct deletions
          // (taking care of foreign keys between child-parent)
          // the leafs of this tree must be deleted first before the parents elements.
  
          List<? extends ExecutionEntity> childExecutions = collectChildren(executionEntity);
          for (int i = childExecutions.size() - 1; i >= 0; i--) {
              ExecutionEntity childExecutionEntity = childExecutions.get(i);
              if (!childExecutionEntity.isEnded()) {
                  deleteExecutionAndRelatedData(childExecutionEntity, deleteReason);
              }
          }
      }
  
      @Override
      public void cancelChildExecutions(ExecutionEntity executionEntity, String deleteReason) {
          // The children of an execution for a tree. For correct deletions
          // (taking care of foreign keys between child-parent)
          // the leafs of this tree must be deleted first before the parents elements.
  
          List<? extends ExecutionEntity> childExecutions = collectChildren(executionEntity);
          for (int i = childExecutions.size() - 1; i >= 0; i--) {
              ExecutionEntity childExecutionEntity = childExecutions.get(i);
              if (!childExecutionEntity.isEnded()) {
                  cancelExecutionAndRelatedData(childExecutionEntity, deleteReason);
              }
          }
      }
  
      public List<ExecutionEntity> collectChildren(ExecutionEntity executionEntity) {
          List<ExecutionEntity> childExecutions = new ArrayList<ExecutionEntity>();
          collectChildren(executionEntity, childExecutions);
          return childExecutions;
      }
  
      protected void collectChildren(ExecutionEntity executionEntity, List<ExecutionEntity> collectedChildExecution) {
          List<ExecutionEntity> childExecutions = (List<ExecutionEntity>) executionEntity.getExecutions();
          if (childExecutions != null && childExecutions.size() > 0) {
              for (ExecutionEntity childExecution : childExecutions) {
                  if (!childExecution.isDeleted()) {
                      collectedChildExecution.add(childExecution);
                      collectChildren(childExecution, collectedChildExecution);
                  }
              }
          }
  
          ExecutionEntity subProcessInstance = executionEntity.getSubProcessInstance();
          if (subProcessInstance != null && !subProcessInstance.isDeleted()) {
              collectedChildExecution.add(subProcessInstance);
              collectChildren(subProcessInstance, collectedChildExecution);
          }
      }
  
      @Override
      public ExecutionEntity findFirstScope(ExecutionEntity executionEntity) {
          ExecutionEntity currentExecutionEntity = executionEntity;
          while (currentExecutionEntity != null) {
              if (currentExecutionEntity.isScope()) {
                  return currentExecutionEntity;
              }
  
              ExecutionEntity parentExecutionEntity = currentExecutionEntity.getParent();
              if (parentExecutionEntity == null) {
                  parentExecutionEntity = currentExecutionEntity.getSuperExecution();
              }
              currentExecutionEntity = parentExecutionEntity;
          }
          return null;
      }
  
      @Override
      public ExecutionEntity findFirstMultiInstanceRoot(ExecutionEntity executionEntity) {
          ExecutionEntity currentExecutionEntity = executionEntity;
          while (currentExecutionEntity != null) {
              if (currentExecutionEntity.isMultiInstanceRoot()) {
                  return currentExecutionEntity;
              }
  
              ExecutionEntity parentExecutionEntity = currentExecutionEntity.getParent();
              if (parentExecutionEntity == null) {
                  parentExecutionEntity = currentExecutionEntity.getSuperExecution();
              }
              currentExecutionEntity = parentExecutionEntity;
          }
          return null;
      }
  
      private void deleteExecutionEntity(ExecutionEntity executionEntity, String deleteReason) {
          // To start, deactivate the current incoming execution
          executionEntity.setEnded(true);
          executionEntity.setActive(false);
  
          boolean enableExecutionRelationshipCounts = isExecutionRelatedEntityCountEnabled(executionEntity);
  
          if (
              executionEntity.getId().equals(executionEntity.getProcessInstanceId()) &&
              (!enableExecutionRelationshipCounts ||
                  (enableExecutionRelationshipCounts &&
                      ((CountingExecutionEntity) executionEntity).getIdentityLinkCount() > 0))
          ) {
              IdentityLinkEntityManager identityLinkEntityManager = getIdentityLinkEntityManager();
              Collection<IdentityLinkEntity> identityLinks =
                  identityLinkEntityManager.findIdentityLinksByProcessInstanceId(executionEntity.getProcessInstanceId());
              for (IdentityLinkEntity identityLink : identityLinks) {
                  identityLinkEntityManager.delete(identityLink);
              }
          }
  
          // Get variables related to execution and delete them
          if (
              !enableExecutionRelationshipCounts ||
              (enableExecutionRelationshipCounts && ((CountingExecutionEntity) executionEntity).getVariableCount() > 0)
          ) {
              Collection<VariableInstance> executionVariables = executionEntity.getVariableInstancesLocal().values();
              for (VariableInstance variableInstance : executionVariables) {
                  if (variableInstance instanceof VariableInstanceEntity) {
                      VariableInstanceEntity variableInstanceEntity = (VariableInstanceEntity) variableInstance;
  
                      VariableInstanceEntityManager variableInstanceEntityManager = getVariableInstanceEntityManager();
                      variableInstanceEntityManager.delete(variableInstanceEntity);
                      if (
                          variableInstanceEntity.getByteArrayRef() != null &&
                          variableInstanceEntity.getByteArrayRef().getId() != null
                      ) {
                          getByteArrayEntityManager().deleteByteArrayById(
                              variableInstanceEntity.getByteArrayRef().getId()
                          );
                      }
                  }
              }
          }
  
          // Delete jobs
  
          if (
              !enableExecutionRelationshipCounts ||
              (enableExecutionRelationshipCounts && ((CountingExecutionEntity) executionEntity).getTimerJobCount() > 0)
          ) {
              TimerJobEntityManager timerJobEntityManager = getTimerJobEntityManager();
              Collection<TimerJobEntity> timerJobsForExecution = timerJobEntityManager.findJobsByExecutionId(
                  executionEntity.getId()
              );
              for (TimerJobEntity job : timerJobsForExecution) {
                  timerJobEntityManager.delete(job);
                  if (getEventDispatcher().isEnabled()) {
                      getEventDispatcher().dispatchEvent(
                          ActivitiEventBuilder.createEntityEvent(ActivitiEventType.JOB_CANCELED, job)
                      );
                  }
              }
          }
  
          if (
              !enableExecutionRelationshipCounts ||
              (enableExecutionRelationshipCounts && ((CountingExecutionEntity) executionEntity).getJobCount() > 0)
          ) {
              JobEntityManager jobEntityManager = getJobEntityManager();
              Collection<JobEntity> jobsForExecution = jobEntityManager.findJobsByExecutionId(executionEntity.getId());
              for (JobEntity job : jobsForExecution) {
                  getJobEntityManager().delete(job);
                  if (getEventDispatcher().isEnabled()) {
                      getEventDispatcher().dispatchEvent(
                          ActivitiEventBuilder.createEntityEvent(ActivitiEventType.JOB_CANCELED, job)
                      );
                  }
              }
          }
  
          if (
              !enableExecutionRelationshipCounts ||
              (enableExecutionRelationshipCounts &&
                  ((CountingExecutionEntity) executionEntity).getSuspendedJobCount() > 0)
          ) {
              SuspendedJobEntityManager suspendedJobEntityManager = getSuspendedJobEntityManager();
              Collection<SuspendedJobEntity> suspendedJobsForExecution = suspendedJobEntityManager.findJobsByExecutionId(
                  executionEntity.getId()
              );
              for (SuspendedJobEntity job : suspendedJobsForExecution) {
                  suspendedJobEntityManager.delete(job);
                  if (getEventDispatcher().isEnabled()) {
                      getEventDispatcher().dispatchEvent(
                          ActivitiEventBuilder.createEntityEvent(ActivitiEventType.JOB_CANCELED, job)
                      );
                  }
              }
          }
  
          if (
              !enableExecutionRelationshipCounts ||
              (enableExecutionRelationshipCounts &&
                  ((CountingExecutionEntity) executionEntity).getDeadLetterJobCount() > 0)
          ) {
              DeadLetterJobEntityManager deadLetterJobEntityManager = getDeadLetterJobEntityManager();
              Collection<DeadLetterJobEntity> deadLetterJobsForExecution =
                  deadLetterJobEntityManager.findJobsByExecutionId(executionEntity.getId());
              for (DeadLetterJobEntity job : deadLetterJobsForExecution) {
                  deadLetterJobEntityManager.delete(job);
                  if (getEventDispatcher().isEnabled()) {
                      getEventDispatcher().dispatchEvent(
                          ActivitiEventBuilder.createEntityEvent(ActivitiEventType.JOB_CANCELED, job)
                      );
                  }
              }
          }
  
          // Delete event subscriptions
          if (
              !enableExecutionRelationshipCounts ||
              (enableExecutionRelationshipCounts &&
                  ((CountingExecutionEntity) executionEntity).getEventSubscriptionCount() > 0)
          ) {
              EventSubscriptionEntityManager eventSubscriptionEntityManager = getEventSubscriptionEntityManager();
              List<EventSubscriptionEntity> eventSubscriptions =
                  eventSubscriptionEntityManager.findEventSubscriptionsByExecution(executionEntity.getId());
              for (EventSubscriptionEntity eventSubscription : eventSubscriptions) {
                  eventSubscriptionEntityManager.delete(eventSubscription);
              }
          }
      }
  
      private void deleteUserTask(ExecutionEntity executionEntity, String deleteReason) {
          boolean enableExecutionRelationshipCounts = isExecutionRelatedEntityCountEnabled(executionEntity);
  
          if (
              !enableExecutionRelationshipCounts ||
              (enableExecutionRelationshipCounts && ((CountingExecutionEntity) executionEntity).getTaskCount() > 0)
          ) {
              TaskEntityManager taskEntityManager = getTaskEntityManager();
              Collection<TaskEntity> tasksForExecution = taskEntityManager.findTasksByExecutionId(
                  executionEntity.getId()
              );
              for (TaskEntity taskEntity : tasksForExecution) {
                  taskEntityManager.deleteTask(taskEntity, deleteReason, false, false);
              }
          }
      }
  
      private void cancelUserTask(ExecutionEntity executionEntity, String deleteReason) {
          boolean enableExecutionRelationshipCounts = isExecutionRelatedEntityCountEnabled(executionEntity);
  
          if (
              !enableExecutionRelationshipCounts ||
              (enableExecutionRelationshipCounts && ((CountingExecutionEntity) executionEntity).getTaskCount() > 0)
          ) {
              TaskEntityManager taskEntityManager = getTaskEntityManager();
              Collection<TaskEntity> tasksForExecution = taskEntityManager.findTasksByExecutionId(
                  executionEntity.getId()
              );
              for (TaskEntity taskEntity : tasksForExecution) {
                  taskEntityManager.deleteTask(taskEntity, deleteReason, false, true);
              }
          }
      }
  
      private void deleteDataForExecution(ExecutionEntity executionEntity, String deleteReason) {
          deleteExecutionEntity(executionEntity, deleteReason);
          if (deleteReason != null && deleteReason.startsWith(DeleteReason.TERMINATE_END_EVENT)) {
              deleteChildExecutions(executionEntity, deleteReason);
          }
          deleteUserTask(executionEntity, deleteReason);
      }
  
      private void cancelDataForExecution(ExecutionEntity executionEntity, String deleteReason) {
          boolean isActive = executionEntity.isActive();
  
          deleteExecutionEntity(executionEntity, deleteReason);
          cancelUserTask(executionEntity, deleteReason);
  
          if (
              isActive &&
              executionEntity.getCurrentFlowElement() != null &&
              !(executionEntity.getCurrentFlowElement() instanceof UserTask) &&
              !(executionEntity.getCurrentFlowElement() instanceof SequenceFlow)
          ) {
              getEventDispatcher().dispatchEvent(
                  ActivitiEventBuilder.createActivityCancelledEvent(executionEntity, deleteReason)
              );
          }
      }
  
      // OTHER METHODS
  
      @Override
      public void updateProcessInstanceLockTime(String processInstanceId) {
          Date expirationTime = getClock().getCurrentTime();
          int lockMillis = getAsyncExecutor().getAsyncJobLockTimeInMillis();
  
          GregorianCalendar lockCal = new GregorianCalendar();
          lockCal.setTime(expirationTime);
          lockCal.add(Calendar.MILLISECOND, lockMillis);
          Date lockDate = lockCal.getTime();
  
          executionDataManager.updateProcessInstanceLockTime(processInstanceId, lockDate, expirationTime);
      }
  
      @Override
      public void clearProcessInstanceLockTime(String processInstanceId) {
          executionDataManager.clearProcessInstanceLockTime(processInstanceId);
      }
  
      @Override
      public String updateProcessInstanceBusinessKey(ExecutionEntity executionEntity, String businessKey) {
          if (executionEntity.isProcessInstanceType() && businessKey != null) {
              executionEntity.setBusinessKey(businessKey);
              getHistoryManager().updateProcessBusinessKeyInHistory(executionEntity);
  
              if (getEventDispatcher().isEnabled()) {
                  getEventDispatcher().dispatchEvent(
                      ActivitiEventBuilder.createEntityEvent(ActivitiEventType.ENTITY_UPDATED, executionEntity)
                  );
              }
  
              return businessKey;
          }
          return null;
      }
  
      public ExecutionDataManager getExecutionDataManager() {
          return executionDataManager;
      }
  
      public void setExecutionDataManager(ExecutionDataManager executionDataManager) {
          this.executionDataManager = executionDataManager;
      }
  }
  