/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Id: TransitionManager.java 2576 2007-11-02 16:00:34Z drmlipp $
*
* $Log$
* Revision 1.8 2007/09/20 21:28:55 mlipp
* Removed superfluous import and fixed line break.
*
* Revision 1.7 2007/05/03 21:58:18 mlipp
* Internal refactoring for making better use of local EJBs.
*
* Revision 1.6 2006/12/14 16:05:51 drmlipp
* Changed handling of conditions in exception transitions.
*
* Revision 1.5 2006/09/29 12:32:08 drmlipp
* Consistently using WfMOpen as projct name now.
*
* Revision 1.4 2005/10/10 20:02:12 mlipp
* Synchronized with 1.3.3.
*
* Revision 1.3 2005/02/04 14:25:26 drmlipp
* Synchronized with 1.3rc2.
*
* Revision 1.2.2.4 2005/02/01 21:15:53 drmlipp
* Fixed audit event generation for deferred choice.
*
* Revision 1.2.2.3 2005/02/01 16:08:44 drmlipp
* Implemented deferred choice.
*
* Revision 1.2.2.2 2005/01/31 21:07:31 drmlipp
* Fixed debug statement.
*
* Revision 1.2.2.1 2005/01/31 15:41:12 drmlipp
* Started implementation of deferred choice.
*
* Revision 1.2 2005/01/05 21:29:22 mlipp
* Added method to retrieve handled exceptions.
*
* Revision 1.1.1.1 2004/08/18 15:17:38 drmlipp
* Update to 1.2
*
* Revision 1.3 2004/05/06 19:39:18 lipp
* Restructured block activity handling.
*
* Revision 1.2 2004/03/21 20:33:52 lipp
* Optimized exception condition evaluation.
*
* Revision 1.1 2004/02/21 14:42:43 lipp
* Moved TransitionManager to domain package. Having it in its own
* package caused too many circular dependencies (and there are good
* points for having it in the domain package anyway).
*
* Revision 1.68 2003/12/16 14:40:18 lipp
* Improved debug message.
*
* Revision 1.67 2003/11/17 16:16:02 lipp
* Improved debug output once more.
*
* Revision 1.66 2003/11/17 14:15:17 lipp
* Better debug output.
*
* Revision 1.65 2003/09/25 11:01:20 lipp
* Fixed usage of jsScope (may not be used remotely).
*
* Revision 1.64 2003/09/24 13:49:20 lipp
* Fixed thread handling for block activities.
*
* Revision 1.63 2003/09/23 17:05:04 lipp
* Fixed various problems with block activities.
*
* Revision 1.62 2003/09/22 20:50:13 lipp
* Most of deadline handling for block activities.
*
* Revision 1.61 2003/09/22 12:32:57 lipp
* Implemented deadline creation for block activities.
*
* Revision 1.60 2003/09/21 21:28:14 lipp
* Introducing "virtual" block activity.
*
* Revision 1.59 2003/09/17 11:51:47 lipp
* Fixed evaluation context initialization.
*
* Revision 1.58 2003/09/16 15:36:56 lipp
* Added exception condition evaluation.
*
* Revision 1.57 2003/09/15 19:00:42 lipp
* Fixed quick transition check.
*
* Revision 1.56 2003/09/15 15:43:40 lipp
* Initial version of handling exceptions in transition manager.
*
* Revision 1.55 2003/09/04 08:46:44 lipp
* Fixed client dependency on rhino.jar.
*
* Revision 1.54 2003/06/29 18:38:43 lipp
* Fixed activity set instance successor behaviour.
*
* Revision 1.53 2003/06/27 08:51:45 lipp
* Fixed copyright/license information.
*
* Revision 1.52 2003/05/31 20:55:48 lipp
* Implemented OTHERWISE for AND_SPLIT.
*
* Revision 1.51 2003/05/31 18:26:43 lipp
* Fixed bug in evaluation sequence.
*
* Revision 1.50 2003/04/26 18:56:23 lipp
* Moved extended interfaces to own package.
*
* Revision 1.49 2003/03/31 16:50:28 huaiyang
* Logging using common-logging.
*
* Revision 1.48 2003/03/13 14:07:13 lipp
* Improved implementation of condition evaluation.
*
* Revision 1.47 2003/01/30 16:35:26 lipp
* Support for nested loops.
*
* Revision 1.46 2003/01/30 13:47:29 lipp
* Optimized evaluation, prefer triggering activity in xor join.
*
* Revision 1.45 2003/01/29 15:51:31 lipp
* Loops should work now.
*
* Revision 1.44 2003/01/27 15:57:25 lipp
* Added loop detection.
*
* Revision 1.43 2003/01/24 16:47:09 lipp
* Implemented thread logging.
*
* Revision 1.42 2002/12/05 16:38:20 lipp
* Implemented activity dependencies on process.terminate and .abort.
*
* Revision 1.41 2002/12/04 16:04:19 lipp
* Implemented condition evaluation.
*
* Revision 1.40 2002/11/26 11:23:29 lipp
* Modified RemoteException comment.
*
* Revision 1.39 2002/11/21 21:35:59 lipp
* Proper handling of activity set predecessors.
*
* Revision 1.38 2002/11/21 16:24:51 lipp
* Proper handling of activity set successors.
*
* Revision 1.37 2002/11/19 15:14:53 lipp
* New transition manager.
*
* Revision 1.36 2002/11/12 17:10:03 lipp
* Adapted to new State usage.
*
* Revision 1.35 2002/11/09 19:02:04 lipp
* Better handling of RemoteException.
*
* Revision 1.34 2002/10/21 19:08:05 lipp
* Continuing implementation of new state handling.
*
* Revision 1.33 2002/10/21 11:54:23 lipp
* Better suspend state handling.
*
* Revision 1.32 2002/10/15 13:22:32 huaiyang
* Remove system.out.println and printStackTrace.
*
* Revision 1.31 2002/10/08 13:52:36 lipp
* Merged branch new-state-handling.
*
* Revision 1.30.2.1 2002/10/07 15:04:53 lipp
* Reimplementing state handling, process start to activity start.
*
* Revision 1.30 2002/08/26 20:23:14 lipp
* Lots of method renames.
*
* Revision 1.29 2002/08/26 14:17:07 lipp
* JavaDoc fixes.
*
* Revision 1.28 2002/08/16 10:22:10 lipp
* Removed usage of equals().
*
* Revision 1.27 2002/05/18 11:58:22 lipp
* New interface for Transition in api (instead of using the domain class
* directly).
*
* Revision 1.26 2002/04/03 12:53:05 lipp
* JavaDoc fixes.
*
* Revision 1.25 2002/01/15 14:27:01 robert
* replace Activity, Process, ExecutionObject
* from workflow/domain to workflow/api
*
* Revision 1.24 2002/01/11 12:13:14 robert
* cleanup not needed import statements
*
* Revision 1.23 2001/12/18 22:16:53 lipp
* Restructured DOM generation, implemented "assignments" method from ras.
*
* Revision 1.22 2001/12/10 09:53:58 robert
* changed Vector to ArrayList
*
* Revision 1.21 2001/10/26 11:24:50 montag
* corrected using of getNextActivities() in TransitionManager
*
* Revision 1.20 2001/10/25 17:15:51 lipp
* Renamed getTransitionsRefs() to getNextActivities() (more appropriate
* name) and added TransitionRefs to DOM representatiosn.
*
* Revision 1.19 2001/10/09 13:48:28 montag
* doccheck
*
* Revision 1.18 2001/10/09 11:29:42 montag
* TransitionManager now without ejbs.
* transitionref list now contains Activity elements.
*
* Revision 1.17 2001/10/08 08:28:11 montag
* redundant primary key getters removed.
*
* Revision 1.16 2001/10/06 11:40:28 lipp
* Some javadoc fixes.
*
* Revision 1.15 2001/10/04 08:01:54 montag
* unittests adopt for new transitionmanager
*
* Revision 1.14 2001/10/02 13:15:09 montag
* allNotCompletedActivities() adapted.
*
* Revision 1.13 2001/10/02 12:35:59 montag
* hasClosedSuccessor() finished.
* TO DO: Dont allow state change of activity,
* if process is already closed.
*
* Revision 1.12 2001/10/02 10:50:28 montag
* cleanup obsolte code.
* TO DO: finish hasClosedSuccessor()
*
* Revision 1.11 2001/10/01 14:33:03 montag
* check process completeness
* TO DO: clean up obsolete code
*
* Revision 1.10 2001/10/01 10:29:52 montag
* rewriting of transition manager (fourth part):
* XOR splits now works.
* TO DO: if a process is finished now depends not
* on that all activities closed.
*
* Revision 1.9 2001/09/27 15:17:53 montag
* rewriting of transition manager (third part):
* XOR and AND join now works.
* TO DO: XOR splits
*
* Revision 1.8 2001/09/27 14:38:16 montag
* rewriting of transition manager (second part):
* now the old behaviour is available
*
* Revision 1.7 2001/09/27 12:09:50 montag
* rewriting of transition manager (first part)
*
* Revision 1.6 2001/09/26 13:13:26 montag
* new method description for allOpenableActivities().
*
* Revision 1.5 2001/09/26 08:15:39 montag
* allToActivities() implemented.
* test case for transition manager.
*
* Revision 1.4 2001/09/11 09:59:07 montag
* transition manager now checks all not completed activities
* when an activitiy is completed. next step: determine next activities
* when an activity is complete
*
* Revision 1.3 2001/09/11 09:16:50 montag
* transition manager now finds openable activities
* at process start. next step: determine next activities
* when an activity is complete
*
* Revision 1.2 2001/09/10 15:51:53 montag
* usage of bean resources, first part
*
* Revision 1.1 2001/09/06 15:48:33 montag
* initial version
*
*
*/
package de.danet.an.workflow.domain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.rmi.RemoteException;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.NotAFunctionException;
import org.mozilla.javascript.PropertyException;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import de.danet.an.workflow.internalapi.ExtActivityLocal;
import de.danet.an.workflow.internalapi.ExtTransitionLocal;
import de.danet.an.workflow.internalapi.ThreadInfo;
import de.danet.an.workflow.internalapi.ExtActivityLocal.NotStartedState;
import de.danet.an.workflow.localapi.ActivityLocal;
import de.danet.an.workflow.omgcore.TransitionNotAllowedException;
import de.danet.an.workflow.omgcore.WfExecutionObject.ClosedState;
import de.danet.an.workflow.omgcore.WfExecutionObject.NotRunningState;
import de.danet.an.workflow.omgcore.WfExecutionObject.State;
import de.danet.an.workflow.api.InvalidKeyException;
import de.danet.an.workflow.api.Activity.ClosedCompletedState;
/**
* <code>TransitionManager</code> calculates lists of activities
* with special properties of a
* {@link de.danet.an.workflow.localcoreapi.WfProcessLocal <code>WfProcess</code>}.<P>
*
* If log level is set to <code>DEBUG</code> messages about
* intermediate evaluation results will be generated.
*/
public class TransitionManager implements Serializable {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog(TransitionManager.class);
/** The associated process. */
private AbstractProcess myProcess;
/** The associated process' scriptable. */
private Scriptable procScopeCache = null;
/** All activities that are not started yet but may be started. */
private Map startableActs = new HashMap ();
/** All activities that are closed. */
private Map closedActs = new HashMap ();
/** All activities that are being executed. */
private Map runningActs = new HashMap ();
/** Transition from preceeding activities. */
private Map transByTo = new HashMap ();
/** Transition to following activities. */
private Map transByFrom = new HashMap ();
/** Comparator for transitions. */
private static Comparator transComp = new Comparator () {
public int compare (Object o1, Object o2) {
return ((ExtTransitionLocal)o1).order()
- ((ExtTransitionLocal)o2).order();
}
};
/**
* Helper for transition condition evaluation.
*/
private class EvaluationContext {
private ExtActivityLocal fromAct = null;
private String exception = null;
private Scriptable exceptionScopeCache = null;
/**
* Create a context with all context information initialized
* to defaults values.
*/
public EvaluationContext () {
}
/**
* Create a context for evaluating conditions of transitions
* that origin at the given activity and for which the given
* exception has been received.<P>
*
* @param from the origin activity
* @param exc the exception
*/
public EvaluationContext (ExtActivityLocal from, String exc) {
fromAct = from;
exception = exc;
}
private final Scriptable exceptionScope() throws RemoteException {
if (exceptionScopeCache == null) {
try {
exceptionScopeCache
= (new Context()).newObject (myProcess.jsScope());
exceptionScopeCache.setPrototype(myProcess.jsScope());
exceptionScopeCache.setParentScope(null);
((ScriptableObject)exceptionScopeCache)
.defineProperty ("occurredException", exception,
ScriptableObject.READONLY);
} catch (NotAFunctionException e) {
logger.error (e.getMessage (), e);
} catch (PropertyException e) {
logger.error (e.getMessage (), e);
} catch (JavaScriptException e) {
logger.error (e.getMessage (), e);
}
}
return exceptionScopeCache;
}
/**
* Test if we may walk along a transition. Not that this method
* does not verify whether the to-activity is in state "not started".<P>
*
* If the constructor {@link
* #EvaluationContext(ExtActivityLocal,String) for a given
* activity} was used, the transition must have this activity
* as "from" activity.
*
* @param trans the transition to check.
* @param exceptions <code>null</code> if normal transitions are to
* be evaluated, else the exceptions defined for the activity with
* the first one being the triggered exception
* @return <code>true</code> if transit is possible.
*/
public boolean transitOK (ExtTransitionLocal trans) {
// try special shortcuts
switch (trans.conditionType()) {
case de.danet.an.workflow.api.Transition.COND_TYPE_DEFAULTEXCEPTION:
// this is OK iff the origin event is an exception
return exception != null;
case de.danet.an.workflow.api.Transition.COND_TYPE_EXCEPTION:
// not OK if origin event was not an exception
if (exception == null) {
return false;
}
if (trans.condition().equals (exception)) {
return true;
}
// No need to evaluate something that looks
// like an exception name
if (trans.condition().trim().matches("^[\\w\\$\\s]*$")) {
return false;
}
break;
case de.danet.an.workflow.api.Transition.COND_TYPE_OTHERWISE:
case de.danet.an.workflow.api.Transition.COND_TYPE_CONDITION:
// not OK if origin event was an exception
if (exception != null) {
return false;
}
// always OK if target not completed and no condition
if (trans.conditionType()
== de.danet.an.workflow.api.Transition.COND_TYPE_OTHERWISE
|| trans.condition() == null) {
if (logger.isDebugEnabled()) {
logger.debug ("Evaluating condition for "
+ trans + " results in true");
}
return true;
}
break;
}
// shortcuts didn't work, evaluate
Context cx = Context.enter();
try {
Scriptable proto
= (exception == null
? myProcess.jsScope() : exceptionScope());
Scriptable scope = cx.newObject (proto);
scope.setPrototype (proto);
scope.setParentScope (null);
Object res = cx.evaluateString (scope, trans.condition(),
"<condition>", 1, null);
if (!(res instanceof Boolean)) {
logger.error
("Evaluating transition condition \""+trans.condition()
+ "\" does not yield a boolean result.");
return false;
}
if (logger.isDebugEnabled()) {
logger.debug ("Evaluating condition for " + trans
+ " results in " + res);
}
return ((Boolean)res).booleanValue();
} catch (Exception e) {
if (trans.conditionType() == de.danet.an.workflow.api
.Transition.COND_TYPE_EXCEPTION) {
logger.warn ("Cannot evaluate transition condition \""
+ trans.condition() + "\""
+ " (may be ignored if the exception name"
+ " has intentionally been chosen not to look"
+ " like an identifier): " + e.getMessage());
} else {
logger.error("Problem evaluating transition condition \""
+ trans.condition() + "\": " + e.getMessage());
}
return false;
} finally {
cx.exit();
}
}
}
/**
* Default constructor. Sets up the activity groups and transition
* maps. Activities are grouped as "not started", "running" and
* "closed". The additional, transient state "startable" is
* assigned if an activity is not started and has no
* predecessors. Note that activities with these properties can
* only exist in a not yet started process. Once the process has
* started, all activities that have no predecessors will no
* longer be in state "not started".
*
* @param process the process associated with this object
*/
public TransitionManager (AbstractProcess process) {
if (logger.isDebugEnabled ()) {
logger.debug ("Initializing transition manager for " + process);
}
myProcess = process;
for (Iterator i = process.stepsLocal().iterator (); i.hasNext(); ) {
ExtActivityLocal a = (ExtActivityLocal)i.next();
String key = a.key();
transByTo.put (key, new ArrayList ());
transByFrom.put (key, new ArrayList ());
}
for (Iterator i = process.transitionsLocal().iterator ();
i.hasNext(); ) {
ExtTransitionLocal t = (ExtTransitionLocal)i.next();
Collection fts = (Collection)transByFrom.get(t.from().key());
if (fts == null) {
// this may happen in the case of block activites with
// exception triggered transitions, because there are
// no real activities instantiated
fts = new ArrayList ();
transByFrom.put (t.from().key(), fts);
}
fts.add (t);
((Collection)transByTo.get(t.to().key())).add (t);
}
for (Iterator i = transByFrom.keySet().iterator(); i.hasNext();) {
String key = (String)i.next();
Collections.sort ((List)transByFrom.get(key), transComp);
}
for (Iterator i = transByTo.keySet().iterator(); i.hasNext();) {
String key = (String)i.next();
Collections.sort ((List)transByTo.get(key), transComp);
}
for (Iterator i = process.stepsLocal().iterator (); i.hasNext(); ) {
ExtActivityLocal a = (ExtActivityLocal)i.next();
State as = a.typedState();
String ak = a.key();
if (as.isSameOrSubState(NotRunningState.NOT_STARTED)) {
if (as.isSameOrSubState (NotStartedState.STARTABLE)) {
startableActs.put (ak, a);
} else if (as.isSameOrSubState (NotStartedState.UNKNOWN)
&& (((Collection)transByTo.get(ak)).size () == 0)) {
a.setStartable (null, false);
if (logger.isDebugEnabled()) {
logger.debug (a + " set to startable");
}
startableActs.put (ak, a);
}
} else if (as.isSameOrSubState (State.CLOSED)) {
closedActs.put (ak, a);
} else {
runningActs.put (ak, a);
}
}
if (logger.isDebugEnabled ()) {
logState ("transition manager created");
}
}
/**
* Find out if the activity with the given key is the source of
* any transition.
* @param act the activity
* @return <code>true</code> if there is at least one transition
* originating from the given activity
*/
public boolean isTransitionSource (String act) {
Collection c = (Collection)transByFrom.get (act);
return c != null && c.size () > 0;
}
/**
* Verifies if no work remains to be done for this process.
* @return <code>true</code> if there are no startable or running
* activities.
*/
public boolean isAtEnd () {
return startableActs.size() == 0 && runningActs.size() == 0;
}
/**
* Informs the transition manager about a change of an activity's state.
* @param act the activity that has changed its state.
*/
public void update (ExtActivityLocal act) {
update (act, null);
}
/**
* Informs the transition manager about an event on an activity.
*
* @param act the activity that has received the exception.
* @param exception an exception name if an exception has occured
* on the activity or <code>null</code> if the activity has simply
* reached a new state
*/
public void update (ExtActivityLocal act, String exception) {
String key = act.key();
State ats = act.typedState();
if (logger.isDebugEnabled ()) {
logState ("adjusting to " + act + " updated to " + ats);
}
// Update the sets of activities in a certain state, but only
// if this is a real activity, not a block activity
// representation
if (!(act instanceof BlockActivity)) {
if (ats.isSameOrSubState (NotRunningState.NOT_STARTED)) {
startableActs.remove (key);
runningActs.remove (key);
closedActs.remove (key);
} else if (ats.isSameOrSubState (State.CLOSED)) {
startableActs.remove (key);
runningActs.remove (key);
closedActs.put (key, act);
if (ats.isSameOrSubState(ClosedCompletedState.NORMAL)) {
// this implies exception == null, no conflict
// with code below
updateDependend (act, null);
}
} else {
startableActs.remove (key);
runningActs.put (key, act);
}
}
if (exception != null) {
updateDependend (act, exception);
}
if (logger.isDebugEnabled ()) {
logState ("adjusted to " + act + " updated to " + ats);
}
}
/**
* Update the activities that depend on the given one. If an
* activity is closed or an exception occurs, "not started"
* activities may become startable.
*
* @param act the activity that has changed its state.
* @param exception the name of the exception that has occured
*/
private void updateDependend (ExtActivityLocal act, String exception) {
if (logger.isDebugEnabled()) {
logger.debug ("Updating dependend on " + act.toString()
+ (exception == null
? (" split mode is " + act.splitMode())
: (" due to exception " + exception)));
}
if (! act.typedState().isSameOrSubState (State.CLOSED)
&& exception == null) {
throw new IllegalArgumentException
("State must be closed (is " + act.typedState() + ")"
+ " or there must be an exception (is " + exception + ")");
}
Collection resets = new ArrayList ();
Collection starts = new ArrayList ();
EvaluationContext ctx = new EvaluationContext (act, exception);
if (act.splitMode().isAND() || exception != null) {
// consider all transitions from this activity
ExtTransitionLocal defaultTrans = null;
boolean gotATrans = false;
for (Iterator i = ((Collection)transByFrom.get(act.key()))
.iterator (); i.hasNext();) {
ExtTransitionLocal trans = (ExtTransitionLocal)i.next();
if ((exception == null && trans.conditionType()
== de.danet.an.workflow.api.Transition.COND_TYPE_OTHERWISE)
|| (exception != null && trans.conditionType()
== de.danet.an.workflow.api
.Transition.COND_TYPE_DEFAULTEXCEPTION)) {
defaultTrans = trans;
continue;
}
if (ctx.transitOK (trans)) {
gotATrans = true;
if (!tryTransit (trans, resets, starts)) {
trans.setPendingToken(true);
}
}
}
if (!gotATrans && defaultTrans != null) {
if (logger.isDebugEnabled ()) {
logger.debug ("Trying default: " + defaultTrans);
}
if (!tryTransit(defaultTrans, resets, starts)) {
defaultTrans.setPendingToken(true);
}
}
} else /* act.splitMode().isXOR() */ {
// consider only first match of all transitions from this activity
String group = null;
for (Iterator i = ((Collection)transByFrom.get(act.key()))
.iterator (); i.hasNext();) {
ExtTransitionLocal trans = (ExtTransitionLocal)i.next();
boolean contTransit = false;
if (group != null) {
if (!trans.group().equals (group)) {
break; // end of group, quit.
}
contTransit = true;
}
if (logger.isDebugEnabled()) {
logger.debug ("Trying transition " + trans);
}
// note that reevalution of transit conditions is not
// necessary within a group, it's the same condition.
if (contTransit || ctx.transitOK (trans)) {
if (logger.isDebugEnabled ()) {
logger.debug ("... transit OK (continued: "
+ contTransit + ")");
}
if (!tryTransit (trans, resets, starts)) {
trans.setPendingToken(true);
}
group = trans.group(); // continue for group
}
}
}
boolean deferChoiceOnSplit = act.deferChoiceOnSplit ();
for (Iterator items = starts.iterator(); items.hasNext();) {
Object[] item = (Object[])items.next();
ExtActivityLocal startAct = (ExtActivityLocal)item[0];
Collection triggers = (Collection)item[1];
setStartable (startAct, triggers, deferChoiceOnSplit);
}
for (Iterator items = resets.iterator(); items.hasNext();) {
Object[] item = (Object[])items.next();
ExtActivityLocal toAct = (ExtActivityLocal)item[0];
Collection resetActs = (Collection)item[1];
if (logger.isDebugEnabled ()) {
logger.debug ("Resetting (true): " + toAct);
}
toAct.reset (true, false);
resetTokens (toAct);
for (Iterator i = resetActs.iterator(); i.hasNext(); ) {
ExtActivityLocal resAct = (ExtActivityLocal)i.next();
if (logger.isDebugEnabled ()) {
logger.debug ("Resetting (false): " + resAct);
}
resAct.reset (false, false);
resetTokens (resAct);
closedActs.remove (resAct);
}
setStartable (toAct, null, deferChoiceOnSplit);
}
}
private void resetTokens (ExtActivityLocal act) {
for (Iterator i = ((Collection)transByFrom.get(act.key()))
.iterator(); i.hasNext ();) {
ExtTransitionLocal trans = (ExtTransitionLocal)i.next();
trans.setPendingToken(false);
}
}
private void setStartable
(ExtActivityLocal act, Collection triggers, boolean deferChoiceOnSplit) {
if (logger.isDebugEnabled ()) {
StringBuffer s = new StringBuffer ();
s.append ("Setting " + act + " startable, triggers: ");
if (triggers != null) {
boolean first = true;
for (Iterator i = triggers.iterator (); i.hasNext ();) {
ExtActivityLocal ta = (ExtActivityLocal)i.next ();
if (! first) {
s.append (", ");
}
s.append (ta.key());
first = false;
}
}
s.append (", preliminary chosen: " + deferChoiceOnSplit);
logger.debug (s.toString());
}
act.setStartable (triggers, deferChoiceOnSplit);
startableActs.put (act.key(), act);
}
/**
* Test if a transition is possible considering the states and
* history of the activities. Note that the condition for the
* transition passed as parameter must have been verified and must
* have evaluated to <code>true</code>.
*
* @param trans the transition.
* @param resets activities that should be reset
* @param starts activities that should be started
* @return <code>true</code> if the transition can be made
*/
private boolean tryTransit
(ExtTransitionLocal trans, Collection resets, Collection starts) {
if (logger.isDebugEnabled ()) {
logger.debug ("Trying transit from "
+ trans.from() + " to " + trans.to());
}
ExtActivityLocal toAct = (ExtActivityLocal)trans.to();
if (toAct.typedState().isSameOrSubState (NotStartedState.UNKNOWN)) {
Collection triggers = isStartable (trans, toAct);
if (triggers != null) {
starts.add (new Object[] { toAct, triggers });
return true;
}
return false;
}
ExtActivityLocal fromAct = (ExtActivityLocal)trans.from();
String toActKey = toAct.key();
if (! ((toAct.typedState().isSameOrSubState (ClosedState.COMPLETED)
&& (fromAct.threadInfo().includes (toActKey)
|| fromAct.key().equals (toActKey))))) {
return false;
}
// Now we reset the activities in the loop
if (logger.isDebugEnabled ()) {
logger.debug ("Loop detected: " + fromAct + " triggers " + toAct);
}
if (isStartable (trans, toAct) != null) {
Collection resetActs = collectPredecessors (fromAct, toActKey);
resets.add (new Object[] { toAct, resetActs });
return true;
}
return false;
}
/**
* Collect all predecessors of the given activity on the way back
* to the given origin (including the start activity but excluding
* the origin).
* @param fromAct the activity to start from.
* @param origin the activity to stop with.
* @return the collected <code>Activity</code> objects. Note that
* the result is a set although this cannot formally be specified
* as the equals method is not defined for remote interfaces.
*/
private Collection collectPredecessors
(ExtActivityLocal fromAct, String origin) {
Collection collActs = new ArrayList ();
collectPredecessors (collActs, new HashSet(), fromAct, origin);
return collActs;
}
/**
* Helper that collects the keys as well as the activities.
* @param collected the collected activities.
* @param collKeys the collected keys.
* @param fromAct the activity to start from.
* @param origin the activity to stop with.
*/
private void collectPredecessors
(Collection collected, Set collKeys,
ExtActivityLocal fromAct, String origin) {
try {
if (fromAct.key().equals (origin)) {
return;
}
collected.add (fromAct);
collKeys.add (fromAct.key());
Set pres = fromAct.threadInfo().predecessorsFor (origin);
if (pres.contains (origin)) {
return; // optimization, avoids unnecessary activity
// lookups in loop, not required for proper function.
}
for (Iterator p = pres.iterator (); p.hasNext(); ) {
String k = (String)p.next();
if (collKeys.contains (k)) {
continue;
}
ExtActivityLocal a = (ExtActivityLocal)
myProcess.activityByKeyLocal (k);
collectPredecessors (collected, collKeys, a, origin);
}
} catch (InvalidKeyException e) {
// cannot happen
throw new IllegalStateException ();
}
}
/**
* Verify if an activity may be started, i.e. its predecessor(s)
* are closed and the transition conditions evaluate to true.
*
* @param toTrans the transition leading to the activity to be
* verified or <code>null</code> if the activity is to be
* re-verified without a known trigger.
* @param act the activity to be verified.
* @param exceptions <code>null</code> if normal transitions are to
* be evaluated, else the exceptions defined for the activity with
* the first one being the triggered exception
* @return the activities that trigger the given one or
* <code>null</code> if the activity is not startable
*/
private Collection isStartable
(ExtTransitionLocal toTrans, ExtActivityLocal act) {
Collection triggers = new ArrayList ();
// now things depend on the join mode
if (logger.isDebugEnabled()) {
logger.debug ("Testing join for " + act
+ ", mode " + act.joinMode());
}
if (act.joinMode().isAND()) {
ExtTransitionLocal trans = null;
String curGroup = null;
boolean groupOK = false;
for (Iterator i = ((Collection)transByTo.get(act.key()))
.iterator (); i.hasNext ();) {
trans = (ExtTransitionLocal)i.next();
if (curGroup == null) {
curGroup = trans.group();
} else if (!curGroup.equals (trans.group ())) {
if (!groupOK) {
break;
}
curGroup = trans.group ();
groupOK = false;
}
if (logger.isDebugEnabled()) {
if (toTrans != null && trans.equals (toTrans)) {
logger.debug ("Transition " + trans
+ " is triggering (implicitly true)");
}
}
if (!(toTrans != null && trans.equals (toTrans))
&& !trans.hasPendingToken()) {
continue;
}
groupOK = true;
addToTriggers (triggers, trans.from());
}
if (!groupOK) {
if (logger.isDebugEnabled()) {
logger.debug (act + " not startable");
}
return null;
}
} else { // act.joinMode().isXOR()
addToTriggers (triggers, toTrans.from());
}
if (logger.isDebugEnabled()) {
logger.debug (act + " is startable");
}
return triggers;
}
private void addToTriggers (Collection triggers, ActivityLocal act) {
// do not record sources of asynchronous deadlines
if (act.typedState().isSameOrSubState (State.CLOSED)) {
if (act instanceof BAForExceptionHandling) {
triggers.addAll (((BAForExceptionHandling)act).predecessors());
} else {
triggers.add (act);
}
}
}
/**
* Return the startable activities.
* @return a collection of startable activities.
*/
public Collection startableActivities () {
Collection res = new ArrayList ();
for (Iterator i = startableActs.values().iterator (); i.hasNext(); ) {
ExtActivityLocal a = (ExtActivityLocal)i.next();
if (a.typedState().isSameOrSubState(NotRunningState.NOT_STARTED)) {
res.add (a);
}
}
return res;
}
/**
* Given an activity from a set of activities started in a
* deferred choice, reset all other (competing) activities.
* @param act the activity
*/
public void resetCompeting (ExtActivityLocal act)
throws TransitionNotAllowedException {
ThreadInfo threadInfo = act.threadInfo ();
Collection res = new ArrayList ();
Collection predecessors = (Collection)transByTo.get(act.key());
if (predecessors.size() == 0) {
throw new IllegalArgumentException
(act + " has no predecessors, cannot be preliminarily chosen");
}
for (Iterator i = predecessors.iterator(); i.hasNext ();) {
ExtTransitionLocal trans = (ExtTransitionLocal)i.next();
ExtActivityLocal fromAct = (ExtActivityLocal)trans.from();
if (fromAct.deferChoiceOnSplit ()
&& threadInfo.includes (fromAct.key())) {
for (Iterator j = ((Collection)transByFrom.get(fromAct.key()))
.iterator(); j.hasNext ();) {
ExtTransitionLocal ft = (ExtTransitionLocal)j.next();
res.add (ft.to());
ft.setPendingToken(false);
}
}
}
if (logger.isDebugEnabled ()) {
StringBuffer s = new StringBuffer ();
s.append ("Preliminary chosen with " + act + ": ");
boolean first = true;
for (Iterator i = res.iterator(); i.hasNext();) {
ExtActivityLocal a = (ExtActivityLocal)i.next ();
if (first) {
first = false;
} else {
s.append (", ");
}
s.append (a.toString ());
}
logger.debug (s);
}
// terminate others
for (Iterator i = res.iterator (); i.hasNext ();) {
ExtActivityLocal dcAct = (ExtActivityLocal)i.next ();
if (!dcAct.key().equals (act.key())) {
try {
if (dcAct.preliminarilyChosen ()) {
dcAct.withdrawPreliminaryChoice (true);
update (dcAct);
}
} catch (TransitionNotAllowedException e) {
logger.error ("Inconsistent state: " + e.getMessage (), e);
}
}
}
act.withdrawPreliminaryChoice (false);
}
private void logState (String context) {
logger.debug ("Engine state for " + myProcess.toString()
+ "(context: " + context + "):");
logger.debug (" Transition manager: " + this);
logger.debug (" Process is at end: " + isAtEnd());
for (Iterator i = startableActs.values().iterator(); i.hasNext();) {
ExtActivityLocal a = (ExtActivityLocal)i.next();
logger.debug (" Startable: " + a);
}
for (Iterator i = runningActs.values().iterator (); i.hasNext ();) {
ExtActivityLocal a = (ExtActivityLocal)i.next();
logger.debug (" Running: " + a);
}
}
}