/* 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.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.activiti.engine.EngineServices; import org.activiti.engine.impl.bpmn.parser.BpmnParse; import org.activiti.engine.impl.bpmn.parser.EventSubscriptionDeclaration; import org.activiti.engine.impl.context.Context; import org.activiti.engine.impl.db.DbSqlSession; import org.activiti.engine.impl.db.HasRevision; import org.activiti.engine.impl.db.PersistentObject; import org.activiti.engine.impl.interceptor.CommandContext; import org.activiti.engine.impl.jobexecutor.AsyncContinuationJobHandler; import org.activiti.engine.impl.jobexecutor.TimerDeclarationImpl; import org.activiti.engine.impl.pvm.PvmActivity; import org.activiti.engine.impl.pvm.PvmException; import org.activiti.engine.impl.pvm.PvmExecution; import org.activiti.engine.impl.pvm.PvmProcessDefinition; import org.activiti.engine.impl.pvm.PvmProcessElement; import org.activiti.engine.impl.pvm.PvmProcessInstance; import org.activiti.engine.impl.pvm.PvmTransition; import org.activiti.engine.impl.pvm.delegate.ActivityExecution; import org.activiti.engine.impl.pvm.delegate.ExecutionListenerExecution; import org.activiti.engine.impl.pvm.delegate.SignallableActivityBehavior; import org.activiti.engine.impl.pvm.process.ActivityImpl; import org.activiti.engine.impl.pvm.process.ProcessDefinitionImpl; import org.activiti.engine.impl.pvm.process.ScopeImpl; import org.activiti.engine.impl.pvm.process.TransitionImpl; import org.activiti.engine.impl.pvm.runtime.AtomicOperation; import org.activiti.engine.impl.pvm.runtime.InterpretableExecution; import org.activiti.engine.impl.pvm.runtime.OutgoingExecution; import org.activiti.engine.impl.pvm.runtime.StartingExecution; import org.activiti.engine.impl.util.BitMaskUtil; import org.activiti.engine.impl.variable.VariableDeclaration; import org.activiti.engine.runtime.Execution; import org.activiti.engine.runtime.Job; import org.activiti.engine.runtime.ProcessInstance; /** * @author Tom Baeyens * @author Daniel Meyer * @author Falko Menge */ public class ExecutionEntity extends VariableScopeImpl implements ActivityExecution, ExecutionListenerExecution, Execution, PvmExecution, ProcessInstance, InterpretableExecution, PersistentObject, HasRevision { private static final long serialVersionUID = 1L; private static Logger log = Logger.getLogger(ExecutionEntity.class.getName()); // Persistent refrenced entities state ////////////////////////////////////// protected static final int EVENT_SUBSCRIPTIONS_STATE_BIT = 1; protected static final int TASKS_STATE_BIT = 2; protected static final int JOBS_STATE_BIT = 3; // current position ///////////////////////////////////////////////////////// protected ProcessDefinitionImpl processDefinition; /** current activity */ protected ActivityImpl activity; /** current transition. is null when there is no transition being taken. */ protected TransitionImpl transition = null; /** transition that will be taken. is null when there is no transition being taken. */ protected TransitionImpl transitionBeingTaken = null; /** the process instance. this is the root of the execution tree. * the processInstance of a process instance is a self reference. */ protected ExecutionEntity processInstance; /** the parent execution */ protected ExecutionEntity parent; /** nested executions representing scopes or concurrent paths */ protected List<ExecutionEntity> executions; /** super execution, not-null if this execution is part of a subprocess */ protected ExecutionEntity superExecution; /** reference to a subprocessinstance, not-null if currently subprocess is started from this execution */ protected ExecutionEntity subProcessInstance; protected StartingExecution startingExecution; // state/type of execution ////////////////////////////////////////////////// /** indicates if this execution represents an active path of execution. * Executions are made inactive in the following situations: * <ul> * <li>an execution enters a nested scope</li> * <li>an execution is split up into multiple concurrent executions, then the parent is made inactive.</li> * <li>an execution has arrived in a parallel gateway or join and that join has not yet activated/fired.</li> * <li>an execution is ended.</li> * </ul>*/ protected boolean isActive = true; protected boolean isScope = true; protected boolean isConcurrent = false; protected boolean isEnded = false; protected boolean isEventScope = false; // events /////////////////////////////////////////////////////////////////// protected String eventName; protected PvmProcessElement eventSource; protected int executionListenerIndex = 0; // associated entities ///////////////////////////////////////////////////// // (we cache associated entities here to minimize db queries) protected List<EventSubscriptionEntity> eventSubscriptions; protected List<JobEntity> jobs; protected List<TaskEntity> tasks; protected int cachedEntityState; // cascade deletion //////////////////////////////////////////////////////// protected boolean deleteRoot; protected String deleteReason; // replaced by ////////////////////////////////////////////////////////////// /** when execution structure is pruned during a takeAll, then * the original execution has to be resolved to the replaced execution. * @see {@link #takeAll(List, List)} {@link OutgoingExecution} */ protected ExecutionEntity replacedBy; // atomic operations //////////////////////////////////////////////////////// /** next operation. process execution is in fact runtime interpretation of the process model. * each operation is a logical unit of interpretation of the process. so sequentially processing * the operations drives the interpretation or execution of a process. * @see AtomicOperation * @see #performOperation(AtomicOperation) */ protected AtomicOperation nextOperation; protected boolean isOperating = false; protected int revision = 1; protected int suspensionState = SuspensionState.ACTIVE.getStateCode(); /** * persisted reference to the processDefinition. * * @see #processDefinition * @see #setProcessDefinition(ProcessDefinitionImpl) * @see #getProcessDefinition() */ protected String processDefinitionId; /** * persisted reference to the current position in the diagram within the * {@link #processDefinition}. * * @see #activity * @see #setActivity(ActivityImpl) * @see #getActivity() */ protected String activityId; /** * The name of the current activity position */ protected String activityName; /** * persisted reference to the process instance. * * @see #getProcessInstance() */ protected String processInstanceId; /** * persisted reference to the business key. */ protected String businessKey; /** * persisted reference to the parent of this execution. * * @see #getParent() * @see #setParent(ExecutionEntity) */ protected String parentId; /** * persisted reference to the super execution of this execution * * @See {@link #getSuperExecution()} * @see #setSuperExecution(ExecutionEntity) */ protected String superExecutionId; protected boolean forcedUpdate; public ExecutionEntity() { } public ExecutionEntity(ActivityImpl activityImpl) { this.startingExecution = new StartingExecution(activityImpl); } /** creates a new execution. properties processDefinition, processInstance and activity will be initialized. */ public ExecutionEntity createExecution() { // create the new child execution ExecutionEntity createdExecution = newExecution(); // manage the bidirectional parent-child relation ensureExecutionsInitialized(); executions.add(createdExecution); createdExecution.setParent(this); // initialize the new execution createdExecution.setProcessDefinition(getProcessDefinition()); createdExecution.setProcessInstance(getProcessInstance()); createdExecution.setActivity(getActivity()); if (log.isLoggable(Level.FINE)) { log.fine("Child execution "+createdExecution+" created with parent "+this); } return createdExecution; } public PvmProcessInstance createSubProcessInstance(PvmProcessDefinition processDefinition) { ExecutionEntity subProcessInstance = newExecution(); // manage bidirectional super-subprocess relation subProcessInstance.setSuperExecution(this); this.setSubProcessInstance(subProcessInstance); // Initialize the new execution subProcessInstance.setProcessDefinition((ProcessDefinitionImpl) processDefinition); subProcessInstance.setProcessInstance(subProcessInstance); Context.getCommandContext().getHistoryManager() .recordSubProcessInstanceStart(this, subProcessInstance); return subProcessInstance; } protected ExecutionEntity newExecution() { ExecutionEntity newExecution = new ExecutionEntity(); newExecution.executions = new ArrayList<ExecutionEntity>(); Context .getCommandContext() .getDbSqlSession() .insert(newExecution); return newExecution; } // scopes /////////////////////////////////////////////////////////////////// @SuppressWarnings("unchecked") public void initialize() { log.fine("initializing "+this); ScopeImpl scope = getScope(); ensureParentInitialized(); List<VariableDeclaration> variableDeclarations = (List<VariableDeclaration>) scope.getProperty(BpmnParse.PROPERTYNAME_VARIABLE_DECLARATIONS); if (variableDeclarations!=null) { for (VariableDeclaration variableDeclaration : variableDeclarations) { variableDeclaration.initialize(this, parent); } } // initialize the lists of referenced objects (prevents db queries) variableInstances = new HashMap<String, VariableInstanceEntity>(); eventSubscriptions = new ArrayList<EventSubscriptionEntity>(); jobs = new ArrayList<JobEntity>(); tasks = new ArrayList<TaskEntity>(); // Cached entity-state initialized to null, all bits are zore, indicating NO entities present cachedEntityState = 0; List<TimerDeclarationImpl> timerDeclarations = (List<TimerDeclarationImpl>) scope.getProperty(BpmnParse.PROPERTYNAME_TIMER_DECLARATION); if (timerDeclarations!=null) { for (TimerDeclarationImpl timerDeclaration : timerDeclarations) { TimerEntity timer = timerDeclaration.prepareTimerEntity(this); Context .getCommandContext() .getJobManager() .schedule(timer); } } // create event subscriptions for the current scope List<EventSubscriptionDeclaration> eventSubscriptionDeclarations = (List<EventSubscriptionDeclaration>) scope.getProperty(BpmnParse.PROPERTYNAME_EVENT_SUBSCRIPTION_DECLARATION); if(eventSubscriptionDeclarations != null) { for (EventSubscriptionDeclaration eventSubscriptionDeclaration : eventSubscriptionDeclarations) { if(!eventSubscriptionDeclaration.isStartEvent()) { EventSubscriptionEntity eventSubscriptionEntity = eventSubscriptionDeclaration.prepareEventSubscriptionEntity(this); eventSubscriptionEntity.insert(); } } } } public void start() { if(startingExecution == null && isProcessInstance()) { startingExecution = new StartingExecution(processDefinition.getInitial()); } performOperation(AtomicOperation.PROCESS_START); } public void destroy() { log.fine("destroying "+this); ensureParentInitialized(); deleteVariablesInstanceForLeavingScope(); setScope(false); } /** removes an execution. if there are nested executions, those will be ended recursively. * if there is a parent, this method removes the bidirectional relation * between parent and this execution. */ public void end() { isActive = false; isEnded = true; performOperation(AtomicOperation.ACTIVITY_END); } // methods that translate to operations ///////////////////////////////////// public void signal(String signalName, Object signalData) { ensureActivityInitialized(); SignallableActivityBehavior activityBehavior = (SignallableActivityBehavior) activity.getActivityBehavior(); try { activityBehavior.signal(this, signalName, signalData); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new PvmException("couldn't process signal '"+signalName+"' on activity '"+activity.getId()+"': "+e.getMessage(), e); } } public void take(PvmTransition transition) { if (this.transition!=null) { throw new PvmException("already taking a transition"); } if (transition==null) { throw new PvmException("transition is null"); } setActivity((ActivityImpl)transition.getSource()); setTransition((TransitionImpl) transition); performOperation(AtomicOperation.TRANSITION_NOTIFY_LISTENER_END); } public void executeActivity(PvmActivity activity) { setActivity((ActivityImpl) activity); performOperation(AtomicOperation.ACTIVITY_START); } public List<ActivityExecution> findInactiveConcurrentExecutions(PvmActivity activity) { List<ActivityExecution> inactiveConcurrentExecutionsInActivity = new ArrayList<ActivityExecution>(); List<ActivityExecution> otherConcurrentExecutions = new ArrayList<ActivityExecution>(); if (isConcurrent()) { List< ? extends ActivityExecution> concurrentExecutions = getParent().getAllChildExecutions(); for (ActivityExecution concurrentExecution: concurrentExecutions) { if (concurrentExecution.getActivity()==activity) { if (!concurrentExecution.isActive()) { inactiveConcurrentExecutionsInActivity.add(concurrentExecution); } } else { otherConcurrentExecutions.add(concurrentExecution); } } } else { if (!isActive()) { inactiveConcurrentExecutionsInActivity.add(this); } else { otherConcurrentExecutions.add(this); } } if (log.isLoggable(Level.FINE)) { log.fine("inactive concurrent executions in '"+activity+"': "+inactiveConcurrentExecutionsInActivity); log.fine("other concurrent executions: "+otherConcurrentExecutions); } return inactiveConcurrentExecutionsInActivity; } protected List<ExecutionEntity> getAllChildExecutions() { List<ExecutionEntity> childExecutions = new ArrayList<ExecutionEntity>(); for (ExecutionEntity childExecution : getExecutions()) { childExecutions.add(childExecution); childExecutions.addAll(childExecution.getAllChildExecutions()); } return childExecutions; } @SuppressWarnings({ "unchecked", "rawtypes" }) public void takeAll(List<PvmTransition> transitions, List<ActivityExecution> recyclableExecutions) { transitions = new ArrayList<PvmTransition>(transitions); recyclableExecutions = (recyclableExecutions!=null ? new ArrayList<ActivityExecution>(recyclableExecutions) : new ArrayList<ActivityExecution>()); if (recyclableExecutions.size()>1) { for (ActivityExecution recyclableExecution: recyclableExecutions) { if (((ExecutionEntity)recyclableExecution).isScope()) { throw new PvmException("joining scope executions is not allowed"); } } } ExecutionEntity concurrentRoot = ((isConcurrent && !isScope) ? getParent() : this); List<ExecutionEntity> concurrentActiveExecutions = new ArrayList<ExecutionEntity>(); List<ExecutionEntity> concurrentInActiveExecutions = new ArrayList<ExecutionEntity>(); for (ExecutionEntity execution: concurrentRoot.getExecutions()) { if (execution.isActive()) { concurrentActiveExecutions.add(execution); } else { concurrentInActiveExecutions.add(execution); } } if (log.isLoggable(Level.FINE)) { log.fine("transitions to take concurrent: " + transitions); log.fine("active concurrent executions: " + concurrentActiveExecutions); } if ( (transitions.size()==1) && (concurrentActiveExecutions.isEmpty()) && allExecutionsInSameActivity(concurrentInActiveExecutions) ) { List<ExecutionEntity> recyclableExecutionImpls = (List) recyclableExecutions; recyclableExecutions.remove(concurrentRoot); for (ExecutionEntity prunedExecution: recyclableExecutionImpls) { // End the pruned executions if necessary. // Some recyclable executions are inactivated (joined executions) // Others are already ended (end activities) if (!prunedExecution.isEnded()) { log.fine("pruning execution " + prunedExecution); prunedExecution.remove(); } } log.fine("activating the concurrent root "+concurrentRoot+" as the single path of execution going forward"); concurrentRoot.setActive(true); concurrentRoot.setActivity(activity); concurrentRoot.setConcurrent(false); concurrentRoot.take(transitions.get(0)); } else { List<OutgoingExecution> outgoingExecutions = new ArrayList<OutgoingExecution>(); recyclableExecutions.remove(concurrentRoot); log.fine("recyclable executions for reuse: " + recyclableExecutions); // first create the concurrent executions while (!transitions.isEmpty()) { PvmTransition outgoingTransition = transitions.remove(0); ExecutionEntity outgoingExecution = null; if (recyclableExecutions.isEmpty()) { outgoingExecution = concurrentRoot.createExecution(); log.fine("new "+outgoingExecution+" with parent " + outgoingExecution.getParent()+" created to take transition "+outgoingTransition); } else { outgoingExecution = (ExecutionEntity) recyclableExecutions.remove(0); log.fine("recycled "+outgoingExecution+" to take transition "+outgoingTransition); } outgoingExecution.setActive(true); outgoingExecution.setScope(false); outgoingExecution.setConcurrent(true); outgoingExecution.setTransitionBeingTaken((TransitionImpl) outgoingTransition); outgoingExecutions.add(new OutgoingExecution(outgoingExecution, outgoingTransition, true)); } // prune the executions that are not recycled for (ActivityExecution prunedExecution: recyclableExecutions) { log.fine("pruning execution "+prunedExecution); prunedExecution.end(); } // then launch all the concurrent executions for (OutgoingExecution outgoingExecution: outgoingExecutions) { outgoingExecution.take(); } } } protected boolean allExecutionsInSameActivity(List<ExecutionEntity> executions) { if (executions.size() > 1) { String activityId = executions.get(0).getActivityId(); for (ExecutionEntity execution : executions) { String otherActivityId = execution.getActivityId(); if (!execution.isEnded) { if ( (activityId == null && otherActivityId != null) || (activityId != null && otherActivityId == null) || (activityId != null && otherActivityId!= null && !otherActivityId.equals(activityId))) { return false; } } } } return true; } public void performOperation(AtomicOperation executionOperation) { if(executionOperation.isAsync(this)) { scheduleAtomicOperationAsync(executionOperation); } else { performOperationSync(executionOperation); } } protected void performOperationSync(AtomicOperation executionOperation) { Context .getCommandContext() .performOperation(executionOperation, this); } protected void scheduleAtomicOperationAsync(AtomicOperation executionOperation) { MessageEntity message = new MessageEntity(); message.setExecution(this); message.setExclusive(getActivity().isExclusive()); message.setJobHandlerType(AsyncContinuationJobHandler.TYPE); // At the moment, only AtomicOperationTransitionCreateScope can be performed asynchronously, // so there is no need to pass it to the handler Context .getCommandContext() .getJobManager() .send(message); } public boolean isActive(String activityId) { return findExecution(activityId)!=null; } public void inactivate() { this.isActive = false; } // executions /////////////////////////////////////////////////////////////// /** ensures initialization and returns the non-null executions list */ public List<ExecutionEntity> getExecutions() { ensureExecutionsInitialized(); return executions; } @SuppressWarnings({ "unchecked", "rawtypes" }) protected void ensureExecutionsInitialized() { if (executions==null) { this.executions = (List) Context .getCommandContext() .getExecutionManager() .findChildExecutionsByParentExecutionId(id); } } public void setExecutions(List<ExecutionEntity> executions) { this.executions = executions; } /** searches for an execution positioned in the given activity */ public ExecutionEntity findExecution(String activityId) { if ( (getActivity()!=null) && (getActivity().getId().equals(activityId)) ) { return this; } for (ExecutionEntity nestedExecution : getExecutions()) { ExecutionEntity result = nestedExecution.findExecution(activityId); if (result != null) { return result; } } return null; } public List<String> findActiveActivityIds() { List<String> activeActivityIds = new ArrayList<String>(); collectActiveActivityIds(activeActivityIds); return activeActivityIds; } protected void collectActiveActivityIds(List<String> activeActivityIds) { ensureActivityInitialized(); if (isActive && activity!=null) { activeActivityIds.add(activity.getId()); } ensureExecutionsInitialized(); for (ExecutionEntity execution: executions) { execution.collectActiveActivityIds(activeActivityIds); } } // bussiness key //////////////////////////////////////////////////////////// public String getBusinessKey() { return businessKey; } public void setBusinessKey(String businessKey) { this.businessKey = businessKey; } public String getProcessBusinessKey() { return getProcessInstance().getBusinessKey(); } // process definition /////////////////////////////////////////////////////// /** ensures initialization and returns the process definition. */ public ProcessDefinitionImpl getProcessDefinition() { ensureProcessDefinitionInitialized(); return processDefinition; } public void setProcessDefinitionId(String processDefinitionId) { this.processDefinitionId = processDefinitionId; } public String getProcessDefinitionId() { return processDefinitionId; } /** for setting the process definition, this setter must be used as subclasses can override */ protected void ensureProcessDefinitionInitialized() { if ((processDefinition == null) && (processDefinitionId != null)) { ProcessDefinitionEntity deployedProcessDefinition = Context .getProcessEngineConfiguration() .getDeploymentCache() .findDeployedProcessDefinitionById(processDefinitionId); setProcessDefinition(deployedProcessDefinition); } } public void setProcessDefinition(ProcessDefinitionImpl processDefinition) { this.processDefinition = processDefinition; this.processDefinitionId = processDefinition.getId(); } // process instance ///////////////////////////////////////////////////////// /** ensures initialization and returns the process instance. */ public ExecutionEntity getProcessInstance() { ensureProcessInstanceInitialized(); return processInstance; } protected void ensureProcessInstanceInitialized() { if ((processInstance == null) && (processInstanceId != null)) { processInstance = Context .getCommandContext() .getExecutionManager() .findExecutionById(processInstanceId); } } public void setProcessInstance(InterpretableExecution processInstance) { this.processInstance = (ExecutionEntity) processInstance; if (processInstance != null) { this.processInstanceId = this.processInstance.getId(); } } public boolean isProcessInstance() { return parentId == null; } // activity ///////////////////////////////////////////////////////////////// /** ensures initialization and returns the activity */ public ActivityImpl getActivity() { ensureActivityInitialized(); return activity; } /** must be called before the activity member field or getActivity() is called */ protected void ensureActivityInitialized() { if ((activity == null) && (activityId != null)) { activity = getProcessDefinition().findActivity(activityId); } } public void setActivity(ActivityImpl activity) { this.activity = activity; if (activity != null) { this.activityId = activity.getId(); this.activityName = (String) activity.getProperty("name"); } else { this.activityId = null; this.activityName = null; } } // parent /////////////////////////////////////////////////////////////////// /** ensures initialization and returns the parent */ public ExecutionEntity getParent() { ensureParentInitialized(); return parent; } protected void ensureParentInitialized() { if (parent == null && parentId != null) { parent = Context .getCommandContext() .getExecutionManager() .findExecutionById(parentId); } } public void setParent(InterpretableExecution parent) { this.parent = (ExecutionEntity) parent; if (parent != null) { this.parentId = ((ExecutionEntity)parent).getId(); } else { this.parentId = null; } } // super- and subprocess executions ///////////////////////////////////////// public String getSuperExecutionId() { return superExecutionId; } public ExecutionEntity getSuperExecution() { ensureSuperExecutionInitialized(); return superExecution; } public void setSuperExecution(ExecutionEntity superExecution) { this.superExecution = superExecution; if (superExecution != null) { superExecution.setSubProcessInstance(null); } if (superExecution != null) { this.superExecutionId = ((ExecutionEntity)superExecution).getId(); } else { this.superExecutionId = null; } } protected void ensureSuperExecutionInitialized() { if (superExecution == null && superExecutionId != null) { superExecution = Context .getCommandContext() .getExecutionManager() .findExecutionById(superExecutionId); } } public ExecutionEntity getSubProcessInstance() { ensureSubProcessInstanceInitialized(); return subProcessInstance; } public void setSubProcessInstance(InterpretableExecution subProcessInstance) { this.subProcessInstance = (ExecutionEntity) subProcessInstance; } protected void ensureSubProcessInstanceInitialized() { if (subProcessInstance == null) { subProcessInstance = Context .getCommandContext() .getExecutionManager() .findSubProcessInstanceBySuperExecutionId(id); } } // scopes /////////////////////////////////////////////////////////////////// protected ScopeImpl getScope() { ScopeImpl scope = null; if (isProcessInstance()) { scope = getProcessDefinition(); } else { scope = getActivity(); } return scope; } public boolean isScope() { return isScope; } public void setScope(boolean isScope) { this.isScope = isScope; } // customized persistence behaviour ///////////////////////////////////////// public void remove() { ensureParentInitialized(); if (parent!=null) { parent.ensureExecutionsInitialized(); parent.executions.remove(this); } // delete all the variable instances ensureVariableInstancesInitialized(); deleteVariablesInstanceForLeavingScope(); // delete all the tasks removeTasks(null); // remove all jobs removeJobs(); // remove all event subscriptions for this scope, if the scope has event subscriptions: removeEventSubscriptions(); // remove event scopes: removeEventScopes(); // finally delete this execution Context.getCommandContext() .getDbSqlSession() .delete(this); } public void destroyScope(String reason) { if(log.isLoggable(Level.FINE)) { log.fine("performing destroy scope behavior for execution "+this); } // remove all child executions and sub process instances: List<InterpretableExecution> executions = new ArrayList<InterpretableExecution>(getExecutions()); for (InterpretableExecution childExecution : executions) { if (childExecution.getSubProcessInstance()!=null) { childExecution.getSubProcessInstance().deleteCascade(reason); } childExecution.deleteCascade(reason); } removeTasks(reason); removeJobs(); // Daniel thought this would be needed, but it seems not: removeEventSubscriptions(); } private void removeEventScopes() { List<InterpretableExecution> childExecutions = new ArrayList<InterpretableExecution>(getExecutions()); for (InterpretableExecution childExecution : childExecutions) { if(childExecution.isEventScope()) { log.fine("removing eventScope "+childExecution); childExecution.destroy(); childExecution.remove(); } } } private void removeEventSubscriptions() { for (EventSubscriptionEntity eventSubscription : getEventSubscriptions()) { if (replacedBy != null) { eventSubscription.setExecution((ExecutionEntity) replacedBy); } else { eventSubscription.delete(); } } } private void removeJobs() { for (Job job: getJobs()) { if (replacedBy!=null) { ((JobEntity)job).setExecution((ExecutionEntity) replacedBy); } else { ((JobEntity)job).delete(); } } } private void removeTasks(String reason) { if(reason == null) { reason = TaskEntity.DELETE_REASON_DELETED; } for (TaskEntity task : getTasks()) { if (replacedBy!=null) { if(task.getExecution() == null || task.getExecution() != replacedBy) { // All tasks should have been moved when "replacedBy" has been set. Just in case tasks where added, // wo do an additional check here and move it task.setExecution(replacedBy); this.replacedBy.addTask(task); } } else { Context.getCommandContext() .getTaskManager() .deleteTask(task, reason, false); } } } public ExecutionEntity getReplacedBy() { return replacedBy; } @SuppressWarnings({ "unchecked", "rawtypes" }) public void setReplacedBy(InterpretableExecution replacedBy) { this.replacedBy = (ExecutionEntity) replacedBy; CommandContext commandContext = Context.getCommandContext(); DbSqlSession dbSqlSession = commandContext.getDbSqlSession(); // update the related tasks for (TaskEntity task: getTasks()) { task.setExecutionId(replacedBy.getId()); task.setExecution(this.replacedBy); this.replacedBy.addTask(task); } // All tasks have been moved to 'replacedBy', safe to clear the list this.tasks.clear(); tasks = dbSqlSession.findInCache(TaskEntity.class); for (TaskEntity task: tasks) { if (id.equals(task.getExecutionId())) { task.setExecutionId(replacedBy.getId()); } } // update the related jobs List<JobEntity> jobs = getJobs(); for (JobEntity job: jobs) { job.setExecution((ExecutionEntity) replacedBy); } // update the related event subscriptions List<EventSubscriptionEntity> eventSubscriptions = getEventSubscriptions(); for (EventSubscriptionEntity subscriptionEntity: eventSubscriptions) { subscriptionEntity.setExecution((ExecutionEntity) replacedBy); } // update the related process variables List<VariableInstanceEntity> variables = (List) commandContext .getVariableInstanceManager() .findVariableInstancesByExecutionId(id); for (VariableInstanceEntity variable: variables) { variable.setExecutionId(replacedBy.getId()); } variables = dbSqlSession.findInCache(VariableInstanceEntity.class); for (VariableInstanceEntity variable: variables) { if (id.equals(variable.getExecutionId())) { variable.setExecutionId(replacedBy.getId()); } } commandContext.getHistoryManager() .recordExecutionReplacedBy(this, replacedBy); } // variables //////////////////////////////////////////////////////////////// @Override protected void initializeVariableInstanceBackPointer(VariableInstanceEntity variableInstance) { variableInstance.setProcessInstanceId(processInstanceId); variableInstance.setExecutionId(id); } @Override protected List<VariableInstanceEntity> loadVariableInstances() { return Context .getCommandContext() .getVariableInstanceManager() .findVariableInstancesByExecutionId(id); } @Override protected VariableScopeImpl getParentVariableScope() { return getParent(); } /** used to calculate the sourceActivityExecution for method {@link #updateActivityInstanceIdInHistoricVariableUpdate(HistoricDetailVariableInstanceUpdateEntity, ExecutionEntity)} */ protected ExecutionEntity getSourceActivityExecution() { return (activityId!=null ? this : null); } @Override protected boolean isActivityIdUsedForDetails() { return true; } // persistent state ///////////////////////////////////////////////////////// public Object getPersistentState() { Map<String, Object> persistentState = new HashMap<String, Object>(); persistentState.put("processDefinitionId", this.processDefinitionId); persistentState.put("businessKey", businessKey); persistentState.put("activityId", this.activityId); persistentState.put("isActive", this.isActive); persistentState.put("isConcurrent", this.isConcurrent); persistentState.put("isScope", this.isScope); persistentState.put("isEventScope", this.isEventScope); persistentState.put("parentId", parentId); persistentState.put("superExecution", this.superExecutionId); if (forcedUpdate) { persistentState.put("forcedUpdate", Boolean.TRUE); } persistentState.put("suspensionState", this.suspensionState); persistentState.put("cachedEntityState", this.cachedEntityState); return persistentState; } public void insert() { Context .getCommandContext() .getDbSqlSession() .insert(this); } public void deleteCascade(String deleteReason) { this.deleteReason = deleteReason; this.deleteRoot = true; performOperation(AtomicOperation.DELETE_CASCADE); } public int getRevisionNext() { return revision+1; } public void forceUpdate() { this.forcedUpdate = true; } // process engine convience access ///////////////////////////////////////////////////////////////// public EngineServices getEngineServices() { return Context.getProcessEngineConfiguration(); } // toString ///////////////////////////////////////////////////////////////// public String toString() { if (isProcessInstance()) { return "ProcessInstance["+getToStringIdentity()+"]"; } else { return (isConcurrent? "Concurrent" : "")+(isScope ? "Scope" : "")+"Execution["+getToStringIdentity()+"]"; } } protected String getToStringIdentity() { return id; } // event subscription support ////////////////////////////////////////////// public List<EventSubscriptionEntity> getEventSubscriptionsInternal() { ensureEventSubscriptionsInitialized(); return eventSubscriptions; } public List<EventSubscriptionEntity> getEventSubscriptions() { return new ArrayList<EventSubscriptionEntity>(getEventSubscriptionsInternal()); } public List<CompensateEventSubscriptionEntity> getCompensateEventSubscriptions() { List<EventSubscriptionEntity> eventSubscriptions = getEventSubscriptionsInternal(); List<CompensateEventSubscriptionEntity> result = new ArrayList<CompensateEventSubscriptionEntity>(eventSubscriptions.size()); for (EventSubscriptionEntity eventSubscriptionEntity : eventSubscriptions) { if(eventSubscriptionEntity instanceof CompensateEventSubscriptionEntity) { result.add((CompensateEventSubscriptionEntity) eventSubscriptionEntity); } } return result; } public List<CompensateEventSubscriptionEntity> getCompensateEventSubscriptions(String activityId) { List<EventSubscriptionEntity> eventSubscriptions = getEventSubscriptionsInternal(); List<CompensateEventSubscriptionEntity> result = new ArrayList<CompensateEventSubscriptionEntity>(eventSubscriptions.size()); for (EventSubscriptionEntity eventSubscriptionEntity : eventSubscriptions) { if(eventSubscriptionEntity instanceof CompensateEventSubscriptionEntity) { if(activityId.equals(eventSubscriptionEntity.getActivityId())) { result.add((CompensateEventSubscriptionEntity) eventSubscriptionEntity); } } } return result; } protected void ensureEventSubscriptionsInitialized() { if (eventSubscriptions == null) { eventSubscriptions = Context.getCommandContext() .getEventSubscriptionManager() .findEventSubscriptionsByExecution(id); } } public void addEventSubscription(EventSubscriptionEntity eventSubscriptionEntity) { getEventSubscriptionsInternal().add(eventSubscriptionEntity); } public void removeEventSubscription(EventSubscriptionEntity eventSubscriptionEntity) { getEventSubscriptionsInternal().remove(eventSubscriptionEntity); } // referenced job entities ////////////////////////////////////////////////// @SuppressWarnings({ "unchecked", "rawtypes" }) protected void ensureJobsInitialized() { if(jobs == null) { jobs = (List)Context.getCommandContext() .getJobManager() .findJobsByExecutionId(id); } } protected List<JobEntity> getJobsInternal() { ensureJobsInitialized(); return jobs; } public List<JobEntity> getJobs() { return new ArrayList<JobEntity>(getJobsInternal()); } public void addJob(JobEntity jobEntity) { getJobsInternal().add(jobEntity); } public void removeJob(JobEntity job) { getJobsInternal().remove(job); } // referenced task entities /////////////////////////////////////////////////// @SuppressWarnings({ "unchecked", "rawtypes" }) protected void ensureTasksInitialized() { if(tasks == null) { tasks = (List)Context.getCommandContext() .getTaskManager() .findTasksByExecutionId(id); } } protected List<TaskEntity> getTasksInternal() { ensureTasksInitialized(); return tasks; } public List<TaskEntity> getTasks() { return new ArrayList<TaskEntity>(getTasksInternal()); } public void addTask(TaskEntity taskEntity) { getTasksInternal().add(taskEntity); } public void removeTask(TaskEntity task) { getTasksInternal().remove(task); } // getters and setters ////////////////////////////////////////////////////// public void setCachedEntityState(int cachedEntityState) { this.cachedEntityState = cachedEntityState; // Check for flags that are down. These lists can be safely initialized as empty, preventing // additional queries that end up in an empty list anyway if(jobs == null && !BitMaskUtil.isBitOn(cachedEntityState, JOBS_STATE_BIT)) { jobs = new ArrayList<JobEntity>(); } if(tasks == null && !BitMaskUtil.isBitOn(cachedEntityState, TASKS_STATE_BIT)) { tasks = new ArrayList<TaskEntity>(); } if(eventSubscriptions == null && !BitMaskUtil.isBitOn(cachedEntityState, EVENT_SUBSCRIPTIONS_STATE_BIT)) { eventSubscriptions = new ArrayList<EventSubscriptionEntity>(); } } public int getCachedEntityState() { cachedEntityState = 0; // Only mark a flag as false when the list is not-null and empty. If null, we can't be sure there are no entries in it since // the list hasn't been initialized/queried yet. cachedEntityState = BitMaskUtil.setBit(cachedEntityState, TASKS_STATE_BIT, (tasks == null || tasks.size() > 0)); cachedEntityState = BitMaskUtil.setBit(cachedEntityState, EVENT_SUBSCRIPTIONS_STATE_BIT, (eventSubscriptions == null || eventSubscriptions.size() > 0)); cachedEntityState = BitMaskUtil.setBit(cachedEntityState, JOBS_STATE_BIT, (jobs == null || jobs.size() > 0)); return cachedEntityState; } public String getProcessInstanceId() { return processInstanceId; } public String getParentId() { return parentId; } public void setParentId(String parentId) { this.parentId = parentId; } public String getId() { return id; } public void setId(String id) { this.id = id; } public int getRevision() { return revision; } public void setRevision(int revision) { this.revision = revision; } public String getActivityId() { return activityId; } public TransitionImpl getTransition() { return transition; } public void setTransition(TransitionImpl transition) { this.transition = transition; } public TransitionImpl getTransitionBeingTaken() { return transitionBeingTaken; } public void setTransitionBeingTaken(TransitionImpl transitionBeingTaken) { this.transitionBeingTaken = transitionBeingTaken; } public Integer getExecutionListenerIndex() { return executionListenerIndex; } public void setExecutionListenerIndex(Integer executionListenerIndex) { this.executionListenerIndex = executionListenerIndex; } public boolean isConcurrent() { return isConcurrent; } public void setConcurrent(boolean isConcurrent) { this.isConcurrent = isConcurrent; } public boolean isActive() { return isActive; } public void setActive(boolean isActive) { this.isActive = isActive; } public boolean isEnded() { return isEnded; } public String getEventName() { return eventName; } public void setEventName(String eventName) { this.eventName = eventName; } public PvmProcessElement getEventSource() { return eventSource; } public void setEventSource(PvmProcessElement eventSource) { this.eventSource = eventSource; } public String getDeleteReason() { return deleteReason; } public void setDeleteReason(String deleteReason) { this.deleteReason = deleteReason; } public boolean isDeleteRoot() { return deleteRoot; } public int getSuspensionState() { return suspensionState; } public void setSuspensionState(int suspensionState) { this.suspensionState = suspensionState; } public boolean isSuspended() { return suspensionState == SuspensionState.SUSPENDED.getStateCode(); } public boolean isEventScope() { return isEventScope; } public void setEventScope(boolean isEventScope) { this.isEventScope = isEventScope; } public StartingExecution getStartingExecution() { return startingExecution; } public void disposeStartingExecution() { startingExecution = null; } public String getCurrentActivityId() { return activityId; } public String getCurrentActivityName() { return activityName; } }