/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jbpm.pvm.impl;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jbpm.PvmException;
import org.jbpm.env.Environment;
import org.jbpm.env.session.Message;
import org.jbpm.env.session.MessageSession;
import org.jbpm.pvm.Activity;
import org.jbpm.pvm.Comment;
import org.jbpm.pvm.Event;
import org.jbpm.pvm.Execution;
import org.jbpm.pvm.Node;
import org.jbpm.pvm.ObservableElement;
import org.jbpm.pvm.ProcessDefinition;
import org.jbpm.pvm.ProcessElement;
import org.jbpm.pvm.Transition;
import org.jbpm.pvm.VariableDefinition;
import org.jbpm.pvm.VariableScope;
import org.jbpm.pvm.log.ProcessLog;
import org.jbpm.util.Priority;
/**
* @author Tom Baeyens
*/
public class ExecutionImpl implements Execution, Serializable {
private static final long serialVersionUID = 1L;
private static Logger log = Logger.getLogger(Execution.class.getName());
// atomic operations
static final AtomicOperation EXECUTE_NODE = new ExecuteNodeOp();
static final AtomicOperation PROCEED_TO_DESTINATION = new ProceedToDestinationOp();
static final AtomicOperation TAKE_TRANSITION = new TakeTransitionOp();
static final AtomicOperation PROPAGATE_TO_PARENT = new MoveToParentNodeOp();
static final AtomicOperation DEFAULT_SIGNAL = new SignalOp(null, null);
static enum UserCodeType {
EVENT_ACTIVITY, NODE_BEHAVIOUR, CONDITION, EXCEPTION_HANDLER,CLIENTAPII
}
protected long dbid;
protected int dbversion;
protected String key;
protected String name;
/** @see Execution */
protected String state = Execution.STATE_ACTIVE;
protected ProcessDefinitionImpl processDefinition;
// current position /////////////////////////////////////////////////////////
protected NodeImpl node;
/** transition is not to be made persistable by default */
protected TransitionImpl transition;
/** the node from which the transition was taken. This can be different from
* the transition source in case a transition of an eclosing node was taken.
* transitionOrigin is not to be made persistable by default */
protected NodeImpl transitionOrigin;
protected EventImpl event;
protected ObservableElementImpl eventSource;
/** are concurrent executions that related to this execution. */
protected Collection<ExecutionImpl> executions;
/** the parent child relation of executions is convenient for some forms of
* concurrency. */
protected ExecutionImpl parent = null;
protected ExecutionImpl processInstance;
protected Set<CommentImpl> comments;
protected int priority = Priority.NORMAL;
/** maintains the index of the next log record. That way, the logs don't
* have to be loaded to add one. Instead, for each log that is added to
* this execution, the nextLogIndex is used and incremented. */
protected int nextLogIndex;
/** caches the child executions by execution name. This member might be
* null and is only created from the executions in case its needed. Note
* that not all executions are forced to have a name and duplicates are allowed.
* In case the {@link #executions} change, the executionsMap can be nulled or
* also updated (but a check needs to be added wether it exists). */
protected transient Map<String, Execution> executionsMap = null;
/** stack of runtime variable scopes. An execution scope can maintain
* runtime information related to a local processDefinition scope. Scopes of
* parent executions should be considered as appended to this stack.
* Execution scopes are ordered from inner to outer. So when searching
* for a scope, it should be done in the normal iteration order.
* New execution scopes should always be added as the first element. */
protected List<VariableScope> variableScopes;
// transient members
/** the queue of atomic operations to be performed for this execution. */
protected Queue<AtomicOperation> atomicOperations;
enum Propagation {
UNSPECIFIED, WAIT, EXPLICIT
}
protected Propagation propagation = null;
protected Node previousNode;
protected Transition previousTransition;
protected UserCodeType userCodeType;
protected Exception exception;
// It's important that this refers to a separate entity. This
// execution must do nullpointercheck before accessing the
// process modifications. That way, good performance is guaranteed
// for the most common scenario: a persistent execution without
// process modifications.
protected ProcessModificationsImpl processModifications;
// construction /////////////////////////////////////////////////////////////
/** invoked as part of {@link ProcessDefinitionImpl#startExecution()}. Specific processDefinition
* languages can customize the start behaviour by subclassing and overriding this method. */
public void start() {
fire(ProcessDefinition.EVENT_PROCESS_START, processDefinition);
if (node!=null) {
performAtomicOperation(EXECUTE_NODE);
}
}
// basic object methods /////////////////////////////////////////////////////
public String toString() {
if (name!=null) {
return "execution["+name+"]";
}
return "execution";
}
// execution method : signal ////////////////////////////////////////////////
/** @see Execution#signal() */
public void signal() {
signal(null, null);
}
/** @see Execution#signal(String) */
public void signal(String signal) {
signal(signal, null);
}
/** @see Execution#signal(Map) */
public void signal(Map<String, Object> parameters) {
signal(null, parameters);
}
/** @see Execution#signal(String, Map) */
public void signal(String signal, Map<String, Object> parameters) {
if (userCodeType==UserCodeType.EVENT_ACTIVITY) {
throw new PvmException("invocation of signal is not allowed inside an event. only node behaviour activities can control the propagation of execution");
}
checkLock();
if (node!=null) {
performAtomicOperation(new SignalOp(signal, parameters));
} else if (transition!=null) {
performAtomicOperation(PROCEED_TO_DESTINATION);
} else {
throw new PvmException("execution is not in a node or in a transition");
}
}
// execution method : take ////////////////////////////////////////////////
/** @see Execution#takeDefaultTransition() */
public void takeDefaultTransition() {
if (userCodeType==UserCodeType.EVENT_ACTIVITY) {
throw new PvmException("invocation of takeDefaultTransition is not allowed inside an event. only node behaviour activities can control the propagation of execution");
}
TransitionImpl defaultTransition = node.getDefaultTransition();
if (defaultTransition==null) {
throw new PvmException("there is no default transition in "+node);
}
take(defaultTransition);
}
/** @see Execution#take(String) */
public void take(String transitionName) {
if (userCodeType==UserCodeType.EVENT_ACTIVITY) {
throw new PvmException("invocation of take is not allowed inside an event. only node behaviour activities can control the propagation of execution");
}
if (node==null) {
throw new PvmException(toString()+" is not positioned in node");
}
TransitionImpl transition = findTransition(transitionName);
if (transition==null) {
throw new PvmException("there is no transition "+transitionName+" in "+node);
}
take(transition);
}
/** @see Execution#takeDefaultTransition() */
public void take(Transition transition) {
if (userCodeType==UserCodeType.EVENT_ACTIVITY) {
throw new PvmException("invocation of take is not allowed inside an event. only node behaviour activities can control the propagation of execution");
}
checkLock();
this.propagation = Propagation.EXPLICIT;
this.transition = (TransitionImpl) transition;
// copy the current node as the transition origin. the origin can be different from
// the transition source in case a transition is taken from an enclosing node
this.transitionOrigin = node;
this.previousTransition = null;
performAtomicOperation(TAKE_TRANSITION);
}
// execution method : execute ///////////////////////////////////////////////
/** @see Execution#execute(String) */
public void execute(String nodeName) {
if (node==null) {
throw new PvmException("node is null");
}
Node nestedNode = node.getNode(nodeName);
if (nestedNode==null) {
throw new PvmException("node "+nodeName+" doesn't exist in "+node);
}
execute(nestedNode);
}
/** @see Execution#execute(Node) */
public void execute(Node node) {
if (userCodeType==UserCodeType.EVENT_ACTIVITY) {
throw new PvmException("invocation of execute is not allowed inside an event. only node behaviour activities can control the propagation of execution");
}
if (node==null) {
throw new PvmException("node is null");
}
checkLock();
this.propagation = Propagation.EXPLICIT;
performAtomicOperation(new MoveToChildNodeOp((NodeImpl) node));
}
// execution method : waitForSignal /////////////////////////////////////////
public void waitForSignal() {
if (userCodeType==UserCodeType.EVENT_ACTIVITY) {
throw new PvmException("invocation of waitForSignal is not allowed inside an event. only node behaviour activities can control the propagation of execution");
}
propagation = Propagation.WAIT;
}
// execution method : proceed ///////////////////////////////////////////////
public void proceed() {
if (userCodeType==UserCodeType.EVENT_ACTIVITY) {
throw new PvmException("invocation of proceed is not allowed inside an event. only node behaviour activities can control the propagation of execution");
}
checkLock();
// in graph based processDefinition langauges we assume that a
// default transition is available
TransitionImpl defaultTransition = findDefaultTransition();
if (defaultTransition!=null) {
take(defaultTransition);
// in block structured processDefinition languages we assume that
// there is no default transition and that there is a
// parent node of the current node
} else {
NodeImpl parentNode = node.getParentNode();
// if there is a parent node
if (parentNode!=null) {
// propagate to the parent
performAtomicOperation(PROPAGATE_TO_PARENT);
} else {
// When we don't know how to proceed, i don't know if it's best to
// throw new PvmException("don't know how to proceed");
// or to end the execution. Because of convenience for testing,
// I opted to end the execution.
end();
}
}
}
public void move(Node destination) {
if (userCodeType==UserCodeType.EVENT_ACTIVITY) {
throw new PvmException("invocation of move is not allowed inside an event. only node behaviour activities can control the propagation of execution");
}
checkLock();
setNode((NodeImpl) destination);
}
// execution : internal methods /////////////////////////////////////////////
protected void moveTo(NodeImpl destination) {
// if the parent node needs to know the previous node
if (destination.isPreviousNeeded()) {
this.previousNode = node;
this.previousTransition = transition;
} else {
this.previousNode = null;
this.previousTransition = null;
}
// move the execution to the destination
node = destination;
transition = null;
transitionOrigin = null;
}
public synchronized void performAtomicOperation(AtomicOperation operation) {
if (operation.isAsync(this)) {
sendContinuationMessage(operation);
} else {
performAtomicOperationSync(operation);
}
}
// state ////////////////////////////////////////////////////////////////////
/** @see Execution#getState() */
public String getState() {
return state;
}
/** @see Execution#lock(String) */
public void lock(String state) {
if (userCodeType==UserCodeType.EVENT_ACTIVITY) {
throw new PvmException("lock is not allowed inside an event. only node behaviour activities can control the propagation of execution");
}
if (state==null) {
throw new PvmException("given state is null");
}
checkLock();
log.finest("locking "+this);
this.state = state;
}
/** @see Execution#unlock() */
public void unlock() {
if (STATE_ACTIVE.equals(state)) {
throw new PvmException("state is already active");
}
log.finest("unlocking "+this);
this.state = STATE_ACTIVE;
}
/** @see Execution#isActive() */
public boolean isActive() {
return STATE_ACTIVE.equals(state);
}
/** @see Execution#isLocked() */
public boolean isLocked() {
return ! isActive();
}
/** @see Execution#isSuspended() */
public boolean isSuspended() {
return STATE_SUSPENDED.equals(state);
}
/** @see Execution#isEnded() */
public boolean isEnded() {
return STATE_ENDED.equals(state);
}
/** @see Execution#isFinished() */
public boolean isFinished() {
return STATE_ENDED.equals(state)
|| STATE_CANCELLED.equals(state);
}
/** @see Execution#end() */
public void end() {
end(Execution.STATE_ENDED, true);
}
/** @see Execution#end(String) */
public void end(String state) {
end(state, true);
}
/** @see Execution#end(String, boolean) */
public void end(String state, boolean remove) {
if (state==null) {
throw new PvmException("state is null");
}
if (userCodeType==UserCodeType.EVENT_ACTIVITY) {
throw new PvmException("end is not allowed inside an event. only node behaviour activities can control the propagation of execution");
}
log.finest(toString()+" ends with state "+state);
lock(state);
this.propagation = Propagation.EXPLICIT;
fire(ProcessDefinition.EVENT_PROCESS_END, processDefinition);
if ( (parent!=null)
&& (remove)
) {
parent.removeExecution(this);
}
}
/** @see Execution#cancel() */
public void cancel() {
if (userCodeType==UserCodeType.EVENT_ACTIVITY) {
throw new PvmException("cancel is not allowed inside an event. only node behaviour activities can control the propagation of execution");
}
end(Execution.STATE_CANCELLED, true);
}
/** @see Execution#suspend() */
public void suspend() {
if ( userCodeType==UserCodeType.EVENT_ACTIVITY
|| userCodeType==UserCodeType.NODE_BEHAVIOUR) {
throw new PvmException("suspend is not allowed inside an activity.");
}
if (isSuspended()) {
throw new PvmException(toString()+" is suspended");
}
lock(STATE_SUSPENDED);
}
/** @see Execution#resume() */
public void resume() {
if (userCodeType==UserCodeType.EVENT_ACTIVITY
|| userCodeType==UserCodeType.NODE_BEHAVIOUR) {
throw new PvmException("resume is not allowed inside an activity.");
}
if (! isSuspended()) {
throw new PvmException(toString()+" is not suspended");
}
unlock();
}
// state : internal methods /////////////////////////////////////////////////
protected void checkLock() {
if (!STATE_ACTIVE.equals(state)) {
throw new PvmException(toString()+" is not active: "+state);
}
}
// asynchronous continuations ////////////////////////////////////////////////
public void sendContinuationMessage(AtomicOperation operation) {
Environment environment = Environment.getCurrent();
MessageSession messageSession = environment.get(MessageSession.class);
Message asyncMessage = operation.createAsyncMessage(this);
lock("async continuation message "+asyncMessage);
messageSession.send(asyncMessage);
}
public void performAtomicOperationSync(AtomicOperation operation) {
if (atomicOperations==null) {
atomicOperations = new LinkedList<AtomicOperation>();
atomicOperations.offer(operation);
try {
while (! atomicOperations.isEmpty()) {
AtomicOperation atomicOperation = atomicOperations.poll();
atomicOperation.perform(this);
}
} catch (RuntimeException e ) {
throw e;
} finally {
atomicOperations = null;
}
} else {
atomicOperations.offer(operation);
}
}
// execution scope /////////////////////////////////////////////////////////////
public void pushVariableScope(VariableScope executionScope) {
if (variableScopes==null) {
variableScopes = new ArrayList<VariableScope>();
}
variableScopes.add(0, executionScope);
}
public void popVariableScope() {
if ( (variableScopes!=null)
&& (!variableScopes.isEmpty())
) {
variableScopes.remove(0);
}
}
public List<VariableScope> getVariableScopes() {
return variableScopes;
}
// variables ////////////////////////////////////////////////////////////////
/** @see Execution#setVariable(String, Object) */
public void setVariable(String key, Object value) {
// first search for the most inner variable scope that has a variable for
// the given key
VariableScope variableScope = findVariableScope(key);
// if no variable scope exists for the given variable
if (variableScope==null) {
// then take the most outer scope of the process instance
List<VariableScope> processInstanceVariableScopes = processInstance.getVariableScopes();
if ( (processInstanceVariableScopes!=null)
&& (! processInstanceVariableScopes.isEmpty())
) {
int lastIndex = processInstanceVariableScopes.size()-1;
variableScope = processInstanceVariableScopes.get(lastIndex);
} else { // means the process instance doesn't have a variable scope yet
// create the most outer variable scope
variableScope = processInstance.createVariableScope();
}
}
variableScope.set(key, value);
}
/** @see Execution#setVariables(Map) */
public void setVariables(Map<String, Object> variables) {
if (variables!=null) {
for (String key: variables.keySet()) {
setVariable(key, variables.get(key));
}
}
}
public Object getVariable(String key) {
VariableScope variableScope = findVariableScope(key);
if (variableScope!=null) {
return variableScope.get(key);
}
return null;
}
public boolean hasVariable(String key) {
return (findVariableScope(key)!=null);
}
public Set<String> getVariableKeys() {
Set<String> variableKeys = new HashSet<String>();
VariableScopeIterator iter = new VariableScopeIterator(this);
while (iter.hasNext()) {
VariableScope variableScope = iter.next();
Set<String> keys = variableScope.keys();
if (keys!=null) {
variableKeys.addAll(keys);
}
}
return variableKeys;
}
public Map<String, Object> getVariables() {
Map<String, Object> allVariables = new HashMap<String, Object>();
VariableScopeIterator iter = new VariableScopeIterator(this);
while (iter.hasNext()) {
VariableScope variableScope = iter.next();
Map<String, Object> localVariables = variableScope.getAll();
if (localVariables!=null) {
for(String key: localVariables.keySet()) {
if (! allVariables.containsKey(key)) {
allVariables.put(key, localVariables.get(key));
}
}
}
}
return allVariables;
}
public VariableScope createVariableScope() {
return createVariableScope(null);
}
public VariableScope createVariableScope(List<VariableDefinition> variableDefinitions) {
log.finest("creating variable scope on "+this);
VariableScope variableScope = newVariableScope();
pushVariableScope(variableScope);
if (variableDefinitions!=null) {
for (VariableDefinition variableDefinition: variableDefinitions) {
String variableName = variableDefinition.getName();
Object initialValue = variableDefinition.getInitialValue(this);
variableScope.set(variableName, initialValue);
}
}
return variableScope;
}
protected VariableScope newVariableScope() {
return new VariableMap(this);
}
public void destroyVariableScope() {
if ( (variableScopes!=null)
&& (! variableScopes.isEmpty())
) {
variableScopes.remove(0);
} else if (parent!=null) {
parent.destroyVariableScope();
} else {
throw new PvmException("no variable scope to destroy");
}
}
public Iterator<VariableScope> getVariableScopeIterator() {
return new VariableScopeIterator(this);
}
public void removeVariable(String key) {
VariableScope variableScope = findVariableScope(key);
if (variableScope!=null) {
variableScope.remove(key);
}
}
// protected variable helper methods ////////////////////////////////////////
protected VariableScope getVariableScope() {
VariableScope variableScope = findVariableScope();
if (variableScope!=null) {
return variableScope;
}
return processInstance.createVariableScope();
}
protected VariableScope findVariableScope() {
VariableScopeIterator iter = new VariableScopeIterator(this);
if (iter.hasNext()) {
return iter.next();
}
return null;
}
protected VariableScope findVariableScope(String key) {
VariableScopeIterator iter = new VariableScopeIterator(this, key);
if (iter.hasNext()) {
return iter.next();
}
return null;
}
// events ///////////////////////////////////////////////////////////////////
/** @see Execution#fire(String, ObservableElement) */
public void fire(String eventName, ObservableElement eventSource) {
fire(eventName, eventSource, eventSource);
}
/** fires the event on the given *processElement* and then propagates the event
* up to the *processElement* parent chain. */
void fire(String eventName, ObservableElement eventSource, ObservableElement observableElement) {
if (observableElement!=null) {
EventImpl event = (EventImpl) observableElement.getEvent(eventName);
if (event!=null) {
if (log.isLoggable(Level.FINEST)) {
if (observableElement==eventSource) {
log.finest("firing "+event+" on "+eventSource);
} else {
log.finest("firing "+event+" on "+observableElement+", propagated from source "+eventSource);
}
}
fire(event, eventSource, observableElement);
}
propagateEvent(eventName, eventSource, observableElement);
}
}
/** this method enables specific process languages to overwrite the event propagation behaviour */
protected void propagateEvent(String eventName, ObservableElement eventSource, ObservableElement observableElement) {
fire(eventName, eventSource, observableElement.getParent());
}
/** fires the given event without propagation */
void fire(EventImpl event, ObservableElement eventSource, ObservableElement observableElement) {
try {
this.event = event;
this.eventSource = (ObservableElementImpl) eventSource;
List<EventListenerReference> eventListenerReferences = event.getListenerReferences();
if (eventListenerReferences!=null) {
for (EventListenerReference eventListenerReference: eventListenerReferences) {
if ( (observableElement.equals(eventSource)) // this event is not propagated
|| (eventListenerReference.isPropagationEnabled()) // propagation is allowed
) {
Activity activity = eventListenerReference.get();
log.finest("executing "+activity+" for "+event);
try {
this.userCodeType = UserCodeType.EVENT_ACTIVITY;
activity.execute(this);
} catch (Exception e) {
log.finest("exception during action: "+e);
handleException((ObservableElementImpl) observableElement, event, eventListenerReference, e, "couldn't run action "+activity);
} finally {
this.userCodeType = null;
}
}
}
}
} finally {
this.eventSource = null;
this.event = null;
}
}
public void handleException(ObservableElementImpl observableElement,
EventImpl actionEvent,
EventListenerReference eventListenerReference,
Exception exception,
String rethrowMessage) {
List<ProcessElementImpl> processElements = new ArrayList<ProcessElementImpl>();
if (eventListenerReference!=null) {
processElements.add(eventListenerReference);
}
if (actionEvent!=null) {
processElements.add(actionEvent);
}
while (observableElement!=null) {
processElements.add(observableElement);
observableElement = observableElement.getParent();
}
for (ProcessElementImpl processElement: processElements) {
List<ExceptionHandlerImpl> exceptionHandlers = processElement.getExceptionHandlers();
if (exceptionHandlers!=null) {
for (ExceptionHandlerImpl exceptionHandler: exceptionHandlers) {
if (exceptionHandler.matches(exception)) {
try {
exceptionHandler.handle(this, exception);
return;
} catch (Exception rethrowException) {
if (!exceptionHandler.isRethrowMasked()) {
exception = rethrowException;
}
}
break;
}
}
}
}
log.finest("rethrowing exception cause no exception handler for "+exception);
ExceptionHandlerImpl.rethrow(exception, rethrowMessage+": "+exception.getMessage());
}
/** searches for an event up the process element parent hierarchy starting
* from the given process element and returns an event or null if no such
* event exists. */
EventImpl findEvent(String eventName, ObservableElement observableElement) {
EventImpl event = null;
while ( (event==null)
&& (observableElement!=null)
) {
event = (EventImpl) observableElement.getEvent(eventName);
if (event==null) {
observableElement = observableElement.getParent();
}
}
return event;
}
// comments /////////////////////////////////////////////////////////////////
public void addComment(String message) {
addComment(new CommentImpl(message, this));
}
public void addComment(CommentImpl comment) {
if (comments==null) {
comments = new HashSet<CommentImpl>();
}
comments.add(comment);
}
// child executions /////////////////////////////////////////////////////////
/** @see Execution#createExecution() */
public ExecutionImpl createExecution() {
return createExecution(null);
}
/** @see Execution#createExecution(String) */
public ExecutionImpl createExecution(String name) {
// creating a child execution implies that this execution
// is not a leave any more and therefore, it is inactivated
if (userCodeType==UserCodeType.EVENT_ACTIVITY) {
throw new PvmException("createExecution is not allowed inside an event. only node behaviour activities can control the propagation of execution");
}
if (isActive()) {
lock(STATE_INACTIVE);
propagation = Propagation.EXPLICIT;
}
// create child execution
ExecutionImpl childExecution = newChildExecution();
childExecution.processDefinition = this.processDefinition;
childExecution.processInstance = this.processInstance;
childExecution.node = this.node;
childExecution.state = STATE_ACTIVE;
childExecution.name = name;
log.fine("creating "+childExecution);
// add it to this execution
addExecution(childExecution);
// invalidate the cached executionsMap
executionsMap = null;
return childExecution;
}
protected ExecutionImpl newChildExecution() {
return new ExecutionImpl();
}
public void addExecution(Execution execution) {
ExecutionImpl executionImpl = (ExecutionImpl) execution;
executionImpl.parent = this;
if (executions==null) {
executions = new ArrayList<ExecutionImpl>();
}
executions.add(executionImpl);
}
/** @see Execution#getExecution(String) */
public ExecutionImpl getExecution(String name) {
Map<String, Execution> executionsMap = getExecutionsMap();
return (ExecutionImpl) (executionsMap!=null ? executionsMap.get(name) : null);
}
public void removeExecution(Execution execution) {
if (executions!=null) {
executions.remove(execution);
// invalidate the executionsMap cache
executionsMap = null;
}
}
public void removeExecutions() {
executions = null;
executionsMap = null;
}
/** @see Execution#getExecutionsMap() */
public Map<String, Execution> getExecutionsMap() {
if ( (executionsMap==null)
&& (executions!=null)
) {
// initialize executionsMap cache
executionsMap = new HashMap<String, Execution>();
for(Execution execution: executions) {
String executionName = execution.getName();
// the next test makes sure that the first execution wins
// in case there are multiple executions with the same name
if (! executionsMap.containsKey(executionName)) {
executionsMap.put(executionName, execution);
}
}
}
return executionsMap;
}
public boolean hasExecution(String name) {
return ( (getExecutionsMap()!=null)
&& executionsMap.containsKey(name)
);
}
////////////////////////////////////////////////////////////////////////////////
public void addLog(ProcessLog processLog) {
processLog.setExecution(this);
// TODO send log to the logging service if it is available in the environment
}
public int nextLogIndex() {
return nextLogIndex++;
}
// overridable by process languages /////////////////////////////////////////
/** by default this will use {@link NodeImpl#findOutgoingTransition(String)} to
* search for the outgoing transition, which includes a search over the parent chain
* of the current node. This method allows process languages to overwrite this default
* implementation of the transition lookup by transitionName.*/
protected TransitionImpl findTransition(String transitionName) {
return node.findOutgoingTransition(transitionName);
}
protected TransitionImpl findDefaultTransition() {
return node.findDefaultTransition();
}
// extensions ///////////////////////////////////////////////////////////////
public <T> T getExtension(Class<T> extensionClass) {
if (extensionClass==null) {
throw new PvmException("extensionClass is null");
}
throw new PvmException("unsuppported extension "+extensionClass.getName());
}
// getters and setters /////////////////////////////////////////////////////////
public NodeImpl getNode() {
return node;
}
public long getDbid() {
return dbid;
}
public Event getEvent() {
return event;
}
public ObservableElement getEventSource() {
return eventSource;
}
public Collection<Execution> getExecutions() {
return (Collection) executions;
}
public String getName() {
return name;
}
public Execution getParent() {
return parent;
}
public int getPriority() {
return priority;
}
public ProcessDefinitionImpl getProcessDefinition() {
return processDefinition;
}
public TransitionImpl getTransition() {
return transition;
}
public void setEvent(EventImpl event) {
this.event = event;
}
public void setEventSource(ObservableElementImpl eventSource) {
this.eventSource = eventSource;
}
public void setNode(NodeImpl node) {
this.node = node;
}
public void setPriority(int priority) {
this.priority = priority;
}
public void setTransition(TransitionImpl transition) {
this.transition = transition;
}
public Node getPreviousNode() {
return previousNode;
}
public Transition getPreviousTransition() {
return previousTransition;
}
public ExecutionImpl getProcessInstance() {
return processInstance;
}
public void setProcess(ProcessDefinitionImpl processDefinition) {
this.processDefinition = processDefinition;
}
public void setProcessInstance(ExecutionImpl processInstance) {
this.processInstance = processInstance;
}
public Set<Comment> getComments() {
return (Set) comments;
}
public void setComments(Set<CommentImpl> comments) {
this.comments = comments;
}
public NodeImpl getTransitionOrigin() {
return transitionOrigin;
}
public void setTransitionOrigin(NodeImpl transitionOrigin) {
this.transitionOrigin = transitionOrigin;
}
public Exception getException() {
return exception;
}
public void setException(Exception exception) {
this.exception = exception;
}
public ProcessModificationsImpl getProcessModifications() {
return processModifications;
}
public void setProcessModifications(ProcessModificationsImpl processModifications) {
this.processModifications = processModifications;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Propagation getPropagation() {
return propagation;
}
public void setPropagation(Propagation propagation) {
this.propagation = propagation;
}
public UserCodeType getUserCodeType() {
return userCodeType;
}
public void setUserCodeType(UserCodeType userCodeType) {
this.userCodeType = userCodeType;
}
public void setName(String name) {
this.name = name;
}
public void setState(String state) {
this.state = state;
}
public void setProcessDefinition(ProcessDefinitionImpl processDefinition) {
this.processDefinition = processDefinition;
}
public void setExecutions(Collection<ExecutionImpl> executions) {
this.executions = executions;
}
public void setParent(ExecutionImpl parent) {
this.parent = parent;
}
public void setVariableScopes(List<VariableScope> variableScopes) {
this.variableScopes = variableScopes;
}
public void setPreviousNode(Node previousNode) {
this.previousNode = previousNode;
}
public void setPreviousTransition(Transition previousTransition) {
this.previousTransition = previousTransition;
}
}