/* Copyright 2010-2012 Alfresco Software, Ltd. * Copyright 2012 Thorben Lindhauer * * Licensed under the Apache License, ersion 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.pvm.runtime; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; 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.TransitionImpl; /** * @author Tom Baeyens * @author Joram Barrez */ public class ExecutionImpl implements Serializable, ActivityExecution, ExecutionListenerExecution, PvmExecution, InterpretableExecution { private static final long serialVersionUID = 1L; private static Logger log = Logger.getLogger(ExecutionImpl.class.getName()); // 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; /** * the process instance. this is the root of the execution tree. the * processInstance of a process instance is a self reference. */ protected ExecutionImpl processInstance; /** the parent execution */ protected ExecutionImpl parent; /** nested executions representing scopes or concurrent paths */ protected List<ExecutionImpl> executions; /** super execution, not-null if this execution is part of a subprocess */ protected ExecutionImpl superExecution; /** * reference to a subprocessinstance, not-null if currently subprocess is * started from this execution */ protected ExecutionImpl subProcessInstance; /** only available until the process instance is started */ 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; protected Map<String, Object> variables = null; // events // /////////////////////////////////////////////////////////////////// protected String eventName; protected PvmProcessElement eventSource; protected int executionListenerIndex = 0; // 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 ExecutionImpl 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; /* Default constructor for ibatis/jpa/etc. */ public ExecutionImpl() { } public ExecutionImpl(ActivityImpl initial) { startingExecution = new StartingExecution(initial); } // lifecycle methods // //////////////////////////////////////////////////////// /** * creates a new execution. properties processDefinition, processInstance * and activity will be initialized. */ public ExecutionImpl createExecution() { // create the new child execution ExecutionImpl 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()); return createdExecution; } /** instantiates a new execution. can be overridden by subclasses */ protected ExecutionImpl newExecution() { return new ExecutionImpl(); } public PvmProcessInstance createSubProcessInstance( PvmProcessDefinition processDefinition) { ExecutionImpl subProcessInstance = newExecution(); // manage bidirectional super-subprocess relation subProcessInstance.setSuperExecution(this); this.setSubProcessInstance(subProcessInstance); // Initialize the new execution subProcessInstance .setProcessDefinition((ProcessDefinitionImpl) processDefinition); subProcessInstance.setProcessInstance(subProcessInstance); return subProcessInstance; } public void initialize() { } public void destroy() { setScope(false); } public void remove() { ensureParentInitialized(); if (parent != null) { parent.ensureExecutionsInitialized(); parent.executions.remove(this); } // remove event scopes: List<InterpretableExecution> childExecutions = new ArrayList<InterpretableExecution>( getExecutions()); for (InterpretableExecution childExecution : childExecutions) { if (childExecution.isEventScope()) { log.fine("removing eventScope " + childExecution); childExecution.destroy(); childExecution.remove(); } } } // parent // /////////////////////////////////////////////////////////////////// /** ensures initialization and returns the parent */ public ExecutionImpl getParent() { ensureParentInitialized(); return parent; } /** * all updates need to go through this setter as subclasses can override * this method */ public void setParent(InterpretableExecution parent) { this.parent = (ExecutionImpl) parent; } /** * must be called before memberfield parent is used. can be used by * subclasses to provide parent member field initialization. */ protected void ensureParentInitialized() { } // executions // /////////////////////////////////////////////////////////////// /** ensures initialization and returns the non-null executions list */ public List<ExecutionImpl> getExecutions() { ensureExecutionsInitialized(); return executions; } public ExecutionImpl getSuperExecution() { ensureSuperExecutionInitialized(); return superExecution; } public void setSuperExecution(ExecutionImpl superExecution) { this.superExecution = superExecution; if (superExecution != null) { superExecution.setSubProcessInstance(null); } } // Meant to be overridden by persistent subclasseses protected void ensureSuperExecutionInitialized() { } public ExecutionImpl getSubProcessInstance() { ensureSubProcessInstanceInitialized(); return subProcessInstance; } public void setSubProcessInstance(InterpretableExecution subProcessInstance) { this.subProcessInstance = (ExecutionImpl) subProcessInstance; } // Meant to be overridden by persistent subclasses protected void ensureSubProcessInstanceInitialized() { } public void deleteCascade(String deleteReason) { this.deleteReason = deleteReason; this.deleteRoot = true; performOperation(AtomicOperation.DELETE_CASCADE); } /** * 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); } /** searches for an execution positioned in the given activity */ public ExecutionImpl findExecution(String activityId) { if ((getActivity() != null) && (getActivity().getId().equals(activityId))) { return this; } for (ExecutionImpl nestedExecution : getExecutions()) { ExecutionImpl 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 (ExecutionImpl execution : executions) { execution.collectActiveActivityIds(activeActivityIds); } } /** * must be called before memberfield executions is used. can be used by * subclasses to provide executions member field initialization. */ protected void ensureExecutionsInitialized() { if (executions == null) { executions = new ArrayList<ExecutionImpl>(); } } // process definition // /////////////////////////////////////////////////////// /** ensures initialization and returns the process definition. */ public ProcessDefinitionImpl getProcessDefinition() { ensureProcessDefinitionInitialized(); return processDefinition; } public String getProcessDefinitionId() { return getProcessDefinition().getId(); } /** * for setting the process definition, this setter must be used as * subclasses can override */ /** * must be called before memberfield processDefinition is used. can be used * by subclasses to provide processDefinition member field initialization. */ protected void ensureProcessDefinitionInitialized() { } // process instance // ///////////////////////////////////////////////////////// /** ensures initialization and returns the process instance. */ public ExecutionImpl getProcessInstance() { ensureProcessInstanceInitialized(); return processInstance; } public String getProcessInstanceId() { return getProcessInstance().getId(); } public String getBusinessKey() { return getProcessInstance().getBusinessKey(); } public String getProcessBusinessKey() { return getProcessInstance().getBusinessKey(); } /** * for setting the process instance, this setter must be used as subclasses * can override */ public void setProcessInstance(InterpretableExecution processInstance) { this.processInstance = (ExecutionImpl) processInstance; } /** * must be called before memberfield processInstance is used. can be used by * subclasses to provide processInstance member field initialization. */ protected void ensureProcessInstanceInitialized() { } // activity // ///////////////////////////////////////////////////////////////// /** ensures initialization and returns the activity */ public ActivityImpl getActivity() { ensureActivityInitialized(); return activity; } /** * sets the current activity. can be overridden by subclasses. doesn't * require initialization. */ public void setActivity(ActivityImpl activity) { this.activity = activity; } /** * must be called before the activity member field or getActivity() is * called */ protected void ensureActivityInitialized() { } // scopes // /////////////////////////////////////////////////////////////////// protected void ensureScopeInitialized() { } public boolean isScope() { return isScope; } public void setScope(boolean isScope) { this.isScope = isScope; } // process instance start implementation // //////////////////////////////////// public void start() { if (startingExecution == null && isProcessInstance()) { startingExecution = new StartingExecution( processDefinition.getInitial()); } performOperation(AtomicOperation.PROCESS_START); } // 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"); } 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() .getExecutions(); for (ActivityExecution concurrentExecution : concurrentExecutions) { if (concurrentExecution.getActivity() == activity) { if (concurrentExecution.isActive()) { throw new PvmException( "didn't expect active execution in " + activity + ". bug?"); } 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; } @SuppressWarnings("unchecked") 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 (((ExecutionImpl) recyclableExecution).isScope()) { throw new PvmException( "joining scope executions is not allowed"); } } } ExecutionImpl concurrentRoot = ((isConcurrent && !isScope) ? getParent() : this); List<ExecutionImpl> concurrentActiveExecutions = new ArrayList<ExecutionImpl>(); for (ExecutionImpl execution : concurrentRoot.getExecutions()) { if (execution.isActive()) { concurrentActiveExecutions.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())) { List<ExecutionImpl> recyclableExecutionImpls = (List) recyclableExecutions; for (ExecutionImpl 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 reused: " + recyclableExecutions); // first create the concurrent executions while (!transitions.isEmpty()) { PvmTransition outgoingTransition = transitions.remove(0); ExecutionImpl outgoingExecution = null; if (recyclableExecutions.isEmpty()) { outgoingExecution = concurrentRoot.createExecution(); log.fine("new " + outgoingExecution + " created to take transition " + outgoingTransition); } else { outgoingExecution = (ExecutionImpl) recyclableExecutions .remove(0); log.fine("recycled " + outgoingExecution + " to take transition " + outgoingTransition); } outgoingExecution.setActive(true); outgoingExecution.setScope(false); outgoingExecution.setConcurrent(true); 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(); } } } public void performOperation(AtomicOperation executionOperation) { this.nextOperation = executionOperation; if (!isOperating) { isOperating = true; while (nextOperation != null) { AtomicOperation currentOperation = this.nextOperation; this.nextOperation = null; if (log.isLoggable(Level.FINEST)) { log.finest("AtomicOperation: " + currentOperation + " on " + this); } currentOperation.execute(this); } isOperating = false; } } public boolean isActive(String activityId) { return findExecution(activityId) != null; } // variables // //////////////////////////////////////////////////////////////// public Object getVariable(String variableName) { ensureVariablesInitialized(); // If value is found in this scope, return it if (variables.containsKey(variableName)) { return variables.get(variableName); } // If value not found in this scope, check the parent scope ensureParentInitialized(); if (parent != null) { return parent.getVariable(variableName); } // Variable is nowhere to be found return null; } public Map<String, Object> getVariables() { Map<String, Object> collectedVariables = new HashMap<String, Object>(); collectVariables(collectedVariables); return collectedVariables; } protected void collectVariables(Map<String, Object> collectedVariables) { ensureParentInitialized(); if (parent != null) { parent.collectVariables(collectedVariables); } ensureVariablesInitialized(); for (String variableName : variables.keySet()) { collectedVariables.put(variableName, variables.get(variableName)); } } public void setVariables(Map<String, ? extends Object> variables) { ensureVariablesInitialized(); if (variables != null) { for (String variableName : variables.keySet()) { setVariable(variableName, variables.get(variableName)); } } } public void setVariable(String variableName, Object value) { ensureVariablesInitialized(); if (variables.containsKey(variableName)) { setVariableLocally(variableName, value); } else { ensureParentInitialized(); if (parent != null) { parent.setVariable(variableName, value); } else { setVariableLocally(variableName, value); } } } public void setVariableLocally(String variableName, Object value) { log.fine("setting variable '" + variableName + "' to value '" + value + "' on " + this); variables.put(variableName, value); } public boolean hasVariable(String variableName) { ensureVariablesInitialized(); if (variables.containsKey(variableName)) { return true; } ensureParentInitialized(); if (parent != null) { return parent.hasVariable(variableName); } return false; } protected void ensureVariablesInitialized() { if (variables == null) { variables = new HashMap<String, Object>(); } } // toString // ///////////////////////////////////////////////////////////////// public String toString() { if (isProcessInstance()) { return "ProcessInstance[" + getToStringIdentity() + "]"; } else { return (isEventScope ? "EventScope" : "") + (isConcurrent ? "Concurrent" : "") + (isScope() ? "Scope" : "") + "Execution[" + getToStringIdentity() + "]"; } } protected String getToStringIdentity() { return Integer.toString(System.identityHashCode(this)); } // customized getters and setters // /////////////////////////////////////////// public boolean isProcessInstance() { ensureParentInitialized(); return parent == null; } public void inactivate() { this.isActive = false; } // allow for subclasses to expose a real id // ///////////////////////////////// public String getId() { return null; } // getters and setters // ////////////////////////////////////////////////////// public TransitionImpl getTransition() { return transition; } public void setTransition(TransitionImpl transition) { this.transition = transition; } 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 void setProcessDefinition(ProcessDefinitionImpl processDefinition) { this.processDefinition = processDefinition; } 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 ExecutionImpl getReplacedBy() { return replacedBy; } public void setReplacedBy(InterpretableExecution replacedBy) { this.replacedBy = (ExecutionImpl) replacedBy; } public void setExecutions(List<ExecutionImpl> executions) { this.executions = executions; } public boolean isDeleteRoot() { return deleteRoot; } public void createVariableLocal(String variableName, Object value) { } public void createVariablesLocal(Map<String, ? extends Object> variables) { } public Object getVariableLocal(Object variableName) { return null; } public Set<String> getVariableNames() { return null; } public Set<String> getVariableNamesLocal() { return null; } public Map<String, Object> getVariablesLocal() { return null; } public boolean hasVariableLocal(String variableName) { return false; } public boolean hasVariables() { return false; } public boolean hasVariablesLocal() { return false; } public void removeVariable(String variableName) { } public void removeVariableLocal(String variableName) { } public void removeVariables() { } public void removeVariablesLocal() { } public Object setVariableLocal(String variableName, Object value) { return null; } public void setVariablesLocal(Map<String, ? extends Object> variables) { } public boolean isEventScope() { return isEventScope; } public void setEventScope(boolean isEventScope) { this.isEventScope = isEventScope; } public StartingExecution getStartingExecution() { return startingExecution; } public void disposeStartingExecution() { startingExecution = null; } }