
org.ow2.bonita.runtime.model.Execution Maven / Gradle / Ivy
The newest version!
/**
* 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.
*
* Modified by Matthieu Chaffotte - BonitaSoft S.A.
*/
package org.ow2.bonita.runtime.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import org.ow2.bonita.definition.activity.ConnectorExecutor;
import org.ow2.bonita.definition.activity.ExternalActivity;
import org.ow2.bonita.env.Environment;
import org.ow2.bonita.facade.def.InternalActivityDefinition;
import org.ow2.bonita.facade.def.InternalProcessDefinition;
import org.ow2.bonita.facade.def.element.HookDefinition;
import org.ow2.bonita.facade.def.majorElement.TransitionDefinition;
import org.ow2.bonita.facade.runtime.impl.InternalActivityInstance;
import org.ow2.bonita.facade.runtime.impl.InternalProcessInstance;
import org.ow2.bonita.facade.runtime.impl.InternalProcessInstance.TransitionState;
import org.ow2.bonita.facade.uuid.ActivityDefinitionUUID;
import org.ow2.bonita.facade.uuid.ActivityInstanceUUID;
import org.ow2.bonita.services.Querier;
import org.ow2.bonita.services.Recorder;
import org.ow2.bonita.util.BonitaRuntimeException;
import org.ow2.bonita.util.Command;
import org.ow2.bonita.util.EnvTool;
import org.ow2.bonita.util.EqualsUtil;
import org.ow2.bonita.util.ExceptionManager;
import org.ow2.bonita.util.ProcessUtil;
import org.ow2.bonita.util.TransientData;
/**
* @author Tom Baeyens
*/
public class Execution implements Serializable {
private static final long serialVersionUID = 1L;
private static final Logger LOG = Logger.getLogger(Execution.class.getName());
public static final String INITIAL_ITERATION_ID = "it1";
protected long id;
protected int dbversion;
protected String activityInstanceId = "mainActivityInstance"; // used when this execution points a node in a multi
// instantiation
protected String iterationId;
protected int waitingForActivityInstanceNb;
protected int activityInstanceNb;
protected InternalProcessInstance instance;
protected InternalActivityInstance activityInstance;
protected String name;
protected String state;
protected String eventUUID;
protected Propagation propagation = null;
protected InternalProcessDefinition processDefinition;
protected InternalActivityDefinition node;
protected Collection executions;
protected Execution parent;
/** the queue of atomic operations to be performed for this execution. */
protected Queue atomicOperations;
public enum Propagation {
UNSPECIFIED, WAIT, EXPLICIT
}
/**
* between creation of a new process instance and the {@link Execution#beginWithOneStartNode() start} of that process
* instance. The motivation of this state is that variables can be set programmatically on the process instance so
* that they can be used during initializations of variables and timers
*/
public static final String STATE_CREATED = "created";
/**
* either executing or in a wait state waiting for a signal. This is the normal state of an execution and the initial
* state when creating a new execution. Make sure that comparisons are done with .equals and not with '==' because if
* executions are loaded from persistent storage, a new string is created instead of the constants.
*/
public static final String STATE_ACTIVE = "active";
/**
* parents with concurrent child executions are inactive. When an execution has concurrent child executions, it
* implies that this execution can't be active. For example, at a fork, the parent execution can wait inactively in
* the fork being till all the child executions are joined. Only leaves of the execution tree can be active. Make sure
* that comparisons are done with .equals and not with '==' because if executions are loaded from persistent storage,
* a new string is created instead of the constants.
*/
public static final String STATE_INACTIVE = "inactive";
/**
* this execution has ended normally. Make sure that comparisons are done with .equals and not with '==' because if
* executions are loaded from persistent storage, a new string is created instead of the constants.
*/
public static final String STATE_ENDED = "ended";
/**
* this execution was cancelled with the {@link #cancel()} method before normal execution ended. Make sure that
* comparisons are done with .equals and not with '==' because if executions are loaded from persistent storage, a new
* string is created instead of the constants.
*/
public static final String STATE_CANCELLED = "cancelled";
/** indicates that this execution is doing an asynchronous continuation. */
public static final String STATE_ASYNC = "async";
// Mandatory for hibernate
protected Execution() {
}
public Execution(final String name, final InternalProcessDefinition processDefinition,
final InternalProcessInstance processInstance, final InternalActivityDefinition activity, final String state,
final String iterationId) {
this.processDefinition = processDefinition;
instance = processInstance;
this.name = name;
this.state = state;
node = activity;
this.iterationId = iterationId;
}
public void beginWithOneStartNode() {
setIterationId(INITIAL_ITERATION_ID);
if (!STATE_CREATED.equals(state)) {
final String message = ExceptionManager.getInstance().getFullMessage("bp_EI_1 ", toString(), state);
throw new BonitaRuntimeException(message);
}
state = STATE_ACTIVE;
if (node != null) {
performAtomicOperation(new ExecuteNode());
}
}
public void beginWithManyStartNodes(final ActivityDefinitionUUID activityUUID) {
beginWithOneStartNode();
for (final InternalActivityDefinition activity : getProcessDefinition().getInternalInitialActivities().values()) {
if (activityUUID == null && !activity.isReceiveEvent() || activity.getUUID().equals(activityUUID)) {
final Execution child = createChildExecution(activity.getName());
child.execute(activity);
}
}
}
public Execution createChildExecution(final String name) {
// creating a child execution implies that this execution
// is not a leave any more and therefore, it is inactivated
if (isActive()) {
lock(STATE_INACTIVE);
propagation = Propagation.EXPLICIT;
}
// create child execution
final Execution child = new Execution(name, getProcessDefinition(), getInstance(), getNode(), STATE_ACTIVE,
getIterationId());
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("creating " + child);
}
// copy the current state from the child execution to the parent execution
child.setPropagation(getPropagation());
// add it to this execution
addExecution(child);
return child;
}
public Execution backToParent() {
if (parent == null) {
return this;
}
// copy the current state from the child execution to the parent execution
getParent().setNode(getNode());
getParent().setPropagation(getPropagation());
end();
parent.removeExecution(this);
return parent;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("execution, name=").append(getName()).append(", parent= ").append(getParent())
.append(", instance= ").append(getInstance()).append(", activityInstanceUUID= ")
.append(getActivityInstanceUUID());
return builder.toString();
}
public void end() {
end(Execution.STATE_ENDED);
}
public void end(final String state) {
if (state == null) {
final String message = ExceptionManager.getInstance().getFullMessage("bp_EI_2");
throw new BonitaRuntimeException(message);
}
if (state.equals(STATE_ACTIVE) || state.equals(STATE_CREATED) || state.equals(STATE_INACTIVE)
|| state.equals(STATE_ASYNC)) {
final String message = ExceptionManager.getInstance().getFullMessage("bp_EI_3", state);
throw new BonitaRuntimeException(message);
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine(toString() + " ends with state " + state);
}
// end all child executions
if (executions != null) {
for (final Execution child : executions) {
child.end(state);
}
}
lock(state);
propagation = Propagation.EXPLICIT;
}
public void cancel() {
if (getExecutions() != null) {
for (final Execution child : new ArrayList(getExecutions())) {
child.cancel();
}
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine(this + " cancelled.");
}
if (getActivityInstanceUUID() != null) {
final boolean isSubflow = getNode().isSubflow();
if (isSubflow) {
final Querier journal = EnvTool.getJournalQueriers();
final InternalProcessInstance childInstance = journal.getProcessInstance(getActivityInstance()
.getSubflowProcessInstanceUUID());
childInstance.cancel();
}
EnvTool.getRecorder().recordBodyCancelled(getActivityInstance());
TransientData.removeTransientData(getActivityInstanceUUID());
}
if (!STATE_ACTIVE.equals(state)) {
unlock();
}
end(Execution.STATE_CANCELLED);
final Execution parent = getParent();
if (parent != null) {
parent.removeExecution(this);
}
}
public void abort() {
if (getExecutions() != null) {
for (final Execution child : new ArrayList(getExecutions())) {
child.abort();
}
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine(this + " aborted.");
}
ConnectorExecutor.executeConnectors(this, HookDefinition.Event.instanceOnAbort);
if (getActivityInstanceUUID() != null) {
final boolean isSubflow = getNode().isSubflow();
if (isSubflow && getActivityInstance().getSubflowProcessInstanceUUID() != null) {
// execution has been started only if getSubflowProcessInstanceUUID is not null
final Querier journal = EnvTool.getJournalQueriers();
final InternalProcessInstance childInstance = journal.getProcessInstance(getActivityInstance()
.getSubflowProcessInstanceUUID());
childInstance.getRootExecution().abort();
final Recorder recorder = EnvTool.getRecorder();
recorder.recordInstanceAborted(childInstance.getUUID(), EnvTool.getUserId());
ProcessUtil.removeInternalInstanceEvents(instance.getUUID());
}
EnvTool.getRecorder().recordBodyAborted(getActivityInstance());
TransientData.removeTransientData(getActivityInstanceUUID());
}
end(Execution.STATE_CANCELLED);
final Execution parent = getParent();
if (parent != null) {
parent.removeExecution(this);
}
}
public void signal(final String signal, final Map parameters) {
checkLock();
if (node != null) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine(toString() + " signals " + node);
}
final ExternalActivity externalActivity = node.getBehaviour();
try {
setPropagation(Propagation.UNSPECIFIED);
externalActivity.signal(this, signal, parameters);
} catch (final RuntimeException e) {
throw e;
} catch (final Exception e) {
final String message = ExceptionManager.getInstance().getFullMessage("bp_S_1", node, e.getMessage());
throw new BonitaRuntimeException(message, e);
}
if (getPropagation() == Propagation.UNSPECIFIED && !node.isInALoop()) {
proceed();
}
} else {
final String message = ExceptionManager.getInstance().getFullMessage("bp_EI_6");
throw new BonitaRuntimeException(message);
}
}
public void take(final TransitionDefinition transition) {
instance.setTransitionState(transition.getName(), TransitionState.TAKEN);
checkLock();
setPropagation(Propagation.EXPLICIT);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine(toString() + " takes " + transition);
}
setNode(getProcessDefinition().getActivity(transition.getTo()));
performAtomicOperation(new ExecuteNode());
}
public void execute(final InternalActivityDefinition node) {
if (node == null) {
final String message = ExceptionManager.getInstance().getFullMessage("bp_EI_12");
throw new BonitaRuntimeException(message);
}
checkLock();
propagation = Propagation.EXPLICIT;
this.node = node;
performAtomicOperation(new ExecuteNode());
}
// execution method : waitForSignal /////////////////////////////////////////
public void waitForSignal() {
propagation = Propagation.WAIT;
}
// execution method : proceed ///////////////////////////////////////////////
public void proceed() {
checkLock();
// in graph based processDefinition languages we assume that a
// default transition is available
final TransitionDefinition defaultTransition = getDefaultTransition(node);
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 {
// When we don't know how to proceed, i don't know if it's best to throw new
// BonitaRuntimeException("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(final InternalActivityDefinition destination, final Execution execution) {
execution.move(destination);
}
public void move(final InternalActivityDefinition destination) {
checkLock();
setNode(destination);
}
/** @see Execution#lock(String) */
public void lock(final String state) {
if (state == null) {
final String message = ExceptionManager.getInstance().getFullMessage("bp_EI_22");
throw new BonitaRuntimeException(message);
}
checkLock();
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("locking " + this);
}
this.state = state;
}
/** @see Execution#unlock() */
public void unlock() {
if (STATE_ACTIVE.equals(state)) {
final String message = ExceptionManager.getInstance().getFullMessage("bp_EI_23");
throw new BonitaRuntimeException(message);
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("unlocking " + this);
}
state = STATE_ACTIVE;
}
// state : internal methods /////////////////////////////////////////////////
protected void checkLock() {
if (STATE_INACTIVE.equals(state) || STATE_CANCELLED.equals(state) || STATE_ENDED.equals(state)) {
final String message = ExceptionManager.getInstance().getFullMessage("bp_EI_24", toString(), state);
throw new BonitaRuntimeException(message);
}
}
public void performAtomicOperation(final ExecuteNode operation) {
performAtomicOperation(operation, true);
}
public void performAtomicOperation(final ExecuteNode operation, final boolean checkJoinType) {
if (atomicOperations == null) {
// initialise the fifo queue of atomic operations
atomicOperations = new LinkedList();
atomicOperations.offer(operation);
try {
while (!atomicOperations.isEmpty()) {
final ExecuteNode atomicOperation = atomicOperations.poll();
atomicOperation.perform(this, checkJoinType);
}
} catch (final RuntimeException e) {
throw e;
} finally {
atomicOperations = null;
}
} else {
atomicOperations.offer(operation);
}
}
public boolean isActive() {
return STATE_ACTIVE.equals(state);
}
public boolean isFinished() {
return STATE_ENDED.equals(state) || STATE_CANCELLED.equals(state);
}
public String getState() {
return state;
}
public InternalProcessInstance getInstance() {
return instance;
}
public void setInstance(final InternalProcessInstance instance) {
this.instance = instance;
}
public String getIterationId() {
return iterationId;
}
public void setIterationId(final String iterationId) {
this.iterationId = iterationId;
}
public String getActivityInstanceId() {
return activityInstanceId;
}
public void setActivityInstanceId(final String activityInstanceId) {
this.activityInstanceId = activityInstanceId;
}
public int getWaitingForActivityInstanceNb() {
return waitingForActivityInstanceNb;
}
public void setWaitingForActivityInstanceNb(final int waitingFor) {
waitingForActivityInstanceNb = waitingFor;
}
public int getActivityInstanceNb() {
return activityInstanceNb;
}
public void setActivityInstanceNb(final int activityInstanceNb) {
this.activityInstanceNb = activityInstanceNb;
}
public ActivityInstanceUUID getActivityInstanceUUID() {
if (activityInstance == null) {
return null;
}
return activityInstance.getUUID();
}
public InternalActivityInstance getActivityInstance() {
return activityInstance;
}
public void setActivityInstance(final InternalActivityInstance activityInstance) {
this.activityInstance = activityInstance;
}
public void addExecution(final Execution execution) {
execution.parent = this;
if (executions == null) {
executions = new ArrayList();
}
executions.add(execution);
}
public Execution getExecution(final String name) {
for (final Execution exec : getExecutions()) {
if (exec.getName().equals(name)) {
return exec;
}
}
return null;
}
public void removeExecution(final Execution child) {
if (executions != null) {
if (executions.remove(child)) {
if (state.equals(STATE_INACTIVE) && executions.isEmpty()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("last child execution was removed; unlocking");
}
state = STATE_ACTIVE;
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("removed " + child + " from " + this);
}
} else {
final String message = ExceptionManager.getInstance().getFullMessage("bp_EI_29", child, this);
throw new BonitaRuntimeException(message);
}
}
EnvTool.getTransaction().registerSynchronization(new Synchronization() {
@Override
public void beforeCompletion() {
}
@Override
public void afterCompletion(final int status) {
if (status == Status.STATUS_COMMITTED) {
EnvTool.getCommandService().execute(new DeleteExecutionCommand(child.getId()));
}
}
});
}
private static class DeleteExecutionCommand implements Command {
private static final long serialVersionUID = 1L;
private final long executionId;
public DeleteExecutionCommand(final long executionId) {
this.executionId = executionId;
}
@Override
public Void execute(final Environment environment) throws Exception {
EnvTool.getJournal().removeExecution(executionId);
return null;
}
}
public String getNodeName() {
if (node == null) {
return null;
}
return node.getName();
}
/**
* by default this will use activity.getOutgoingTransitions 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 TransitionDefinition findTransition(final String transitionName) {
return node.getOutgoingTransition(transitionName);
}
// equals ///////////////////////////////////////////////////////////////////
// hack to support comparing hibernate proxies against the real objects
// since this always falls back to ==, we don't need to overwrite the hashcode
@Override
public boolean equals(final Object o) {
return EqualsUtil.equals(this, o);
}
// getters and setters
// /////////////////////////////////////////////////////////
public Collection getExecutions() {
if (executions == null) {
return Collections.emptySet();
}
return executions;
}
public String getName() {
return name;
}
public Execution getParent() {
return parent;
}
public Propagation getPropagation() {
return propagation;
}
public void setPropagation(final Propagation propagation) {
this.propagation = propagation;
}
public void setName(final String name) {
this.name = name;
}
public void setState(final String state) {
this.state = state;
}
public InternalActivityDefinition getNode() {
return node;
}
private void setNode(final InternalActivityDefinition node) {
this.node = node;
}
public long getId() {
return id;
}
public InternalProcessDefinition getProcessDefinition() {
return processDefinition;
}
private static TransitionDefinition getDefaultTransition(final InternalActivityDefinition activity) {
if (activity.hasOutgoingTransitions()) {
return activity.getOutgoingTransitions().iterator().next();
}
return null;
}
public String getEventUUID() {
return eventUUID;
}
public void setEventUUID(final String eventUUID) {
this.eventUUID = eventUUID;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy