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.cmd;
  
  import static java.util.Collections.singletonList;
  
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.List;
  import org.activiti.engine.ActivitiException;
  import org.activiti.engine.ActivitiIllegalArgumentException;
  import org.activiti.engine.ActivitiObjectNotFoundException;
  import org.activiti.engine.ProcessEngineConfiguration;
  import org.activiti.engine.impl.ProcessDefinitionQueryImpl;
  import org.activiti.engine.impl.ProcessInstanceQueryImpl;
  import org.activiti.engine.impl.interceptor.Command;
  import org.activiti.engine.impl.interceptor.CommandContext;
  import org.activiti.engine.impl.jobexecutor.JobHandler;
  import org.activiti.engine.impl.jobexecutor.TimerChangeProcessDefinitionSuspensionStateJobHandler;
  import org.activiti.engine.impl.persistence.entity.JobEntity;
  import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
  import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntityManager;
  import org.activiti.engine.impl.persistence.entity.SuspensionState;
  import org.activiti.engine.impl.persistence.entity.SuspensionState.SuspensionStateUtil;
  import org.activiti.engine.impl.persistence.entity.TimerJobEntity;
  import org.activiti.engine.repository.ProcessDefinition;
  import org.activiti.engine.runtime.ProcessInstance;
  
  /**
  
  
   */
  public abstract class AbstractSetProcessDefinitionStateCmd implements Command<Void> {
  
      protected String processDefinitionId;
      protected String processDefinitionKey;
      protected ProcessDefinitionEntity processDefinitionEntity;
      protected boolean includeProcessInstances = false;
      protected Date executionDate;
      protected String tenantId;
  
      public AbstractSetProcessDefinitionStateCmd(
          ProcessDefinitionEntity processDefinitionEntity,
          boolean includeProcessInstances,
          Date executionDate,
          String tenantId
      ) {
          this.processDefinitionEntity = processDefinitionEntity;
          this.includeProcessInstances = includeProcessInstances;
          this.executionDate = executionDate;
          this.tenantId = tenantId;
      }
  
      public AbstractSetProcessDefinitionStateCmd(
          String processDefinitionId,
          String processDefinitionKey,
          boolean includeProcessInstances,
          Date executionDate,
          String tenantId
      ) {
          this.processDefinitionId = processDefinitionId;
          this.processDefinitionKey = processDefinitionKey;
          this.includeProcessInstances = includeProcessInstances;
          this.executionDate = executionDate;
          this.tenantId = tenantId;
      }
  
      public Void execute(CommandContext commandContext) {
          List<ProcessDefinitionEntity> processDefinitions = findProcessDefinition(commandContext);
  
          executeInternal(commandContext, processDefinitions);
  
          return null;
      }
  
      protected void executeInternal(CommandContext commandContext, List<ProcessDefinitionEntity> processDefinitions) {
          if (executionDate != null) {
              // Process definition state change is delayed
              createTimerForDelayedExecution(commandContext, processDefinitions);
          } else {
              // Process definition state is changed now
              changeProcessDefinitionState(commandContext, processDefinitions);
          }
      }
  
      protected List<ProcessDefinitionEntity> findProcessDefinition(CommandContext commandContext) {
          // If process definition is already provided (eg. when command is called through the DeployCmd)
          // we don't need to do an extra database fetch and we can simply return it, wrapped in a list
          if (processDefinitionEntity != null) {
              return singletonList(processDefinitionEntity);
          }
  
          // Validation of input parameters
          if (processDefinitionId == null && processDefinitionKey == null) {
              throw new ActivitiIllegalArgumentException("Process definition id or key cannot be null");
          }
  
          List<ProcessDefinitionEntity> processDefinitionEntities = new ArrayList<ProcessDefinitionEntity>();
          ProcessDefinitionEntityManager processDefinitionManager = commandContext.getProcessDefinitionEntityManager();
  
          if (processDefinitionId != null) {
              ProcessDefinitionEntity processDefinitionEntity = processDefinitionManager.findById(processDefinitionId);
              if (processDefinitionEntity == null) {
                  throw new ActivitiObjectNotFoundException(
                      "Cannot find process definition for id '" + processDefinitionId + "'",
                      ProcessDefinition.class
                  );
              }
              processDefinitionEntities.add(processDefinitionEntity);
          } else {
              ProcessDefinitionQueryImpl query = new ProcessDefinitionQueryImpl(commandContext).processDefinitionKey(
                  processDefinitionKey
              );
  
              if (tenantId == null || ProcessEngineConfiguration.NO_TENANT_ID.equals(tenantId)) {
                  query.processDefinitionWithoutTenantId();
              } else {
                  query.processDefinitionTenantId(tenantId);
              }
  
              List<ProcessDefinition> processDefinitions = query.list();
              if (processDefinitions.isEmpty()) {
                  throw new ActivitiException("Cannot find process definition for key '" + processDefinitionKey + "'");
              }
  
              for (ProcessDefinition processDefinition : processDefinitions) {
                  processDefinitionEntities.add((ProcessDefinitionEntity) processDefinition);
              }
          }
          return processDefinitionEntities;
      }
  
      protected void createTimerForDelayedExecution(
          CommandContext commandContext,
          List<ProcessDefinitionEntity> processDefinitions
      ) {
          for (ProcessDefinitionEntity processDefinition : processDefinitions) {
              createTimerForDelayedExecutionInternal(commandContext, processDefinition);
          }
      }
  
      protected void createTimerForDelayedExecutionInternal(
          CommandContext commandContext,
          ProcessDefinitionEntity processDefinition
      ) {
          TimerJobEntity timer = commandContext.getTimerJobEntityManager().create();
          timer.setJobType(JobEntity.JOB_TYPE_TIMER);
          timer.setProcessDefinitionId(processDefinition.getId());
  
          // Inherit tenant identifier (if applicable)
          if (processDefinition.getTenantId() != null) {
              timer.setTenantId(processDefinition.getTenantId());
          }
  
          timer.setDuedate(executionDate);
          timer.setJobHandlerType(getDelayedExecutionJobHandlerType());
          timer.setJobHandlerConfiguration(
              TimerChangeProcessDefinitionSuspensionStateJobHandler.createJobHandlerConfiguration(includeProcessInstances)
          );
          commandContext.getJobManager().scheduleTimerJob(timer);
      }
  
      protected void changeProcessDefinitionState(
          CommandContext commandContext,
          List<ProcessDefinitionEntity> processDefinitions
      ) {
          for (ProcessDefinitionEntity processDefinition : processDefinitions) {
              changeProcessDefinitionStateInternal(commandContext, processDefinition);
          }
      }
  
      protected void changeProcessDefinitionStateInternal(
          CommandContext commandContext,
          ProcessDefinitionEntity processDefinition
      ) {
          SuspensionStateUtil.setSuspensionState(processDefinition, getProcessDefinitionSuspensionState());
  
          // Evict cache
          commandContext
              .getProcessEngineConfiguration()
              .getDeploymentManager()
              .getProcessDefinitionCache()
              .remove(processDefinition.getId());
  
          // Suspend process instances (if needed)
          if (includeProcessInstances) {
              int currentStartIndex = 0;
              List<ProcessInstance> processInstances = fetchProcessInstancesPage(
                  commandContext,
                  processDefinition,
                  currentStartIndex
              );
              while (!processInstances.isEmpty()) {
                  for (ProcessInstance processInstance : processInstances) {
                      AbstractSetProcessInstanceStateCmd processInstanceCmd = getProcessInstanceChangeStateCmd(
                          processInstance
                      );
                      processInstanceCmd.execute(commandContext);
                  }
  
                  // Fetch new batch of process instances
                  currentStartIndex += processInstances.size();
                  processInstances = fetchProcessInstancesPage(commandContext, processDefinition, currentStartIndex);
              }
          }
      }
  
      protected List<ProcessInstance> fetchProcessInstancesPage(
          CommandContext commandContext,
          ProcessDefinition processDefinition,
          int currentPageStartIndex
      ) {
          if (SuspensionState.ACTIVE.equals(getProcessDefinitionSuspensionState())) {
              return new ProcessInstanceQueryImpl(commandContext)
                  .processDefinitionId(processDefinition.getId())
                  .suspended()
                  .listPage(
                      currentPageStartIndex,
                      commandContext.getProcessEngineConfiguration().getBatchSizeProcessInstances()
                  );
          } else {
              return new ProcessInstanceQueryImpl(commandContext)
                  .processDefinitionId(processDefinition.getId())
                  .active()
                  .listPage(
                      currentPageStartIndex,
                      commandContext.getProcessEngineConfiguration().getBatchSizeProcessInstances()
                  );
          }
      }
  
      // ABSTRACT METHODS
      // ////////////////////////////////////////////////////////////////////
  
      /**
       * Subclasses should return the wanted {@link SuspensionState} here.
       */
      protected abstract SuspensionState getProcessDefinitionSuspensionState();
  
      /**
       * Subclasses should return the type of the {@link JobHandler} here. it will be used when the user provides an execution date on which the actual state change will happen.
       */
      protected abstract String getDelayedExecutionJobHandlerType();
  
      /**
       * Subclasses should return a {@link Command} implementation that matches the process definition state change.
       */
      protected abstract AbstractSetProcessInstanceStateCmd getProcessInstanceChangeStateCmd(
          ProcessInstance processInstance
      );
  }
  