org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl Maven / Gradle / Ivy
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
* under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. Camunda licenses this file to you under the Apache License,
* Version 2.0; 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.camunda.bpm.engine.impl.pvm.runtime;
import static org.camunda.bpm.engine.impl.bpmn.helper.CompensationUtil.SIGNAL_COMPENSATION_DONE;
import static org.camunda.bpm.engine.impl.pvm.runtime.ActivityInstanceState.ENDING;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.camunda.bpm.engine.ProcessEngineException;
import org.camunda.bpm.engine.impl.ProcessEngineLogger;
import org.camunda.bpm.engine.impl.bpmn.helper.BpmnProperties;
import org.camunda.bpm.engine.impl.cmmn.execution.CmmnExecution;
import org.camunda.bpm.engine.impl.cmmn.model.CmmnCaseDefinition;
import org.camunda.bpm.engine.impl.context.Context;
import org.camunda.bpm.engine.impl.core.instance.CoreExecution;
import org.camunda.bpm.engine.impl.core.variable.event.VariableEvent;
import org.camunda.bpm.engine.impl.core.variable.scope.AbstractVariableScope;
import org.camunda.bpm.engine.impl.form.FormPropertyHelper;
import org.camunda.bpm.engine.impl.history.HistoryLevel;
import org.camunda.bpm.engine.impl.history.event.HistoryEvent;
import org.camunda.bpm.engine.impl.history.event.HistoryEventProcessor;
import org.camunda.bpm.engine.impl.history.event.HistoryEventTypes;
import org.camunda.bpm.engine.impl.history.producer.HistoryEventProducer;
import org.camunda.bpm.engine.impl.incident.IncidentContext;
import org.camunda.bpm.engine.impl.incident.IncidentHandler;
import org.camunda.bpm.engine.impl.incident.IncidentHandling;
import org.camunda.bpm.engine.impl.persistence.entity.DelayedVariableEvent;
import org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity;
import org.camunda.bpm.engine.impl.persistence.entity.IncidentEntity;
import org.camunda.bpm.engine.impl.pvm.PvmActivity;
import org.camunda.bpm.engine.impl.pvm.PvmException;
import org.camunda.bpm.engine.impl.pvm.PvmExecution;
import org.camunda.bpm.engine.impl.pvm.PvmLogger;
import org.camunda.bpm.engine.impl.pvm.PvmProcessDefinition;
import org.camunda.bpm.engine.impl.pvm.PvmProcessInstance;
import org.camunda.bpm.engine.impl.pvm.PvmScope;
import org.camunda.bpm.engine.impl.pvm.PvmTransition;
import org.camunda.bpm.engine.impl.pvm.delegate.ActivityExecution;
import org.camunda.bpm.engine.impl.pvm.delegate.CompositeActivityBehavior;
import org.camunda.bpm.engine.impl.pvm.delegate.ModificationObserverBehavior;
import org.camunda.bpm.engine.impl.pvm.delegate.SignallableActivityBehavior;
import org.camunda.bpm.engine.impl.pvm.process.ActivityImpl;
import org.camunda.bpm.engine.impl.pvm.process.ActivityStartBehavior;
import org.camunda.bpm.engine.impl.pvm.process.ProcessDefinitionImpl;
import org.camunda.bpm.engine.impl.pvm.process.ScopeImpl;
import org.camunda.bpm.engine.impl.pvm.process.TransitionImpl;
import org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperation;
import org.camunda.bpm.engine.impl.tree.ExecutionWalker;
import org.camunda.bpm.engine.impl.tree.FlowScopeWalker;
import org.camunda.bpm.engine.impl.tree.LeafActivityInstanceExecutionCollector;
import org.camunda.bpm.engine.impl.tree.ReferenceWalker;
import org.camunda.bpm.engine.impl.tree.ScopeCollector;
import org.camunda.bpm.engine.impl.tree.ScopeExecutionCollector;
import org.camunda.bpm.engine.impl.tree.TreeVisitor;
import org.camunda.bpm.engine.impl.util.EnsureUtil;
import org.camunda.bpm.engine.runtime.Incident;
import org.camunda.bpm.engine.variable.VariableMap;
/**
* @author Daniel Meyer
* @author Roman Smirnov
* @author Sebastian Menski
*/
public abstract class PvmExecutionImpl extends CoreExecution implements
ActivityExecution, PvmProcessInstance {
private static final long serialVersionUID = 1L;
private static final PvmLogger LOG = ProcessEngineLogger.PVM_LOGGER;
protected transient ProcessDefinitionImpl processDefinition;
protected transient ScopeInstantiationContext scopeInstantiationContext;
protected transient boolean ignoreAsync = false;
/**
* true for process instances in the initial phase. Currently
* this controls that historic variable updates created during this phase receive
* the initial
flag (see {@link HistoricVariableUpdateEventEntity#isInitial}).
*/
protected transient boolean isStarting = false;
// current position /////////////////////////////////////////////////////////
/**
* current activity
*/
protected transient ActivityImpl activity;
/**
* the activity which is to be started next
*/
protected transient PvmActivity nextActivity;
/**
* the transition that is currently being taken
*/
protected transient TransitionImpl transition;
/**
* A list of outgoing transitions from the current activity
* that are going to be taken
*/
protected transient List transitionsToTake = null;
/**
* the unique id of the current activity instance
*/
protected String activityInstanceId;
/**
* the id of a case associated with this execution
*/
protected String caseInstanceId;
protected PvmExecutionImpl replacedBy;
// cascade deletion ////////////////////////////////////////////////////////
protected boolean deleteRoot;
protected String deleteReason;
protected boolean externallyTerminated;
//state/type of execution //////////////////////////////////////////////////
/**
* indicates if this execution represents an active path of execution.
* Executions are made inactive in the following situations:
*
* - an execution enters a nested scope
* - an execution is split up into multiple concurrent executions, then the parent is made inactive.
* - an execution has arrived in a parallel gateway or join and that join has not yet activated/fired.
* - an execution is ended.
*
*/
protected boolean isActive = true;
protected boolean isScope = true;
protected boolean isConcurrent = false;
protected boolean isEnded = false;
protected boolean isEventScope = false;
protected boolean isRemoved = false;
/**
* transient; used for process instance modification to preserve a scope from getting deleted
*/
protected boolean preserveScope = false;
/**
* marks the current activity instance
*/
protected int activityInstanceState = ActivityInstanceState.DEFAULT.getStateCode();
protected boolean activityInstanceEndListenersFailed = false;
// sequence counter ////////////////////////////////////////////////////////
protected long sequenceCounter = 0;
public PvmExecutionImpl() {
}
// API ////////////////////////////////////////////////
/**
* creates a new execution. properties processDefinition, processInstance and activity will be initialized.
*/
@Override
public abstract PvmExecutionImpl createExecution();
// sub process instance
@Override
public PvmExecutionImpl createSubProcessInstance(PvmProcessDefinition processDefinition) {
return createSubProcessInstance(processDefinition, null);
}
@Override
public PvmExecutionImpl createSubProcessInstance(PvmProcessDefinition processDefinition, String businessKey) {
PvmExecutionImpl processInstance = getProcessInstance();
String caseInstanceId = null;
if (processInstance != null) {
caseInstanceId = processInstance.getCaseInstanceId();
}
return createSubProcessInstance(processDefinition, businessKey, caseInstanceId);
}
@Override
public PvmExecutionImpl createSubProcessInstance(PvmProcessDefinition processDefinition, String businessKey, String caseInstanceId) {
PvmExecutionImpl subProcessInstance = newExecution();
// manage bidirectional super-subprocess relation
subProcessInstance.setSuperExecution(this);
this.setSubProcessInstance(subProcessInstance);
// Initialize the new execution
subProcessInstance.setProcessDefinition((ProcessDefinitionImpl) processDefinition);
subProcessInstance.setProcessInstance(subProcessInstance);
subProcessInstance.setActivity(processDefinition.getInitial());
if (businessKey != null) {
subProcessInstance.setBusinessKey(businessKey);
}
if (caseInstanceId != null) {
subProcessInstance.setCaseInstanceId(caseInstanceId);
}
return subProcessInstance;
}
protected abstract PvmExecutionImpl newExecution();
// sub case instance
@Override
public abstract CmmnExecution createSubCaseInstance(CmmnCaseDefinition caseDefinition);
@Override
public abstract CmmnExecution createSubCaseInstance(CmmnCaseDefinition caseDefinition, String businessKey);
public abstract void initialize();
public abstract void initializeTimerDeclarations();
public void executeIoMapping() {
// execute Input Mappings (if they exist).
ScopeImpl currentScope = getScopeActivity();
if (currentScope != currentScope.getProcessDefinition()) {
ActivityImpl currentActivity = (ActivityImpl) currentScope;
if (currentActivity != null && currentActivity.getIoMapping() != null && !skipIoMapping) {
currentActivity.getIoMapping().executeInputParameters(this);
}
}
}
@Override
public void start() {
start(null);
}
@Override
public void start(Map variables) {
start(variables, null);
}
public void startWithFormProperties(VariableMap formProperties) {
start(null, formProperties);
}
protected void start(Map variables, VariableMap formProperties) {
initialize();
fireHistoricProcessStartEvent();
if (variables != null) {
setVariables(variables);
}
if (formProperties != null) {
FormPropertyHelper.initFormPropertiesOnScope(formProperties, this);
}
initializeTimerDeclarations();
performOperation(PvmAtomicOperation.PROCESS_START);
}
/**
* perform starting behavior but don't execute the initial activity
*
* @param variables the variables which are used for the start
*/
public void startWithoutExecuting(Map variables) {
initialize();
fireHistoricProcessStartEvent();
setActivityInstanceId(getId());
setVariables(variables);
initializeTimerDeclarations();
performOperation(PvmAtomicOperation.FIRE_PROCESS_START);
setActivity(null);
}
public abstract void fireHistoricProcessStartEvent();
@Override
public void destroy() {
destroy(false);
}
/**
* @param alwaysSkipIoMappings set to true to always skip IO mappings,
* regardless of internal state of execution (=> {@link CoreExecution#isSkipIoMappings()})
*/
public void destroy(boolean alwaysSkipIoMappings) {
LOG.destroying(this);
setScope(false);
}
public void removeAllTasks() {
}
protected void removeEventScopes() {
List childExecutions = new ArrayList<>(getEventScopeExecutions());
for (PvmExecutionImpl childExecution : childExecutions) {
LOG.removingEventScope(childExecution);
childExecution.destroy();
childExecution.remove();
}
}
public void clearScope(String reason, boolean skipCustomListeners, boolean skipIoMappings, boolean externallyTerminated) {
this.skipCustomListeners = skipCustomListeners;
this.skipIoMapping = skipIoMappings;
if (getSubProcessInstance() != null) {
getSubProcessInstance().deleteCascade(reason, skipCustomListeners, skipIoMappings, externallyTerminated, false);
}
// remove all child executions and sub process instances:
List executions = new ArrayList<>(getNonEventScopeExecutions());
for (PvmExecutionImpl childExecution : executions) {
if (childExecution.getSubProcessInstance() != null) {
childExecution.getSubProcessInstance().deleteCascade(reason, skipCustomListeners, skipIoMappings, externallyTerminated, false);
}
childExecution.deleteCascade(reason, skipCustomListeners, skipIoMappings, externallyTerminated, false);
}
// fire activity end on active activity
PvmActivity activity = getActivity();
if (isActive && activity != null) {
// set activity instance state to cancel
if (activityInstanceState != ENDING.getStateCode() || activityInstanceEndListenersFailed) {
setCanceled(true);
performOperation(PvmAtomicOperation.FIRE_ACTIVITY_END);
}
// set activity instance state back to 'default'
// -> execution will be reused for executing more activities and we want the state to
// be default initially.
activityInstanceState = ActivityInstanceState.DEFAULT.getStateCode();
}
}
/**
* Interrupts an execution
*/
@Override
public void interrupt(String reason) {
interrupt(reason, false, false, false);
}
public void interrupt(String reason, boolean skipCustomListeners, boolean skipIoMappings, boolean externallyTerminated) {
LOG.interruptingExecution(reason, skipCustomListeners);
clearScope(reason, skipCustomListeners, skipIoMappings, externallyTerminated);
}
/**
* Ends an execution. Invokes end listeners for the current activity and notifies the flow scope execution
* of this happening which may result in the flow scope ending.
*
* @param completeScope true if ending the execution contributes to completing the BPMN 2.0 scope
*/
@Override
public void end(boolean completeScope) {
setCompleteScope(completeScope);
isActive = false;
isEnded = true;
if (hasReplacedParent()) {
getParent().replacedBy = null;
}
performOperation(PvmAtomicOperation.ACTIVITY_NOTIFY_LISTENER_END);
}
@Override
public void endCompensation() {
performOperation(PvmAtomicOperation.FIRE_ACTIVITY_END);
remove();
PvmExecutionImpl parent = getParent();
if (parent.getActivity() == null) {
parent.setActivity((PvmActivity) getActivity().getFlowScope());
}
parent.signal(SIGNAL_COMPENSATION_DONE, null);
}
/**
* Precondition: execution is already ended but this has not been propagated yet.
*
*
Propagates the ending of this execution to the flowscope execution; currently only supports
* the process instance execution
*/
public void propagateEnd() {
if (!isEnded()) {
throw new ProcessEngineException(toString() + " must have ended before ending can be propagated");
}
if (isProcessInstanceExecution()) {
performOperation(PvmAtomicOperation.PROCESS_END);
} else {
// not supported yet
}
}
@Override
public void remove() {
PvmExecutionImpl parent = getParent();
if (parent != null) {
parent.getExecutions().remove(this);
// if the sequence counter is greater than the
// sequence counter of the parent, then set
// the greater sequence counter on the parent.
long parentSequenceCounter = parent.getSequenceCounter();
long mySequenceCounter = getSequenceCounter();
if (mySequenceCounter > parentSequenceCounter) {
parent.setSequenceCounter(mySequenceCounter);
}
// propagate skipping configuration upwards, if it was not initially set on
// the root execution
parent.skipCustomListeners |= this.skipCustomListeners;
parent.skipIoMapping |= this.skipIoMapping;
}
isActive = false;
isEnded = true;
isRemoved = true;
if (hasReplacedParent()) {
getParent().replacedBy = null;
}
removeEventScopes();
}
public boolean isRemoved() {
return isRemoved;
}
public PvmExecutionImpl createConcurrentExecution() {
if (!isScope()) {
throw new ProcessEngineException("Cannot create concurrent execution for " + this);
}
// The following covers the three cases in which a concurrent execution may be created
// (this execution is the root in each scenario).
//
// Note: this should only consider non-event-scope executions. Event-scope executions
// are not relevant for the tree structure and should remain under their original parent.
//
//
// (1) A compacted tree:
//
// Before: After:
// ------- -------
// | e1 | | e1 |
// ------- -------
// / \
// ------- -------
// | e2 | | e3 |
// ------- -------
//
// e2 replaces e1; e3 is the new root for the activity stack to instantiate
//
//
// (2) A single child that is a scope execution
// Before: After:
// ------- -------
// | e1 | | e1 |
// ------- -------
// | / \
// ------- ------- -------
// | e2 | | e3 | | e4 |
// ------- ------- -------
// |
// -------
// | e2 |
// -------
//
//
// e3 is created and is concurrent;
// e4 is the new root for the activity stack to instantiate
//
// (3) Existing concurrent execution(s)
// Before: After:
// ------- ---------
// | e1 | | e1 |
// ------- ---------
// / \ / | \
// ------- ------- ------- ------- -------
// | e2 | .. | eX | | e2 |..| eX | | eX+1|
// ------- ------- ------- ------- -------
//
// eX+1 is concurrent and the new root for the activity stack to instantiate
List children = this.getNonEventScopeExecutions();
// whenever we change the set of child executions we have to force an update
// on the scope executions to avoid concurrent modifications (e.g. tree compaction)
// that go unnoticed
forceUpdate();
if (children.isEmpty()) {
// (1)
PvmExecutionImpl replacingExecution = this.createExecution();
replacingExecution.setConcurrent(true);
replacingExecution.setScope(false);
replacingExecution.replace(this);
this.inactivate();
this.setActivity(null);
} else if (children.size() == 1) {
// (2)
PvmExecutionImpl child = children.get(0);
PvmExecutionImpl concurrentReplacingExecution = this.createExecution();
concurrentReplacingExecution.setConcurrent(true);
concurrentReplacingExecution.setScope(false);
concurrentReplacingExecution.setActive(false);
concurrentReplacingExecution.onConcurrentExpand(this);
child.setParent(concurrentReplacingExecution);
this.leaveActivityInstance();
this.setActivity(null);
}
// (1), (2), and (3)
PvmExecutionImpl concurrentExecution = this.createExecution();
concurrentExecution.setConcurrent(true);
concurrentExecution.setScope(false);
return concurrentExecution;
}
@Override
public boolean tryPruneLastConcurrentChild() {
if (getNonEventScopeExecutions().size() == 1) {
PvmExecutionImpl lastConcurrent = getNonEventScopeExecutions().get(0);
if (lastConcurrent.isConcurrent()) {
if (!lastConcurrent.isScope()) {
setActivity(lastConcurrent.getActivity());
setTransition(lastConcurrent.getTransition());
this.replace(lastConcurrent);
// Move children of lastConcurrent one level up
if (lastConcurrent.hasChildren()) {
for (PvmExecutionImpl childExecution : lastConcurrent.getExecutionsAsCopy()) {
childExecution.setParent(this);
}
}
// Make sure parent execution is re-activated when the last concurrent
// child execution is active
if (!isActive() && lastConcurrent.isActive()) {
setActive(true);
}
lastConcurrent.remove();
} else {
// legacy behavior
LegacyBehavior.pruneConcurrentScope(lastConcurrent);
}
return true;
}
}
return false;
}
@Override
public void deleteCascade(String deleteReason) {
deleteCascade(deleteReason, false, false);
}
public void deleteCascade(String deleteReason, boolean skipCustomListeners, boolean skipIoMappings) {
deleteCascade(deleteReason, skipCustomListeners, skipIoMappings, false, false);
}
public void deleteCascade(String deleteReason, boolean skipCustomListeners, boolean skipIoMappings, boolean externallyTerminated, boolean skipSubprocesses) {
this.deleteReason = deleteReason;
setDeleteRoot(true);
this.isEnded = true;
this.skipCustomListeners = skipCustomListeners;
this.skipIoMapping = skipIoMappings;
this.externallyTerminated = externallyTerminated;
this.skipSubprocesses = skipSubprocesses;
performOperation(PvmAtomicOperation.DELETE_CASCADE);
}
public void executeEventHandlerActivity(ActivityImpl eventHandlerActivity) {
// the target scope
ScopeImpl flowScope = eventHandlerActivity.getFlowScope();
// the event scope (the current activity)
ScopeImpl eventScope = eventHandlerActivity.getEventScope();
if (eventHandlerActivity.getActivityStartBehavior() == ActivityStartBehavior.CONCURRENT_IN_FLOW_SCOPE
&& flowScope != eventScope) {
// the current scope is the event scope of the activity
findExecutionForScope(eventScope, flowScope)
.executeActivity(eventHandlerActivity);
} else {
executeActivity(eventHandlerActivity);
}
}
// tree compaction & expansion ///////////////////////////////////////////
/**
* Returns an execution that has replaced this execution for executing activities in their shared scope.
* Invariant: this execution and getReplacedBy() execute in the same scope.
*/
public abstract PvmExecutionImpl getReplacedBy();
/**
* Instead of {@link #getReplacedBy()}, which returns the execution that this execution was directly replaced with,
* this resolves the chain of replacements (i.e. in the case the replacedBy execution itself was replaced again)
*/
public PvmExecutionImpl resolveReplacedBy() {
// follow the links of execution replacement;
// note: this can be at most two hops:
// case 1:
// this execution is a scope execution
// => tree may have expanded meanwhile
// => scope execution references replacing execution directly (one hop)
//
// case 2:
// this execution is a concurrent execution
// => tree may have compacted meanwhile
// => concurrent execution references scope execution directly (one hop)
//
// case 3:
// this execution is a concurrent execution
// => tree may have compacted/expanded/compacted/../expanded any number of times
// => the concurrent execution has been removed and therefore references the scope execution (first hop)
// => the scope execution may have been replaced itself again with another concurrent execution (second hop)
// note that the scope execution may have a long "history" of replacements, but only the last replacement is relevant here
PvmExecutionImpl replacingExecution = getReplacedBy();
if (replacingExecution != null) {
PvmExecutionImpl secondHopReplacingExecution = replacingExecution.getReplacedBy();
if (secondHopReplacingExecution != null) {
replacingExecution = secondHopReplacingExecution;
}
}
return replacingExecution;
}
public boolean hasReplacedParent() {
return getParent() != null && getParent().getReplacedBy() == this;
}
public boolean isReplacedByParent() {
return getReplacedBy() != null && getReplacedBy() == this.getParent();
}
/**
* Replace an execution by this execution. The replaced execution has a pointer ({@link #getReplacedBy()}) to this execution.
* This pointer is maintained until the replaced execution is removed or this execution is removed/ended.
*
*
This is used for two cases: Execution tree expansion and execution tree compaction
*
* - expansion: Before:
*
* -------
* | e1 | scope
* -------
*
* After:
*
* -------
* | e1 | scope
* -------
* |
* -------
* | e2 | cc (no scope)
* -------
*
* e2 replaces e1: it should receive all entities associated with the activity currently executed
* by e1; these are tasks, (local) variables, jobs (specific for the activity, not the scope)
*
* - compaction: Before:
*
* -------
* | e1 | scope
* -------
* |
* -------
* | e2 | cc (no scope)
* -------
*
* After:
*
* -------
* | e1 | scope
* -------
*
* e1 replaces e2: it should receive all entities associated with the activity currently executed
* by e2; these are tasks, (all) variables, all jobs
*
*
*
* @see #createConcurrentExecution()
* @see #tryPruneLastConcurrentChild()
*/
public void replace(PvmExecutionImpl execution) {
// activity instance id handling
this.activityInstanceId = execution.getActivityInstanceId();
this.isActive = execution.isActive;
this.replacedBy = null;
execution.replacedBy = this;
this.transitionsToTake = execution.transitionsToTake;
execution.leaveActivityInstance();
}
/**
* Callback on tree expansion when this execution is used as the concurrent execution
* where the argument's children become a subordinate to. Note that this case is not the inverse
* of replace because replace has the semantics that the replacing execution can be used to continue
* execution of this execution's activity instance.
*/
public void onConcurrentExpand(PvmExecutionImpl scopeExecution) {
// by default, do nothing
}
// methods that translate to operations /////////////////////////////////////
@Override
public void signal(String signalName, Object signalData) {
if (getActivity() == null) {
throw new PvmException("cannot signal execution " + this.id + ": it has no current activity");
}
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() {
if (this.transition == null) {
throw new PvmException(toString() + ": no transition to take specified");
}
TransitionImpl transitionImpl = transition;
setActivity(transitionImpl.getSource());
// while executing the transition, the activityInstance is 'null'
// (we are not executing an activity)
setActivityInstanceId(null);
setActive(true);
performOperation(PvmAtomicOperation.TRANSITION_NOTIFY_LISTENER_TAKE);
}
/**
* Execute an activity which is not contained in normal flow (having no incoming sequence flows).
* Cannot be called for activities contained in normal flow.
*
* First, the ActivityStartBehavior is evaluated.
* In case the start behavior is not {@link ActivityStartBehavior#DEFAULT}, the corresponding start
* behavior is executed before executing the activity.
*
* For a given activity, the execution on which this method must be called depends on the type of the start behavior:
*
* - CONCURRENT_IN_FLOW_SCOPE: scope execution for {@link PvmActivity#getFlowScope()}
* - INTERRUPT_EVENT_SCOPE: scope execution for {@link PvmActivity#getEventScope()}
* - CANCEL_EVENT_SCOPE: scope execution for {@link PvmActivity#getEventScope()}
*
*
* @param activity the activity to start
*/
@Override
public void executeActivity(PvmActivity activity) {
if (!activity.getIncomingTransitions().isEmpty()) {
throw new ProcessEngineException("Activity is contained in normal flow and cannot be executed using executeActivity().");
}
ActivityStartBehavior activityStartBehavior = activity.getActivityStartBehavior();
if (!isScope() && ActivityStartBehavior.DEFAULT != activityStartBehavior) {
throw new ProcessEngineException("Activity '" + activity + "' with start behavior '" + activityStartBehavior + "'"
+ "cannot be executed by non-scope execution.");
}
PvmActivity activityImpl = activity;
this.isEnded = false;
this.isActive = true;
switch (activityStartBehavior) {
case CONCURRENT_IN_FLOW_SCOPE:
this.nextActivity = activityImpl;
performOperation(PvmAtomicOperation.ACTIVITY_START_CONCURRENT);
break;
case CANCEL_EVENT_SCOPE:
this.nextActivity = activityImpl;
performOperation(PvmAtomicOperation.ACTIVITY_START_CANCEL_SCOPE);
break;
case INTERRUPT_EVENT_SCOPE:
this.nextActivity = activityImpl;
performOperation(PvmAtomicOperation.ACTIVITY_START_INTERRUPT_SCOPE);
break;
default:
setActivity(activityImpl);
setActivityInstanceId(null);
performOperation(PvmAtomicOperation.ACTIVITY_START_CREATE_SCOPE);
break;
}
}
/**
* Instantiates the given activity stack under this execution.
* Sets the variables for the execution responsible to execute the most deeply nested
* activity.
*
* @param activityStack The most deeply nested activity is the last element in the list
*/
public void executeActivitiesConcurrent(List activityStack, PvmActivity targetActivity,
PvmTransition targetTransition, Map variables, Map localVariables,
boolean skipCustomListeners, boolean skipIoMappings) {
ScopeImpl flowScope = null;
if (!activityStack.isEmpty()) {
flowScope = activityStack.get(0).getFlowScope();
} else if (targetActivity != null) {
flowScope = targetActivity.getFlowScope();
} else if (targetTransition != null) {
flowScope = targetTransition.getSource().getFlowScope();
}
PvmExecutionImpl propagatingExecution = null;
if (flowScope.getActivityBehavior() instanceof ModificationObserverBehavior) {
ModificationObserverBehavior flowScopeBehavior = (ModificationObserverBehavior) flowScope.getActivityBehavior();
propagatingExecution = (PvmExecutionImpl) flowScopeBehavior.createInnerInstance(this);
} else {
propagatingExecution = createConcurrentExecution();
}
propagatingExecution.executeActivities(activityStack, targetActivity, targetTransition, variables, localVariables,
skipCustomListeners, skipIoMappings);
}
/**
* Instantiates the given set of activities and returns the execution for the bottom-most activity
*/
public Map instantiateScopes(List activityStack,
boolean skipCustomListeners,
boolean skipIoMappings) {
if (activityStack.isEmpty()) {
return Collections.emptyMap();
}
this.skipCustomListeners = skipCustomListeners;
this.skipIoMapping = skipIoMappings;
ScopeInstantiationContext executionStartContext = new ScopeInstantiationContext();
InstantiationStack instantiationStack = new InstantiationStack(new LinkedList<>(activityStack));
executionStartContext.setInstantiationStack(instantiationStack);
setStartContext(executionStartContext);
performOperation(PvmAtomicOperation.ACTIVITY_INIT_STACK_AND_RETURN);
Map createdExecutions = new HashMap<>();
PvmExecutionImpl currentExecution = this;
for (PvmActivity instantiatedActivity : activityStack) {
// there must exactly one child execution
currentExecution = currentExecution.getNonEventScopeExecutions().get(0);
if (currentExecution.isConcurrent()) {
// there may be a non-scope execution that we have to skip (e.g. multi-instance)
currentExecution = currentExecution.getNonEventScopeExecutions().get(0);
}
createdExecutions.put(instantiatedActivity, currentExecution);
}
return createdExecutions;
}
/**
* Instantiates the given activity stack. Uses this execution to execute the
* highest activity in the stack.
* Sets the variables for the execution responsible to execute the most deeply nested
* activity.
*
* @param activityStack The most deeply nested activity is the last element in the list
*/
public void executeActivities(List activityStack, PvmActivity targetActivity,
PvmTransition targetTransition, Map variables, Map localVariables,
boolean skipCustomListeners, boolean skipIoMappings) {
this.skipCustomListeners = skipCustomListeners;
this.skipIoMapping = skipIoMappings;
this.activityInstanceId = null;
this.isEnded = false;
if (!activityStack.isEmpty()) {
ScopeInstantiationContext executionStartContext = new ScopeInstantiationContext();
InstantiationStack instantiationStack = new InstantiationStack(activityStack, targetActivity, targetTransition);
executionStartContext.setInstantiationStack(instantiationStack);
executionStartContext.setVariables(variables);
executionStartContext.setVariablesLocal(localVariables);
setStartContext(executionStartContext);
performOperation(PvmAtomicOperation.ACTIVITY_INIT_STACK);
} else if (targetActivity != null) {
setVariables(variables);
setVariablesLocal(localVariables);
setActivity(targetActivity);
performOperation(PvmAtomicOperation.ACTIVITY_START_CREATE_SCOPE);
} else if (targetTransition != null) {
setVariables(variables);
setVariablesLocal(localVariables);
setActivity(targetTransition.getSource());
setTransition(targetTransition);
performOperation(PvmAtomicOperation.TRANSITION_START_NOTIFY_LISTENER_TAKE);
}
}
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public List findInactiveConcurrentExecutions(PvmActivity activity) {
List inactiveConcurrentExecutionsInActivity = new ArrayList<>();
if (isConcurrent()) {
return getParent().findInactiveChildExecutions(activity);
} else if (!isActive()) {
inactiveConcurrentExecutionsInActivity.add(this);
}
return (List) inactiveConcurrentExecutionsInActivity;
}
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public List findInactiveChildExecutions(PvmActivity activity) {
List inactiveConcurrentExecutionsInActivity = new ArrayList<>();
List concurrentExecutions = getAllChildExecutions();
for (PvmExecutionImpl concurrentExecution : concurrentExecutions) {
if (concurrentExecution.getActivity() == activity && !concurrentExecution.isActive()) {
inactiveConcurrentExecutionsInActivity.add(concurrentExecution);
}
}
return (List) inactiveConcurrentExecutionsInActivity;
}
protected List getAllChildExecutions() {
List childExecutions = new ArrayList<>();
for (PvmExecutionImpl childExecution : getExecutions()) {
childExecutions.add(childExecution);
childExecutions.addAll(childExecution.getAllChildExecutions());
}
return childExecutions;
}
@Override
public void leaveActivityViaTransition(PvmTransition outgoingTransition) {
leaveActivityViaTransitions(Arrays.asList(outgoingTransition), Collections.emptyList());
}
@Override
public void leaveActivityViaTransitions(List _transitions, List _recyclableExecutions) {
List recyclableExecutions = Collections.emptyList();
if (_recyclableExecutions != null) {
recyclableExecutions = new ArrayList<>(_recyclableExecutions);
}
// if recyclable executions size is greater
// than 1, then the executions are joined and
// the activity is left with 'this' execution,
// if it is not not the last concurrent execution.
// therefore it is necessary to remove the local
// variables (event if it is the last concurrent
// execution).
if (recyclableExecutions.size() > 1) {
removeVariablesLocalInternal();
}
// mark all recyclable executions as ended
// if the list of recyclable executions also
// contains 'this' execution, then 'this' execution
// is also marked as ended. (if 'this' execution is
// pruned, then the local variables are not copied
// to the parent execution)
// this is a workaround to not delete all recyclable
// executions and create a new execution which leaves
// the activity.
for (ActivityExecution execution : recyclableExecutions) {
execution.setEnded(true);
}
// remove 'this' from recyclable executions to
// leave the activity with 'this' execution
// (when 'this' execution is the last concurrent
// execution, then 'this' execution will be pruned,
// and the activity is left with the scope
// execution)
recyclableExecutions.remove(this);
// End all other executions synchronously.
// This ensures a proper execution tree in case
// the activity is marked as 'async-after'.
// Otherwise, ending the other executions as well
// as the next logical operation are executed
// asynchronously. The order of those operations can
// not be guaranteed anymore. This can lead to executions
// getting stuck in case they rely on ending the other
// executions first.
for (ActivityExecution execution : recyclableExecutions) {
execution.setIgnoreAsync(true);
execution.end(_transitions.isEmpty());
}
PvmExecutionImpl propagatingExecution = this;
if (getReplacedBy() != null) {
propagatingExecution = getReplacedBy();
}
propagatingExecution.isActive = true;
propagatingExecution.isEnded = false;
if (_transitions.isEmpty()) {
propagatingExecution.end(!propagatingExecution.isConcurrent());
} else {
propagatingExecution.setTransitionsToTake(_transitions);
propagatingExecution.performOperation(PvmAtomicOperation.TRANSITION_NOTIFY_LISTENER_END);
}
}
protected abstract void removeVariablesLocalInternal();
public boolean isActive(String activityId) {
return findExecution(activityId) != null;
}
@Override
public void inactivate() {
this.isActive = false;
}
// executions ///////////////////////////////////////////////////////////////
@Override
public abstract List getExecutions();
public abstract List getExecutionsAsCopy();
public List getNonEventScopeExecutions() {
List children = getExecutions();
List result = new ArrayList<>();
for (PvmExecutionImpl child : children) {
if (!child.isEventScope()) {
result.add(child);
}
}
return result;
}
public List getEventScopeExecutions() {
List children = getExecutions();
List result = new ArrayList<>();
for (PvmExecutionImpl child : children) {
if (child.isEventScope()) {
result.add(child);
}
}
return result;
}
@Override
public PvmExecutionImpl findExecution(String activityId) {
if ((getActivity() != null)
&& (getActivity().getId().equals(activityId))
) {
return this;
}
for (PvmExecutionImpl nestedExecution : getExecutions()) {
PvmExecutionImpl result = nestedExecution.findExecution(activityId);
if (result != null) {
return result;
}
}
return null;
}
@Override
public List findExecutions(String activityId) {
List matchingExecutions = new ArrayList<>();
collectExecutions(activityId, matchingExecutions);
return matchingExecutions;
}
protected void collectExecutions(String activityId, List executions) {
if ((getActivity() != null)
&& (getActivity().getId().equals(activityId))
) {
executions.add(this);
}
for (PvmExecutionImpl nestedExecution : getExecutions()) {
nestedExecution.collectExecutions(activityId, executions);
}
}
@Override
public List findActiveActivityIds() {
List activeActivityIds = new ArrayList<>();
collectActiveActivityIds(activeActivityIds);
return activeActivityIds;
}
protected void collectActiveActivityIds(List activeActivityIds) {
ActivityImpl activity = getActivity();
if (isActive && activity != null) {
activeActivityIds.add(activity.getId());
}
for (PvmExecutionImpl execution : getExecutions()) {
execution.collectActiveActivityIds(activeActivityIds);
}
}
// business key /////////////////////////////////////////
@Override
public String getProcessBusinessKey() {
return getProcessInstance().getBusinessKey();
}
@Override
public void setProcessBusinessKey(String businessKey) {
final PvmExecutionImpl processInstance = getProcessInstance();
processInstance.setBusinessKey(businessKey);
HistoryLevel historyLevel = Context
.getCommandContext().getProcessEngineConfiguration().getHistoryLevel();
if (historyLevel.isHistoryEventProduced(HistoryEventTypes.PROCESS_INSTANCE_UPDATE, processInstance)) {
HistoryEventProcessor.processHistoryEvents(new HistoryEventProcessor.HistoryEventCreator() {
@Override
public HistoryEvent createHistoryEvent(HistoryEventProducer producer) {
return producer.createProcessInstanceUpdateEvt(processInstance);
}
});
}
}
@Override
public String getBusinessKey() {
if (this.isProcessInstanceExecution()) {
return businessKey;
} else return getProcessBusinessKey();
}
// process definition ///////////////////////////////////////////////////////
public void setProcessDefinition(ProcessDefinitionImpl processDefinition) {
this.processDefinition = processDefinition;
}
public ProcessDefinitionImpl getProcessDefinition() {
return processDefinition;
}
// process instance /////////////////////////////////////////////////////////
/**
* ensures initialization and returns the process instance.
*/
@Override
public abstract PvmExecutionImpl getProcessInstance();
public abstract void setProcessInstance(PvmExecutionImpl pvmExecutionImpl);
// case instance id /////////////////////////////////////////////////////////
public String getCaseInstanceId() {
return caseInstanceId;
}
public void setCaseInstanceId(String caseInstanceId) {
this.caseInstanceId = caseInstanceId;
}
// activity /////////////////////////////////////////////////////////////////
/**
* ensures initialization and returns the activity
*/
@Override
public ActivityImpl getActivity() {
return activity;
}
public String getActivityId() {
ActivityImpl activity = getActivity();
if (activity != null) {
return activity.getId();
} else {
return null;
}
}
@Override
public String getCurrentActivityName() {
ActivityImpl activity = getActivity();
if (activity != null) {
return activity.getName();
} else {
return null;
}
}
@Override
public String getCurrentActivityId() {
return getActivityId();
}
@Override
public void setActivity(PvmActivity activity) {
this.activity = (ActivityImpl) activity;
}
@Override
public void enterActivityInstance() {
ActivityImpl activity = getActivity();
activityInstanceId = generateActivityInstanceId(activity.getId());
LOG.debugEnterActivityInstance(this, getParentActivityInstanceId());
// : in general, io mappings may only exist when the activity is scope
// however, for multi instance activities, the inner activity does not become a scope
// due to the presence of an io mapping. In that case, it is ok to execute the io mapping
// anyway because the multi-instance body already ensures variable isolation
executeIoMapping();
if (activity.isScope()) {
initializeTimerDeclarations();
}
activityInstanceEndListenersFailed = false;
}
public void activityInstanceStarting() {
this.activityInstanceState = ActivityInstanceState.STARTING.getStateCode();
}
public void activityInstanceStarted() {
this.activityInstanceState = ActivityInstanceState.DEFAULT.getStateCode();
}
public void activityInstanceDone() {
this.activityInstanceState = ENDING.getStateCode();
}
public void activityInstanceEndListenerFailure() {
this.activityInstanceEndListenersFailed = true;
}
protected abstract String generateActivityInstanceId(String activityId);
@Override
public void leaveActivityInstance() {
if (activityInstanceId != null) {
LOG.debugLeavesActivityInstance(this, activityInstanceId);
}
activityInstanceId = getParentActivityInstanceId();
activityInstanceState = ActivityInstanceState.DEFAULT.getStateCode();
activityInstanceEndListenersFailed = false;
}
@Override
public String getParentActivityInstanceId() {
if (isProcessInstanceExecution()) {
return getId();
} else {
return getParent().getActivityInstanceId();
}
}
@Override
public void setActivityInstanceId(String activityInstanceId) {
this.activityInstanceId = activityInstanceId;
}
@Override
public String getActivityInstanceId() {
return activityInstanceId;
}
// parent ///////////////////////////////////////////////////////////////////
/**
* ensures initialization and returns the parent
*/
@Override
public abstract PvmExecutionImpl getParent();
@Override
public String getParentId() {
PvmExecutionImpl parent = getParent();
if (parent != null) {
return parent.getId();
} else {
return null;
}
}
public boolean hasChildren() {
return !getExecutions().isEmpty();
}
/**
* Sets the execution's parent and updates the old and new parents' set of
* child executions
*/
@SuppressWarnings("unchecked")
public void setParent(PvmExecutionImpl parent) {
PvmExecutionImpl currentParent = getParent();
setParentExecution(parent);
if (currentParent != null) {
currentParent.getExecutions().remove(this);
}
if (parent != null) {
((List) parent.getExecutions()).add(this);
}
}
/**
* Use #setParent to also update the child execution sets
*/
public abstract void setParentExecution(PvmExecutionImpl parent);
// super- and subprocess executions /////////////////////////////////////////
@Override
public abstract PvmExecutionImpl getSuperExecution();
public abstract void setSuperExecution(PvmExecutionImpl superExecution);
public abstract PvmExecutionImpl getSubProcessInstance();
public abstract void setSubProcessInstance(PvmExecutionImpl subProcessInstance);
// super case execution /////////////////////////////////////////////////////
public abstract CmmnExecution getSuperCaseExecution();
public abstract void setSuperCaseExecution(CmmnExecution superCaseExecution);
// sub case execution ///////////////////////////////////////////////////////
public abstract CmmnExecution getSubCaseInstance();
public abstract void setSubCaseInstance(CmmnExecution subCaseInstance);
// scopes ///////////////////////////////////////////////////////////////////
protected ScopeImpl getScopeActivity() {
ScopeImpl scope = null;
// this if condition is important during process instance startup
// where the activity of the process instance execution may not be aligned
// with the execution tree
if (isProcessInstanceExecution()) {
scope = getProcessDefinition();
} else {
scope = getActivity();
}
return scope;
}
@Override
public boolean isScope() {
return isScope;
}
@Override
public void setScope(boolean isScope) {
this.isScope = isScope;
}
/**
* For a given target flow scope, this method returns the corresponding scope execution.
*
* Precondition: the execution is active and executing an activity.
* Can be invoked for scope and non scope executions.
*
* @param targetFlowScope scope activity or process definition for which the scope execution should be found
* @return the scope execution for the provided targetFlowScope
*/
@Override
public PvmExecutionImpl findExecutionForFlowScope(PvmScope targetFlowScope) {
// if this execution is not a scope execution, use the parent
final PvmExecutionImpl scopeExecution = isScope() ? this : getParent();
ScopeImpl currentActivity = getActivity();
EnsureUtil.ensureNotNull("activity of current execution", currentActivity);
// if this is a scope execution currently executing a non scope activity
currentActivity = currentActivity.isScope() ? currentActivity : currentActivity.getFlowScope();
return scopeExecution.findExecutionForScope(currentActivity, (ScopeImpl) targetFlowScope);
}
public PvmExecutionImpl findExecutionForScope(ScopeImpl currentScope, ScopeImpl targetScope) {
if (!targetScope.isScope()) {
throw new ProcessEngineException("Target scope must be a scope.");
}
Map activityExecutionMapping = createActivityExecutionMapping(currentScope);
PvmExecutionImpl scopeExecution = activityExecutionMapping.get(targetScope);
if (scopeExecution == null) {
// the target scope is scope but no corresponding execution was found
// => legacy behavior
scopeExecution = LegacyBehavior.getScopeExecution(targetScope, activityExecutionMapping);
}
return scopeExecution;
}
public Map createActivityExecutionMapping(ScopeImpl currentScope) {
if (!isScope()) {
throw new ProcessEngineException("Execution must be a scope execution");
}
if (!currentScope.isScope()) {
throw new ProcessEngineException("Current scope must be a scope.");
}
// A single path in the execution tree from a leaf (no child executions) to the root
// may in fact contain multiple executions that correspond to leaves in the activity instance hierarchy.
//
// This is because compensation throwing executions have child executions. In that case, the
// flow scope hierarchy is not aligned with the scope execution hierarchy: There is a scope
// execution for a compensation-throwing event that is an ancestor of this execution,
// while these events are not ancestor scopes of currentScope.
//
// The strategy to deal with this situation is as follows:
// 1. Determine all executions that correspond to leaf activity instances
// 2. Order the leaf executions in top-to-bottom fashion
// 3. Iteratively build the activity execution mapping based on the leaves in top-to-bottom order
// 3.1. For the first leaf, create the activity execution mapping regularly
// 3.2. For every following leaf, rebuild the mapping but reuse any scopes and scope executions
// that are part of the mapping created in the previous iteration
//
// This process ensures that the resulting mapping does not contain scopes that are not ancestors
// of currentScope and that it does not contain scope executions for such scopes.
// For any execution hierarchy that does not involve compensation, the number of iterations in step 3
// should be 1, i.e. there are no other leaf activity instance executions in the hierarchy.
// 1. Find leaf activity instance executions
LeafActivityInstanceExecutionCollector leafCollector = new LeafActivityInstanceExecutionCollector();
new ExecutionWalker(this).addPreVisitor(leafCollector).walkUntil();
List leaves = leafCollector.getLeaves();
leaves.remove(this);
// 2. Order them from top to bottom
Collections.reverse(leaves);
// 3. Iteratively extend the mapping for every additional leaf
Map mapping = new HashMap<>();
for (PvmExecutionImpl leaf : leaves) {
ScopeImpl leafFlowScope = leaf.getFlowScope();
PvmExecutionImpl leafFlowScopeExecution = leaf.getFlowScopeExecution();
mapping = leafFlowScopeExecution.createActivityExecutionMapping(leafFlowScope, mapping);
}
// finally extend the mapping for the current execution
// (note that the current execution need not be a leaf itself)
mapping = this.createActivityExecutionMapping(currentScope, mapping);
return mapping;
}
@Override
public Map createActivityExecutionMapping() {
ScopeImpl currentActivity = getActivity();
EnsureUtil.ensureNotNull("activity of current execution", currentActivity);
ScopeImpl flowScope = getFlowScope();
PvmExecutionImpl flowScopeExecution = getFlowScopeExecution();
return flowScopeExecution.createActivityExecutionMapping(flowScope);
}
protected PvmExecutionImpl getFlowScopeExecution() {
if (!isScope
|| CompensationBehavior.executesNonScopeCompensationHandler(this)
|| isAsyncAfterScopeWithoutTransition())
{
// LEGACY: a correct implementation should also skip a compensation-throwing parent scope execution
// (since compensation throwing activities are scopes), but this cannot be done for backwards compatibility
// where a compensation throwing activity was no scope (and we would wrongly skip an execution in that case)
return getParent().getFlowScopeExecution();
} else {
return this;
}
}
protected ScopeImpl getFlowScope() {
ActivityImpl activity = getActivity();
if (!activity.isScope() || activityInstanceId == null
|| (activity.isScope() && !isScope() && activity.getActivityBehavior() instanceof CompositeActivityBehavior)) {
// if
// - this is a scope execution currently executing a non scope activity
// - or it is not scope but the current activity is (e.g. can happen during activity end, when the actual
// scope execution has been removed and the concurrent parent has been set to the scope activity)
// - or it is asyncBefore/asyncAfter
return activity.getFlowScope();
} else {
return activity;
}
}
/**
* Creates an extended mapping based on this execution and the given existing mapping.
* Any entry mapping
in mapping that corresponds to an ancestor scope of
* currentScope
is reused.
*/
protected Map createActivityExecutionMapping(ScopeImpl currentScope,
final Map mapping) {
if (!isScope()) {
throw new ProcessEngineException("Execution must be a scope execution");
}
if (!currentScope.isScope()) {
throw new ProcessEngineException("Current scope must be a scope.");
}
// collect all ancestor scope executions unless one is encountered that is already in "mapping"
ScopeExecutionCollector scopeExecutionCollector = new ScopeExecutionCollector();
new ExecutionWalker(this)
.addPreVisitor(scopeExecutionCollector)
.walkWhile(new ReferenceWalker.WalkCondition() {
public boolean isFulfilled(PvmExecutionImpl element) {
return element == null || mapping.containsValue(element);
}
});
final List scopeExecutions = scopeExecutionCollector.getScopeExecutions();
// collect all ancestor scopes unless one is encountered that is already in "mapping"
ScopeCollector scopeCollector = new ScopeCollector();
new FlowScopeWalker(currentScope)
.addPreVisitor(scopeCollector)
.walkWhile(new ReferenceWalker.WalkCondition() {
public boolean isFulfilled(ScopeImpl element) {
return element == null || mapping.containsKey(element);
}
});
final List scopes = scopeCollector.getScopes();
// add all ancestor scopes and scopeExecutions that are already in "mapping"
// and correspond to ancestors of the topmost previously collected scope
ScopeImpl topMostScope = scopes.get(scopes.size() - 1);
new FlowScopeWalker(topMostScope.getFlowScope())
.addPreVisitor(new TreeVisitor() {
public void visit(ScopeImpl obj) {
scopes.add(obj);
PvmExecutionImpl priorMappingExecution = mapping.get(obj);
if (priorMappingExecution != null && !scopeExecutions.contains(priorMappingExecution)) {
scopeExecutions.add(priorMappingExecution);
}
}
})
.walkWhile();
if (scopes.size() == scopeExecutions.size()) {
// the trees are in sync
Map result = new HashMap<>();
for (int i = 0; i < scopes.size(); i++) {
result.put(scopes.get(i), scopeExecutions.get(i));
}
return result;
} else {
// Wounderful! The trees are out of sync. This is due to legacy behavior
return LegacyBehavior.createActivityExecutionMapping(scopeExecutions, scopes);
}
}
// toString /////////////////////////////////////////////////////////////////
@Override
public String toString() {
if (isProcessInstanceExecution()) {
return "ProcessInstance[" + getToStringIdentity() + "]";
} else {
return (isConcurrent ? "Concurrent" : "") + (isScope ? "Scope" : "") + "Execution[" + getToStringIdentity() + "]";
}
}
protected String getToStringIdentity() {
return id;
}
// variables ////////////////////////////////////////////
@Override
public String getVariableScopeKey() {
return "execution";
}
@Override
public AbstractVariableScope getParentVariableScope() {
return getParent();
}
/**
* {@inheritDoc}
*/
public void setVariable(String variableName, Object value, String targetActivityId) {
String activityId = getActivityId();
if (activityId != null && activityId.equals(targetActivityId)) {
setVariableLocal(variableName, value);
} else {
PvmExecutionImpl executionForFlowScope = findExecutionForFlowScope(targetActivityId);
if (executionForFlowScope != null) {
executionForFlowScope.setVariableLocal(variableName, value);
}
}
}
/**
* @param targetScopeId - destination scope to be found in current execution tree
* @return execution with activity id corresponding to targetScopeId
*/
protected PvmExecutionImpl findExecutionForFlowScope(final String targetScopeId) {
EnsureUtil.ensureNotNull("target scope id", targetScopeId);
ScopeImpl currentActivity = getActivity();
EnsureUtil.ensureNotNull("activity of current execution", currentActivity);
FlowScopeWalker walker = new FlowScopeWalker(currentActivity);
ScopeImpl targetFlowScope = walker.walkUntil(new ReferenceWalker.WalkCondition() {
@Override
public boolean isFulfilled(ScopeImpl scope) {
return scope == null || scope.getId().equals(targetScopeId);
}
});
if (targetFlowScope == null) {
throw LOG.scopeNotFoundException(targetScopeId, this.getId());
}
return findExecutionForFlowScope(targetFlowScope);
}
// sequence counter ///////////////////////////////////////////////////////////
public long getSequenceCounter() {
return sequenceCounter;
}
public void setSequenceCounter(long sequenceCounter) {
this.sequenceCounter = sequenceCounter;
}
public void incrementSequenceCounter() {
sequenceCounter++;
}
// Getter / Setters ///////////////////////////////////
public boolean isExternallyTerminated() {
return externallyTerminated;
}
public void setExternallyTerminated(boolean externallyTerminated) {
this.externallyTerminated = externallyTerminated;
}
public String getDeleteReason() {
return deleteReason;
}
public void setDeleteReason(String deleteReason) {
this.deleteReason = deleteReason;
}
public boolean isDeleteRoot() {
return deleteRoot;
}
public void setDeleteRoot(boolean deleteRoot) {
this.deleteRoot = deleteRoot;
}
@Override
public TransitionImpl getTransition() {
return transition;
}
public List getTransitionsToTake() {
return transitionsToTake;
}
public void setTransitionsToTake(List transitionsToTake) {
this.transitionsToTake = transitionsToTake;
}
@Override
public String getCurrentTransitionId() {
TransitionImpl transition = getTransition();
if (transition != null) {
return transition.getId();
} else {
return null;
}
}
public void setTransition(PvmTransition transition) {
this.transition = (TransitionImpl) transition;
}
@Override
public boolean isConcurrent() {
return isConcurrent;
}
@Override
public void setConcurrent(boolean isConcurrent) {
this.isConcurrent = isConcurrent;
}
@Override
public boolean isActive() {
return isActive;
}
@Override
public void setActive(boolean isActive) {
this.isActive = isActive;
}
public void setEnded(boolean isEnded) {
this.isEnded = isEnded;
}
@Override
public boolean isEnded() {
return isEnded;
}
@Override
public boolean isCanceled() {
return ActivityInstanceState.CANCELED.getStateCode() == activityInstanceState;
}
public void setCanceled(boolean canceled) {
if (canceled) {
activityInstanceState = ActivityInstanceState.CANCELED.getStateCode();
}
}
@Override
public boolean isCompleteScope() {
return ActivityInstanceState.SCOPE_COMPLETE.getStateCode() == activityInstanceState;
}
public void setCompleteScope(boolean completeScope) {
if (completeScope && !isCanceled()) {
activityInstanceState = ActivityInstanceState.SCOPE_COMPLETE.getStateCode();
}
}
public void setPreserveScope(boolean preserveScope) {
this.preserveScope = preserveScope;
}
public boolean isPreserveScope() {
return preserveScope;
}
public int getActivityInstanceState() {
return activityInstanceState;
}
public boolean isInState(ActivityInstanceState state) {
return activityInstanceState == state.getStateCode();
}
@Override
public boolean hasFailedOnEndListeners() {
return activityInstanceEndListenersFailed;
}
public boolean isEventScope() {
return isEventScope;
}
public void setEventScope(boolean isEventScope) {
this.isEventScope = isEventScope;
}
public ScopeInstantiationContext getScopeInstantiationContext() {
return scopeInstantiationContext;
}
public void disposeScopeInstantiationContext() {
scopeInstantiationContext = null;
PvmExecutionImpl parent = this;
while ((parent = parent.getParent()) != null && parent.scopeInstantiationContext != null) {
parent.scopeInstantiationContext = null;
}
}
@Override
public PvmActivity getNextActivity() {
return nextActivity;
}
@Override
public boolean isProcessInstanceExecution() {
return getParent() == null;
}
public void setStartContext(ScopeInstantiationContext startContext) {
this.scopeInstantiationContext = startContext;
}
@Override
public void setIgnoreAsync(boolean ignoreAsync) {
this.ignoreAsync = ignoreAsync;
}
public boolean isIgnoreAsync() {
return ignoreAsync;
}
public void setStarting(boolean isStarting) {
this.isStarting = isStarting;
}
public boolean isStarting() {
return isStarting;
}
public boolean isProcessInstanceStarting() {
return getProcessInstance().isStarting();
}
public void setProcessInstanceStarting(boolean starting) {
getProcessInstance().setStarting(starting);
}
public void setNextActivity(PvmActivity nextActivity) {
this.nextActivity = nextActivity;
}
public PvmExecutionImpl getParentScopeExecution(boolean considerSuperExecution) {
if (isProcessInstanceExecution()) {
if (considerSuperExecution && getSuperExecution() != null) {
PvmExecutionImpl superExecution = getSuperExecution();
if (superExecution.isScope()) {
return superExecution;
} else {
return superExecution.getParent();
}
} else {
return null;
}
} else {
PvmExecutionImpl parent = getParent();
if (parent.isScope()) {
return parent;
} else {
return parent.getParent();
}
}
}
/**
* Contains the delayed variable events, which will be dispatched on a save point.
*/
protected transient List delayedEvents = new ArrayList<>();
/**
* Delays a given variable event with the given target scope.
*
* @param targetScope the target scope of the variable event
* @param variableEvent the variable event which should be delayed
*/
public void delayEvent(PvmExecutionImpl targetScope, VariableEvent variableEvent) {
DelayedVariableEvent delayedVariableEvent = new DelayedVariableEvent(targetScope, variableEvent);
delayEvent(delayedVariableEvent);
}
/**
* Delays and stores the given DelayedVariableEvent on the process instance.
*
* @param delayedVariableEvent the DelayedVariableEvent which should be store on the process instance
*/
public void delayEvent(DelayedVariableEvent delayedVariableEvent) {
//if process definition has no conditional events the variable events does not have to be delayed
Boolean hasConditionalEvents = this.getProcessDefinition().getProperties().get(BpmnProperties.HAS_CONDITIONAL_EVENTS);
if (hasConditionalEvents == null || !hasConditionalEvents.equals(Boolean.TRUE)) {
return;
}
if (isProcessInstanceExecution()) {
delayedEvents.add(delayedVariableEvent);
} else {
getProcessInstance().delayEvent(delayedVariableEvent);
}
}
/**
* The current delayed variable events.
*
* @return a list of DelayedVariableEvent objects
*/
public List getDelayedEvents() {
if (isProcessInstanceExecution()) {
return delayedEvents;
}
return getProcessInstance().getDelayedEvents();
}
/**
* Cleares the current delayed variable events.
*/
public void clearDelayedEvents() {
if (isProcessInstanceExecution()) {
delayedEvents.clear();
} else {
getProcessInstance().clearDelayedEvents();
}
}
/**
* Dispatches the current delayed variable events and performs the given atomic operation
* if the current state was not changed.
*
* @param atomicOperation the atomic operation which should be executed
*/
public void dispatchDelayedEventsAndPerformOperation(final PvmAtomicOperation atomicOperation) {
dispatchDelayedEventsAndPerformOperation(new Callback() {
@Override
public Void callback(PvmExecutionImpl param) {
param.performOperation(atomicOperation);
return null;
}
});
}
/**
* Dispatches the current delayed variable events and performs the given atomic operation
* if the current state was not changed.
*
* @param continuation the atomic operation continuation which should be executed
*/
public void dispatchDelayedEventsAndPerformOperation(final Callback continuation) {
PvmExecutionImpl execution = this;
if (execution.getDelayedEvents().isEmpty()) {
continueExecutionIfNotCanceled(continuation, execution);
return;
}
continueIfExecutionDoesNotAffectNextOperation(new Callback() {
@Override
public Void callback(PvmExecutionImpl execution) {
dispatchScopeEvents(execution);
return null;
}
}, new Callback(){
@Override
public Void callback(PvmExecutionImpl execution) {
continueExecutionIfNotCanceled(continuation, execution);
return null;
}
}, execution);
}
/**
* Executes the given depending operations with the given execution.
* The execution state will be checked with the help of the activity instance id and activity id of the execution before and after
* the dispatching callback call. If the id's are not changed the
* continuation callback is called.
*
* @param dispatching the callback to dispatch the variable events
* @param continuation the callback to continue with the next atomic operation
* @param execution the execution which is used for the execution
*/
public void continueIfExecutionDoesNotAffectNextOperation(Callback dispatching,
Callback continuation,
PvmExecutionImpl execution) {
String lastActivityId = execution.getActivityId();
String lastActivityInstanceId = getActivityInstanceId(execution);
dispatching.callback(execution);
execution = execution.getReplacedBy() != null ? execution.getReplacedBy() : execution;
String currentActivityInstanceId = getActivityInstanceId(execution);
String currentActivityId = execution.getActivityId();
//if execution was canceled or was changed during the dispatch we should not execute the next operation
//since another atomic operation was executed during the dispatching
if (!execution.isCanceled() && isOnSameActivity(lastActivityInstanceId, lastActivityId, currentActivityInstanceId, currentActivityId)) {
continuation.callback(execution);
}
}
protected void continueExecutionIfNotCanceled(Callback continuation, PvmExecutionImpl execution) {
if (continuation != null && !execution.isCanceled()) {
continuation.callback(execution);
}
}
/**
* Dispatches the current delayed variable events on the scope of the given execution.
*
* @param execution the execution on which scope the delayed variable should be dispatched
*/
protected void dispatchScopeEvents(PvmExecutionImpl execution) {
PvmExecutionImpl scopeExecution = execution.isScope() ? execution : execution.getParent();
List delayedEvents = new ArrayList<>(scopeExecution.getDelayedEvents());
scopeExecution.clearDelayedEvents();
Map activityInstanceIds = new HashMap<>();
Map activityIds = new HashMap<>();
initActivityIds(delayedEvents, activityInstanceIds, activityIds);
//For each delayed variable event we have to check if the delayed event can be dispatched,
//the check will be done with the help of the activity id and activity instance id.
//That means it will be checked if the dispatching changed the execution tree in a way that we can't dispatch the
//the other delayed variable events. We have to check the target scope with the last activity id and activity instance id
//and also the replace pointer if it exist. Because on concurrency the replace pointer will be set on which we have
//to check the latest state.
for (DelayedVariableEvent event : delayedEvents) {
PvmExecutionImpl targetScope = event.getTargetScope();
PvmExecutionImpl replaced = targetScope.getReplacedBy() != null ? targetScope.getReplacedBy() : targetScope;
dispatchOnSameActivity(targetScope, replaced, activityIds, activityInstanceIds, event);
}
}
/**
* Initializes the given maps with the target scopes and current activity id's and activity instance id's.
*
* @param delayedEvents the delayed events which contains the information about the target scope
* @param activityInstanceIds the map which maps target scope to activity instance id
* @param activityIds the map which maps target scope to activity id
*/
protected void initActivityIds(List delayedEvents,
Map activityInstanceIds,
Map activityIds) {
for (DelayedVariableEvent event : delayedEvents) {
PvmExecutionImpl targetScope = event.getTargetScope();
String targetScopeActivityInstanceId = getActivityInstanceId(targetScope);
activityInstanceIds.put(targetScope, targetScopeActivityInstanceId);
activityIds.put(targetScope, targetScope.getActivityId());
}
}
/**
* Dispatches the delayed variable event, if the target scope and replaced by scope (if target scope was replaced) have the
* same activity Id's and activity instance id's.
*
* @param targetScope the target scope on which the event should be dispatched
* @param replacedBy the replaced by pointer which should have the same state
* @param activityIds the map which maps scope to activity id
* @param activityInstanceIds the map which maps scope to activity instance id
* @param delayedVariableEvent the delayed variable event which should be dispatched
*/
private void dispatchOnSameActivity(PvmExecutionImpl targetScope, PvmExecutionImpl replacedBy,
Map activityIds,
Map activityInstanceIds,
DelayedVariableEvent delayedVariableEvent) {
//check if the target scope has the same activity id and activity instance id
//since the dispatching was started
String currentActivityInstanceId = getActivityInstanceId(targetScope);
String currentActivityId = targetScope.getActivityId();
final String lastActivityInstanceId = activityInstanceIds.get(targetScope);
final String lastActivityId = activityIds.get(targetScope);
boolean onSameAct = isOnSameActivity(lastActivityInstanceId, lastActivityId, currentActivityInstanceId, currentActivityId);
//If not we have to check the replace pointer,
//which was set if a concurrent execution was created during the dispatching.
if (targetScope != replacedBy && !onSameAct) {
currentActivityInstanceId = getActivityInstanceId(replacedBy);
currentActivityId = replacedBy.getActivityId();
onSameAct = isOnSameActivity(lastActivityInstanceId, lastActivityId, currentActivityInstanceId, currentActivityId);
}
//dispatching
if (onSameAct && isOnDispatchableState(targetScope)) {
targetScope.dispatchEvent(delayedVariableEvent.getEvent());
}
}
/**
* Checks if the given execution is on a dispatchable state.
* That means if the current activity is not a leaf in the activity tree OR
* it is a leaf but not a scope OR it is a leaf, a scope
* and the execution is in state DEFAULT, which means not in state
* Starting, Execute or Ending. For this states it is
* prohibited to trigger conditional events, otherwise unexpected behavior can appear.
*
* @return true if the execution is on a dispatchable state, false otherwise
*/
private boolean isOnDispatchableState(PvmExecutionImpl targetScope) {
ActivityImpl targetActivity = targetScope.getActivity();
return
//if not leaf, activity id is null -> dispatchable
targetScope.getActivityId() == null ||
// if leaf and not scope -> dispatchable
!targetActivity.isScope() ||
// if leaf, scope and state in default -> dispatchable
(targetScope.isInState(ActivityInstanceState.DEFAULT));
}
/**
* Compares the given activity instance id's and activity id's to check if the execution is on the same
* activity as before an operation was executed. The activity instance id's can be null on transitions.
* In this case the activity Id's have to be equal, otherwise the execution changed.
*
* @param lastActivityInstanceId the last activity instance id
* @param lastActivityId the last activity id
* @param currentActivityInstanceId the current activity instance id
* @param currentActivityId the current activity id
* @return true if the execution is on the same activity, otherwise false
*/
private boolean isOnSameActivity(String lastActivityInstanceId, String lastActivityId,
String currentActivityInstanceId, String currentActivityId) {
return
//activityInstanceId's can be null on transitions, so the activityId must be equal
((lastActivityInstanceId == null && lastActivityInstanceId == currentActivityInstanceId && lastActivityId.equals(currentActivityId))
//if activityInstanceId's are not null they must be equal -> otherwise execution changed
|| (lastActivityInstanceId != null && lastActivityInstanceId.equals(currentActivityInstanceId)
&& (lastActivityId == null || lastActivityId.equals(currentActivityId))));
}
/**
* Returns the activity instance id for the given execution.
*
* @param targetScope the execution for which the activity instance id should be returned
* @return the activity instance id
*/
private String getActivityInstanceId(PvmExecutionImpl targetScope) {
if (targetScope.isConcurrent()) {
return targetScope.getActivityInstanceId();
} else {
ActivityImpl targetActivity = targetScope.getActivity();
if ((targetActivity != null && targetActivity.getActivities().isEmpty())) {
return targetScope.getActivityInstanceId();
} else {
return targetScope.getParentActivityInstanceId();
}
}
}
/**
* Returns the newest incident in this execution
*
* @param incidentType the type of new incident
* @param configuration configuration of the incident
* @return new incident
*/
@Override
public Incident createIncident(String incidentType, String configuration) {
return createIncident(incidentType, configuration, null);
}
public Incident createIncident(String incidentType, String configuration, String message) {
IncidentContext incidentContext = createIncidentContext(configuration);
return IncidentHandling.createIncident(incidentType, incidentContext, message);
}
protected IncidentContext createIncidentContext(String configuration) {
IncidentContext incidentContext = new IncidentContext();
incidentContext.setTenantId(this.getTenantId());
incidentContext.setProcessDefinitionId(this.getProcessDefinitionId());
incidentContext.setExecutionId(this.getId());
incidentContext.setActivityId(this.getActivityId());
incidentContext.setConfiguration(configuration);
return incidentContext;
}
/**
* Resolves an incident with given id.
*
* @param incidentId
*/
@Override
public void resolveIncident(final String incidentId) {
IncidentEntity incident = (IncidentEntity) Context
.getCommandContext()
.getIncidentManager()
.findIncidentById(incidentId);
IncidentContext incidentContext = new IncidentContext(incident);
IncidentHandling.removeIncidents(incident.getIncidentType(), incidentContext, true);
}
public IncidentHandler findIncidentHandler(String incidentType) {
Map incidentHandlers = Context.getProcessEngineConfiguration().getIncidentHandlers();
return incidentHandlers.get(incidentType);
}
public boolean isExecutingScopeLeafActivity() {
return isActive && getActivity() != null && getActivity().isScope() && activityInstanceId != null
&& !(getActivity().getActivityBehavior() instanceof CompositeActivityBehavior);
}
/**
* This case is special, because the execution tree is different if an async after
* activity is left via transition (case 1) or not (case 2). In case 1, when the
* execution becomes async, the scope execution is already removed. In case 2 it is not.
*
* @return true if
*
* - the execution is in asyncAfter state and completes
*
- leaves a scope activity
*
- completes the parent scope (i.e. does not leave via a transition
* but propagates control to the parent)
*
*/
public boolean isAsyncAfterScopeWithoutTransition() {
return activityInstanceId == null && activity.isScope() && !isActive;
}
}