0001: /*
0002: * This file is part of the WfMOpen project.
0003: * Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
0004: * All rights reserved.
0005: *
0006: * This program is free software; you can redistribute it and/or modify
0007: * it under the terms of the GNU General Public License as published by
0008: * the Free Software Foundation; either version 2 of the License, or
0009: * (at your option) any later version.
0010: *
0011: * This program is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0014: * GNU General Public License for more details.
0015: *
0016: * You should have received a copy of the GNU General Public License
0017: * along with this program; if not, write to the Free Software
0018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0019: *
0020: * $Id: TransitionManager.java,v 1.7.2.1 2007/11/02 16:00:33 drmlipp Exp $
0021: *
0022: * $Log: TransitionManager.java,v $
0023: * Revision 1.7.2.1 2007/11/02 16:00:33 drmlipp
0024: * Merged bug fixes from HEAD.
0025: *
0026: * Revision 1.8 2007/09/20 21:28:55 mlipp
0027: * Removed superfluous import and fixed line break.
0028: *
0029: * Revision 1.7 2007/05/03 21:58:18 mlipp
0030: * Internal refactoring for making better use of local EJBs.
0031: *
0032: * Revision 1.6 2006/12/14 16:05:51 drmlipp
0033: * Changed handling of conditions in exception transitions.
0034: *
0035: * Revision 1.5 2006/09/29 12:32:08 drmlipp
0036: * Consistently using WfMOpen as projct name now.
0037: *
0038: * Revision 1.4 2005/10/10 20:02:12 mlipp
0039: * Synchronized with 1.3.3.
0040: *
0041: * Revision 1.3 2005/02/04 14:25:26 drmlipp
0042: * Synchronized with 1.3rc2.
0043: *
0044: * Revision 1.2.2.4 2005/02/01 21:15:53 drmlipp
0045: * Fixed audit event generation for deferred choice.
0046: *
0047: * Revision 1.2.2.3 2005/02/01 16:08:44 drmlipp
0048: * Implemented deferred choice.
0049: *
0050: * Revision 1.2.2.2 2005/01/31 21:07:31 drmlipp
0051: * Fixed debug statement.
0052: *
0053: * Revision 1.2.2.1 2005/01/31 15:41:12 drmlipp
0054: * Started implementation of deferred choice.
0055: *
0056: * Revision 1.2 2005/01/05 21:29:22 mlipp
0057: * Added method to retrieve handled exceptions.
0058: *
0059: * Revision 1.1.1.1 2004/08/18 15:17:38 drmlipp
0060: * Update to 1.2
0061: *
0062: * Revision 1.3 2004/05/06 19:39:18 lipp
0063: * Restructured block activity handling.
0064: *
0065: * Revision 1.2 2004/03/21 20:33:52 lipp
0066: * Optimized exception condition evaluation.
0067: *
0068: * Revision 1.1 2004/02/21 14:42:43 lipp
0069: * Moved TransitionManager to domain package. Having it in its own
0070: * package caused too many circular dependencies (and there are good
0071: * points for having it in the domain package anyway).
0072: *
0073: * Revision 1.68 2003/12/16 14:40:18 lipp
0074: * Improved debug message.
0075: *
0076: * Revision 1.67 2003/11/17 16:16:02 lipp
0077: * Improved debug output once more.
0078: *
0079: * Revision 1.66 2003/11/17 14:15:17 lipp
0080: * Better debug output.
0081: *
0082: * Revision 1.65 2003/09/25 11:01:20 lipp
0083: * Fixed usage of jsScope (may not be used remotely).
0084: *
0085: * Revision 1.64 2003/09/24 13:49:20 lipp
0086: * Fixed thread handling for block activities.
0087: *
0088: * Revision 1.63 2003/09/23 17:05:04 lipp
0089: * Fixed various problems with block activities.
0090: *
0091: * Revision 1.62 2003/09/22 20:50:13 lipp
0092: * Most of deadline handling for block activities.
0093: *
0094: * Revision 1.61 2003/09/22 12:32:57 lipp
0095: * Implemented deadline creation for block activities.
0096: *
0097: * Revision 1.60 2003/09/21 21:28:14 lipp
0098: * Introducing "virtual" block activity.
0099: *
0100: * Revision 1.59 2003/09/17 11:51:47 lipp
0101: * Fixed evaluation context initialization.
0102: *
0103: * Revision 1.58 2003/09/16 15:36:56 lipp
0104: * Added exception condition evaluation.
0105: *
0106: * Revision 1.57 2003/09/15 19:00:42 lipp
0107: * Fixed quick transition check.
0108: *
0109: * Revision 1.56 2003/09/15 15:43:40 lipp
0110: * Initial version of handling exceptions in transition manager.
0111: *
0112: * Revision 1.55 2003/09/04 08:46:44 lipp
0113: * Fixed client dependency on rhino.jar.
0114: *
0115: * Revision 1.54 2003/06/29 18:38:43 lipp
0116: * Fixed activity set instance successor behaviour.
0117: *
0118: * Revision 1.53 2003/06/27 08:51:45 lipp
0119: * Fixed copyright/license information.
0120: *
0121: * Revision 1.52 2003/05/31 20:55:48 lipp
0122: * Implemented OTHERWISE for AND_SPLIT.
0123: *
0124: * Revision 1.51 2003/05/31 18:26:43 lipp
0125: * Fixed bug in evaluation sequence.
0126: *
0127: * Revision 1.50 2003/04/26 18:56:23 lipp
0128: * Moved extended interfaces to own package.
0129: *
0130: * Revision 1.49 2003/03/31 16:50:28 huaiyang
0131: * Logging using common-logging.
0132: *
0133: * Revision 1.48 2003/03/13 14:07:13 lipp
0134: * Improved implementation of condition evaluation.
0135: *
0136: * Revision 1.47 2003/01/30 16:35:26 lipp
0137: * Support for nested loops.
0138: *
0139: * Revision 1.46 2003/01/30 13:47:29 lipp
0140: * Optimized evaluation, prefer triggering activity in xor join.
0141: *
0142: * Revision 1.45 2003/01/29 15:51:31 lipp
0143: * Loops should work now.
0144: *
0145: * Revision 1.44 2003/01/27 15:57:25 lipp
0146: * Added loop detection.
0147: *
0148: * Revision 1.43 2003/01/24 16:47:09 lipp
0149: * Implemented thread logging.
0150: *
0151: * Revision 1.42 2002/12/05 16:38:20 lipp
0152: * Implemented activity dependencies on process.terminate and .abort.
0153: *
0154: * Revision 1.41 2002/12/04 16:04:19 lipp
0155: * Implemented condition evaluation.
0156: *
0157: * Revision 1.40 2002/11/26 11:23:29 lipp
0158: * Modified RemoteException comment.
0159: *
0160: * Revision 1.39 2002/11/21 21:35:59 lipp
0161: * Proper handling of activity set predecessors.
0162: *
0163: * Revision 1.38 2002/11/21 16:24:51 lipp
0164: * Proper handling of activity set successors.
0165: *
0166: * Revision 1.37 2002/11/19 15:14:53 lipp
0167: * New transition manager.
0168: *
0169: * Revision 1.36 2002/11/12 17:10:03 lipp
0170: * Adapted to new State usage.
0171: *
0172: * Revision 1.35 2002/11/09 19:02:04 lipp
0173: * Better handling of RemoteException.
0174: *
0175: * Revision 1.34 2002/10/21 19:08:05 lipp
0176: * Continuing implementation of new state handling.
0177: *
0178: * Revision 1.33 2002/10/21 11:54:23 lipp
0179: * Better suspend state handling.
0180: *
0181: * Revision 1.32 2002/10/15 13:22:32 huaiyang
0182: * Remove system.out.println and printStackTrace.
0183: *
0184: * Revision 1.31 2002/10/08 13:52:36 lipp
0185: * Merged branch new-state-handling.
0186: *
0187: * Revision 1.30.2.1 2002/10/07 15:04:53 lipp
0188: * Reimplementing state handling, process start to activity start.
0189: *
0190: * Revision 1.30 2002/08/26 20:23:14 lipp
0191: * Lots of method renames.
0192: *
0193: * Revision 1.29 2002/08/26 14:17:07 lipp
0194: * JavaDoc fixes.
0195: *
0196: * Revision 1.28 2002/08/16 10:22:10 lipp
0197: * Removed usage of equals().
0198: *
0199: * Revision 1.27 2002/05/18 11:58:22 lipp
0200: * New interface for Transition in api (instead of using the domain class
0201: * directly).
0202: *
0203: * Revision 1.26 2002/04/03 12:53:05 lipp
0204: * JavaDoc fixes.
0205: *
0206: * Revision 1.25 2002/01/15 14:27:01 robert
0207: * replace Activity, Process, ExecutionObject
0208: * from workflow/domain to workflow/api
0209: *
0210: * Revision 1.24 2002/01/11 12:13:14 robert
0211: * cleanup not needed import statements
0212: *
0213: * Revision 1.23 2001/12/18 22:16:53 lipp
0214: * Restructured DOM generation, implemented "assignments" method from ras.
0215: *
0216: * Revision 1.22 2001/12/10 09:53:58 robert
0217: * changed Vector to ArrayList
0218: *
0219: * Revision 1.21 2001/10/26 11:24:50 montag
0220: * corrected using of getNextActivities() in TransitionManager
0221: *
0222: * Revision 1.20 2001/10/25 17:15:51 lipp
0223: * Renamed getTransitionsRefs() to getNextActivities() (more appropriate
0224: * name) and added TransitionRefs to DOM representatiosn.
0225: *
0226: * Revision 1.19 2001/10/09 13:48:28 montag
0227: * doccheck
0228: *
0229: * Revision 1.18 2001/10/09 11:29:42 montag
0230: * TransitionManager now without ejbs.
0231: * transitionref list now contains Activity elements.
0232: *
0233: * Revision 1.17 2001/10/08 08:28:11 montag
0234: * redundant primary key getters removed.
0235: *
0236: * Revision 1.16 2001/10/06 11:40:28 lipp
0237: * Some javadoc fixes.
0238: *
0239: * Revision 1.15 2001/10/04 08:01:54 montag
0240: * unittests adopt for new transitionmanager
0241: *
0242: * Revision 1.14 2001/10/02 13:15:09 montag
0243: * allNotCompletedActivities() adapted.
0244: *
0245: * Revision 1.13 2001/10/02 12:35:59 montag
0246: * hasClosedSuccessor() finished.
0247: * TO DO: Dont allow state change of activity,
0248: * if process is already closed.
0249: *
0250: * Revision 1.12 2001/10/02 10:50:28 montag
0251: * cleanup obsolte code.
0252: * TO DO: finish hasClosedSuccessor()
0253: *
0254: * Revision 1.11 2001/10/01 14:33:03 montag
0255: * check process completeness
0256: * TO DO: clean up obsolete code
0257: *
0258: * Revision 1.10 2001/10/01 10:29:52 montag
0259: * rewriting of transition manager (fourth part):
0260: * XOR splits now works.
0261: * TO DO: if a process is finished now depends not
0262: * on that all activities closed.
0263: *
0264: * Revision 1.9 2001/09/27 15:17:53 montag
0265: * rewriting of transition manager (third part):
0266: * XOR and AND join now works.
0267: * TO DO: XOR splits
0268: *
0269: * Revision 1.8 2001/09/27 14:38:16 montag
0270: * rewriting of transition manager (second part):
0271: * now the old behaviour is available
0272: *
0273: * Revision 1.7 2001/09/27 12:09:50 montag
0274: * rewriting of transition manager (first part)
0275: *
0276: * Revision 1.6 2001/09/26 13:13:26 montag
0277: * new method description for allOpenableActivities().
0278: *
0279: * Revision 1.5 2001/09/26 08:15:39 montag
0280: * allToActivities() implemented.
0281: * test case for transition manager.
0282: *
0283: * Revision 1.4 2001/09/11 09:59:07 montag
0284: * transition manager now checks all not completed activities
0285: * when an activitiy is completed. next step: determine next activities
0286: * when an activity is complete
0287: *
0288: * Revision 1.3 2001/09/11 09:16:50 montag
0289: * transition manager now finds openable activities
0290: * at process start. next step: determine next activities
0291: * when an activity is complete
0292: *
0293: * Revision 1.2 2001/09/10 15:51:53 montag
0294: * usage of bean resources, first part
0295: *
0296: * Revision 1.1 2001/09/06 15:48:33 montag
0297: * initial version
0298: *
0299: *
0300: */
0301: package de.danet.an.workflow.domain;
0302:
0303: import java.io.Serializable;
0304:
0305: import java.util.ArrayList;
0306: import java.util.Collection;
0307: import java.util.Collections;
0308: import java.util.Comparator;
0309: import java.util.HashMap;
0310: import java.util.HashSet;
0311: import java.util.Iterator;
0312: import java.util.List;
0313: import java.util.Map;
0314: import java.util.Set;
0315:
0316: import java.rmi.RemoteException;
0317:
0318: import org.mozilla.javascript.Context;
0319: import org.mozilla.javascript.JavaScriptException;
0320: import org.mozilla.javascript.NotAFunctionException;
0321: import org.mozilla.javascript.PropertyException;
0322: import org.mozilla.javascript.Scriptable;
0323: import org.mozilla.javascript.ScriptableObject;
0324:
0325: import de.danet.an.workflow.internalapi.ExtActivityLocal;
0326: import de.danet.an.workflow.internalapi.ExtTransitionLocal;
0327: import de.danet.an.workflow.internalapi.ThreadInfo;
0328: import de.danet.an.workflow.internalapi.ExtActivityLocal.NotStartedState;
0329: import de.danet.an.workflow.localapi.ActivityLocal;
0330: import de.danet.an.workflow.omgcore.TransitionNotAllowedException;
0331: import de.danet.an.workflow.omgcore.WfExecutionObject.ClosedState;
0332: import de.danet.an.workflow.omgcore.WfExecutionObject.NotRunningState;
0333: import de.danet.an.workflow.omgcore.WfExecutionObject.State;
0334:
0335: import de.danet.an.workflow.api.InvalidKeyException;
0336: import de.danet.an.workflow.api.Activity.ClosedCompletedState;
0337:
0338: /**
0339: * <code>TransitionManager</code> calculates lists of activities
0340: * with special properties of a
0341: * {@link de.danet.an.workflow.localcoreapi.WfProcessLocal <code>WfProcess</code>}.<P>
0342: *
0343: * If log level is set to <code>DEBUG</code> messages about
0344: * intermediate evaluation results will be generated.
0345: */
0346: public class TransitionManager implements Serializable {
0347: private static final org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory
0348: .getLog(TransitionManager.class);
0349:
0350: /** The associated process. */
0351: private AbstractProcess myProcess;
0352:
0353: /** The associated process' scriptable. */
0354: private Scriptable procScopeCache = null;
0355:
0356: /** All activities that are not started yet but may be started. */
0357: private Map startableActs = new HashMap();
0358:
0359: /** All activities that are closed. */
0360: private Map closedActs = new HashMap();
0361:
0362: /** All activities that are being executed. */
0363: private Map runningActs = new HashMap();
0364:
0365: /** Transition from preceeding activities. */
0366: private Map transByTo = new HashMap();
0367:
0368: /** Transition to following activities. */
0369: private Map transByFrom = new HashMap();
0370:
0371: /** Comparator for transitions. */
0372: private static Comparator transComp = new Comparator() {
0373: public int compare(Object o1, Object o2) {
0374: return ((ExtTransitionLocal) o1).order()
0375: - ((ExtTransitionLocal) o2).order();
0376: }
0377: };
0378:
0379: /**
0380: * Helper for transition condition evaluation.
0381: */
0382: private class EvaluationContext {
0383:
0384: private ExtActivityLocal fromAct = null;
0385: private String exception = null;
0386: private Scriptable exceptionScopeCache = null;
0387:
0388: /**
0389: * Create a context with all context information initialized
0390: * to defaults values.
0391: */
0392: public EvaluationContext() {
0393: }
0394:
0395: /**
0396: * Create a context for evaluating conditions of transitions
0397: * that origin at the given activity and for which the given
0398: * exception has been received.<P>
0399: *
0400: * @param from the origin activity
0401: * @param exc the exception
0402: */
0403: public EvaluationContext(ExtActivityLocal from, String exc) {
0404: fromAct = from;
0405: exception = exc;
0406: }
0407:
0408: private final Scriptable exceptionScope()
0409: throws RemoteException {
0410: if (exceptionScopeCache == null) {
0411: try {
0412: exceptionScopeCache = (new Context())
0413: .newObject(myProcess.jsScope());
0414: exceptionScopeCache.setPrototype(myProcess
0415: .jsScope());
0416: exceptionScopeCache.setParentScope(null);
0417: ((ScriptableObject) exceptionScopeCache)
0418: .defineProperty("occurredException",
0419: exception,
0420: ScriptableObject.READONLY);
0421: } catch (NotAFunctionException e) {
0422: logger.error(e.getMessage(), e);
0423: } catch (PropertyException e) {
0424: logger.error(e.getMessage(), e);
0425: } catch (JavaScriptException e) {
0426: logger.error(e.getMessage(), e);
0427: }
0428: }
0429: return exceptionScopeCache;
0430: }
0431:
0432: /**
0433: * Test if we may walk along a transition. Not that this method
0434: * does not verify whether the to-activity is in state "not started".<P>
0435: *
0436: * If the constructor {@link
0437: * #EvaluationContext(ExtActivityLocal,String) for a given
0438: * activity} was used, the transition must have this activity
0439: * as "from" activity.
0440: *
0441: * @param trans the transition to check.
0442: * @param exceptions <code>null</code> if normal transitions are to
0443: * be evaluated, else the exceptions defined for the activity with
0444: * the first one being the triggered exception
0445: * @return <code>true</code> if transit is possible.
0446: */
0447: public boolean transitOK(ExtTransitionLocal trans) {
0448: // try special shortcuts
0449: switch (trans.conditionType()) {
0450: case de.danet.an.workflow.api.Transition.COND_TYPE_DEFAULTEXCEPTION:
0451: // this is OK iff the origin event is an exception
0452: return exception != null;
0453:
0454: case de.danet.an.workflow.api.Transition.COND_TYPE_EXCEPTION:
0455: // not OK if origin event was not an exception
0456: if (exception == null) {
0457: return false;
0458: }
0459: if (trans.condition().equals(exception)) {
0460: return true;
0461: }
0462: // No need to evaluate something that looks
0463: // like an exception name
0464: if (trans.condition().trim().matches("^[\\w\\$\\s]*$")) {
0465: return false;
0466: }
0467: break;
0468:
0469: case de.danet.an.workflow.api.Transition.COND_TYPE_OTHERWISE:
0470: case de.danet.an.workflow.api.Transition.COND_TYPE_CONDITION:
0471: // not OK if origin event was an exception
0472: if (exception != null) {
0473: return false;
0474: }
0475: // always OK if target not completed and no condition
0476: if (trans.conditionType() == de.danet.an.workflow.api.Transition.COND_TYPE_OTHERWISE
0477: || trans.condition() == null) {
0478: if (logger.isDebugEnabled()) {
0479: logger.debug("Evaluating condition for "
0480: + trans + " results in true");
0481: }
0482: return true;
0483: }
0484: break;
0485: }
0486:
0487: // shortcuts didn't work, evaluate
0488: Context cx = Context.enter();
0489: try {
0490: Scriptable proto = (exception == null ? myProcess
0491: .jsScope() : exceptionScope());
0492: Scriptable scope = cx.newObject(proto);
0493: scope.setPrototype(proto);
0494: scope.setParentScope(null);
0495:
0496: Object res = cx.evaluateString(scope,
0497: trans.condition(), "<condition>", 1, null);
0498: if (!(res instanceof Boolean)) {
0499: logger.error("Evaluating transition condition \""
0500: + trans.condition()
0501: + "\" does not yield a boolean result.");
0502: return false;
0503: }
0504: if (logger.isDebugEnabled()) {
0505: logger.debug("Evaluating condition for " + trans
0506: + " results in " + res);
0507: }
0508: return ((Boolean) res).booleanValue();
0509: } catch (Exception e) {
0510: if (trans.conditionType() == de.danet.an.workflow.api.Transition.COND_TYPE_EXCEPTION) {
0511: logger
0512: .warn("Cannot evaluate transition condition \""
0513: + trans.condition()
0514: + "\""
0515: + " (may be ignored if the exception name"
0516: + " has intentionally been chosen not to look"
0517: + " like an identifier): "
0518: + e.getMessage());
0519: } else {
0520: logger
0521: .error("Problem evaluating transition condition \""
0522: + trans.condition()
0523: + "\": "
0524: + e.getMessage());
0525: }
0526: return false;
0527: } finally {
0528: cx.exit();
0529: }
0530: }
0531: }
0532:
0533: /**
0534: * Default constructor. Sets up the activity groups and transition
0535: * maps. Activities are grouped as "not started", "running" and
0536: * "closed". The additional, transient state "startable" is
0537: * assigned if an activity is not started and has no
0538: * predecessors. Note that activities with these properties can
0539: * only exist in a not yet started process. Once the process has
0540: * started, all activities that have no predecessors will no
0541: * longer be in state "not started".
0542: *
0543: * @param process the process associated with this object
0544: */
0545: public TransitionManager(AbstractProcess process) {
0546: if (logger.isDebugEnabled()) {
0547: logger.debug("Initializing transition manager for "
0548: + process);
0549: }
0550: myProcess = process;
0551: for (Iterator i = process.stepsLocal().iterator(); i.hasNext();) {
0552: ExtActivityLocal a = (ExtActivityLocal) i.next();
0553: String key = a.key();
0554: transByTo.put(key, new ArrayList());
0555: transByFrom.put(key, new ArrayList());
0556: }
0557: for (Iterator i = process.transitionsLocal().iterator(); i
0558: .hasNext();) {
0559: ExtTransitionLocal t = (ExtTransitionLocal) i.next();
0560: Collection fts = (Collection) transByFrom.get(t.from()
0561: .key());
0562: if (fts == null) {
0563: // this may happen in the case of block activites with
0564: // exception triggered transitions, because there are
0565: // no real activities instantiated
0566: fts = new ArrayList();
0567: transByFrom.put(t.from().key(), fts);
0568: }
0569: fts.add(t);
0570: ((Collection) transByTo.get(t.to().key())).add(t);
0571: }
0572: for (Iterator i = transByFrom.keySet().iterator(); i.hasNext();) {
0573: String key = (String) i.next();
0574: Collections.sort((List) transByFrom.get(key), transComp);
0575: }
0576: for (Iterator i = transByTo.keySet().iterator(); i.hasNext();) {
0577: String key = (String) i.next();
0578: Collections.sort((List) transByTo.get(key), transComp);
0579: }
0580: for (Iterator i = process.stepsLocal().iterator(); i.hasNext();) {
0581: ExtActivityLocal a = (ExtActivityLocal) i.next();
0582: State as = a.typedState();
0583: String ak = a.key();
0584: if (as.isSameOrSubState(NotRunningState.NOT_STARTED)) {
0585: if (as.isSameOrSubState(NotStartedState.STARTABLE)) {
0586: startableActs.put(ak, a);
0587: } else if (as.isSameOrSubState(NotStartedState.UNKNOWN)
0588: && (((Collection) transByTo.get(ak)).size() == 0)) {
0589: a.setStartable(null, false);
0590: if (logger.isDebugEnabled()) {
0591: logger.debug(a + " set to startable");
0592: }
0593: startableActs.put(ak, a);
0594: }
0595: } else if (as.isSameOrSubState(State.CLOSED)) {
0596: closedActs.put(ak, a);
0597: } else {
0598: runningActs.put(ak, a);
0599: }
0600: }
0601: if (logger.isDebugEnabled()) {
0602: logState("transition manager created");
0603: }
0604: }
0605:
0606: /**
0607: * Find out if the activity with the given key is the source of
0608: * any transition.
0609: * @param act the activity
0610: * @return <code>true</code> if there is at least one transition
0611: * originating from the given activity
0612: */
0613: public boolean isTransitionSource(String act) {
0614: Collection c = (Collection) transByFrom.get(act);
0615: return c != null && c.size() > 0;
0616: }
0617:
0618: /**
0619: * Verifies if no work remains to be done for this process.
0620: * @return <code>true</code> if there are no startable or running
0621: * activities.
0622: */
0623: public boolean isAtEnd() {
0624: return startableActs.size() == 0 && runningActs.size() == 0;
0625: }
0626:
0627: /**
0628: * Informs the transition manager about a change of an activity's state.
0629: * @param act the activity that has changed its state.
0630: */
0631: public void update(ExtActivityLocal act) {
0632: update(act, null);
0633: }
0634:
0635: /**
0636: * Informs the transition manager about an event on an activity.
0637: *
0638: * @param act the activity that has received the exception.
0639: * @param exception an exception name if an exception has occured
0640: * on the activity or <code>null</code> if the activity has simply
0641: * reached a new state
0642: */
0643: public void update(ExtActivityLocal act, String exception) {
0644: String key = act.key();
0645: State ats = act.typedState();
0646: if (logger.isDebugEnabled()) {
0647: logState("adjusting to " + act + " updated to " + ats);
0648: }
0649: // Update the sets of activities in a certain state, but only
0650: // if this is a real activity, not a block activity
0651: // representation
0652: if (!(act instanceof BlockActivity)) {
0653: if (ats.isSameOrSubState(NotRunningState.NOT_STARTED)) {
0654: startableActs.remove(key);
0655: runningActs.remove(key);
0656: closedActs.remove(key);
0657: } else if (ats.isSameOrSubState(State.CLOSED)) {
0658: startableActs.remove(key);
0659: runningActs.remove(key);
0660: closedActs.put(key, act);
0661: if (ats.isSameOrSubState(ClosedCompletedState.NORMAL)) {
0662: // this implies exception == null, no conflict
0663: // with code below
0664: updateDependend(act, null);
0665: }
0666: } else {
0667: startableActs.remove(key);
0668: runningActs.put(key, act);
0669: }
0670: }
0671: if (exception != null) {
0672: updateDependend(act, exception);
0673: }
0674: if (logger.isDebugEnabled()) {
0675: logState("adjusted to " + act + " updated to " + ats);
0676: }
0677: }
0678:
0679: /**
0680: * Update the activities that depend on the given one. If an
0681: * activity is closed or an exception occurs, "not started"
0682: * activities may become startable.
0683: *
0684: * @param act the activity that has changed its state.
0685: * @param exception the name of the exception that has occured
0686: */
0687: private void updateDependend(ExtActivityLocal act, String exception) {
0688: if (logger.isDebugEnabled()) {
0689: logger.debug("Updating dependend on "
0690: + act.toString()
0691: + (exception == null ? (" split mode is " + act
0692: .splitMode())
0693: : (" due to exception " + exception)));
0694: }
0695: if (!act.typedState().isSameOrSubState(State.CLOSED)
0696: && exception == null) {
0697: throw new IllegalArgumentException(
0698: "State must be closed (is " + act.typedState()
0699: + ")"
0700: + " or there must be an exception (is "
0701: + exception + ")");
0702: }
0703: Collection resets = new ArrayList();
0704: Collection starts = new ArrayList();
0705: EvaluationContext ctx = new EvaluationContext(act, exception);
0706: if (act.splitMode().isAND() || exception != null) {
0707: // consider all transitions from this activity
0708: ExtTransitionLocal defaultTrans = null;
0709: boolean gotATrans = false;
0710: for (Iterator i = ((Collection) transByFrom.get(act.key()))
0711: .iterator(); i.hasNext();) {
0712: ExtTransitionLocal trans = (ExtTransitionLocal) i
0713: .next();
0714: if ((exception == null && trans.conditionType() == de.danet.an.workflow.api.Transition.COND_TYPE_OTHERWISE)
0715: || (exception != null && trans.conditionType() == de.danet.an.workflow.api.Transition.COND_TYPE_DEFAULTEXCEPTION)) {
0716: defaultTrans = trans;
0717: continue;
0718: }
0719: if (ctx.transitOK(trans)) {
0720: gotATrans = true;
0721: if (!tryTransit(trans, resets, starts)) {
0722: trans.setPendingToken(true);
0723: }
0724: }
0725: }
0726: if (!gotATrans && defaultTrans != null) {
0727: if (logger.isDebugEnabled()) {
0728: logger.debug("Trying default: " + defaultTrans);
0729: }
0730: if (!tryTransit(defaultTrans, resets, starts)) {
0731: defaultTrans.setPendingToken(true);
0732: }
0733: }
0734: } else /* act.splitMode().isXOR() */{
0735: // consider only first match of all transitions from this activity
0736: String group = null;
0737: for (Iterator i = ((Collection) transByFrom.get(act.key()))
0738: .iterator(); i.hasNext();) {
0739: ExtTransitionLocal trans = (ExtTransitionLocal) i
0740: .next();
0741: boolean contTransit = false;
0742: if (group != null) {
0743: if (!trans.group().equals(group)) {
0744: break; // end of group, quit.
0745: }
0746: contTransit = true;
0747: }
0748: if (logger.isDebugEnabled()) {
0749: logger.debug("Trying transition " + trans);
0750: }
0751: // note that reevalution of transit conditions is not
0752: // necessary within a group, it's the same condition.
0753: if (contTransit || ctx.transitOK(trans)) {
0754: if (logger.isDebugEnabled()) {
0755: logger.debug("... transit OK (continued: "
0756: + contTransit + ")");
0757: }
0758: if (!tryTransit(trans, resets, starts)) {
0759: trans.setPendingToken(true);
0760: }
0761: group = trans.group(); // continue for group
0762: }
0763: }
0764: }
0765: boolean deferChoiceOnSplit = act.deferChoiceOnSplit();
0766: for (Iterator items = starts.iterator(); items.hasNext();) {
0767: Object[] item = (Object[]) items.next();
0768: ExtActivityLocal startAct = (ExtActivityLocal) item[0];
0769: Collection triggers = (Collection) item[1];
0770: setStartable(startAct, triggers, deferChoiceOnSplit);
0771: }
0772: for (Iterator items = resets.iterator(); items.hasNext();) {
0773: Object[] item = (Object[]) items.next();
0774: ExtActivityLocal toAct = (ExtActivityLocal) item[0];
0775: Collection resetActs = (Collection) item[1];
0776:
0777: if (logger.isDebugEnabled()) {
0778: logger.debug("Resetting (true): " + toAct);
0779: }
0780: toAct.reset(true, false);
0781: resetTokens(toAct);
0782: for (Iterator i = resetActs.iterator(); i.hasNext();) {
0783: ExtActivityLocal resAct = (ExtActivityLocal) i.next();
0784: if (logger.isDebugEnabled()) {
0785: logger.debug("Resetting (false): " + resAct);
0786: }
0787: resAct.reset(false, false);
0788: resetTokens(resAct);
0789: closedActs.remove(resAct);
0790: }
0791: setStartable(toAct, null, deferChoiceOnSplit);
0792: }
0793: }
0794:
0795: private void resetTokens(ExtActivityLocal act) {
0796: for (Iterator i = ((Collection) transByFrom.get(act.key()))
0797: .iterator(); i.hasNext();) {
0798: ExtTransitionLocal trans = (ExtTransitionLocal) i.next();
0799: trans.setPendingToken(false);
0800: }
0801: }
0802:
0803: private void setStartable(ExtActivityLocal act,
0804: Collection triggers, boolean deferChoiceOnSplit) {
0805: if (logger.isDebugEnabled()) {
0806: StringBuffer s = new StringBuffer();
0807: s.append("Setting " + act + " startable, triggers: ");
0808: if (triggers != null) {
0809: boolean first = true;
0810: for (Iterator i = triggers.iterator(); i.hasNext();) {
0811: ExtActivityLocal ta = (ExtActivityLocal) i.next();
0812: if (!first) {
0813: s.append(", ");
0814: }
0815: s.append(ta.key());
0816: first = false;
0817: }
0818: }
0819: s.append(", preliminary chosen: " + deferChoiceOnSplit);
0820: logger.debug(s.toString());
0821: }
0822: act.setStartable(triggers, deferChoiceOnSplit);
0823: startableActs.put(act.key(), act);
0824: }
0825:
0826: /**
0827: * Test if a transition is possible considering the states and
0828: * history of the activities. Note that the condition for the
0829: * transition passed as parameter must have been verified and must
0830: * have evaluated to <code>true</code>.
0831: *
0832: * @param trans the transition.
0833: * @param resets activities that should be reset
0834: * @param starts activities that should be started
0835: * @return <code>true</code> if the transition can be made
0836: */
0837: private boolean tryTransit(ExtTransitionLocal trans,
0838: Collection resets, Collection starts) {
0839: if (logger.isDebugEnabled()) {
0840: logger.debug("Trying transit from " + trans.from() + " to "
0841: + trans.to());
0842: }
0843: ExtActivityLocal toAct = (ExtActivityLocal) trans.to();
0844: if (toAct.typedState()
0845: .isSameOrSubState(NotStartedState.UNKNOWN)) {
0846: Collection triggers = isStartable(trans, toAct);
0847: if (triggers != null) {
0848: starts.add(new Object[] { toAct, triggers });
0849: return true;
0850: }
0851: return false;
0852: }
0853: ExtActivityLocal fromAct = (ExtActivityLocal) trans.from();
0854: String toActKey = toAct.key();
0855: if (!((toAct.typedState().isSameOrSubState(
0856: ClosedState.COMPLETED) && (fromAct.threadInfo()
0857: .includes(toActKey) || fromAct.key().equals(toActKey))))) {
0858: return false;
0859: }
0860: // Now we reset the activities in the loop
0861: if (logger.isDebugEnabled()) {
0862: logger.debug("Loop detected: " + fromAct + " triggers "
0863: + toAct);
0864: }
0865: if (isStartable(trans, toAct) != null) {
0866: Collection resetActs = collectPredecessors(fromAct,
0867: toActKey);
0868: resets.add(new Object[] { toAct, resetActs });
0869: return true;
0870: }
0871: return false;
0872: }
0873:
0874: /**
0875: * Collect all predecessors of the given activity on the way back
0876: * to the given origin (including the start activity but excluding
0877: * the origin).
0878: * @param fromAct the activity to start from.
0879: * @param origin the activity to stop with.
0880: * @return the collected <code>Activity</code> objects. Note that
0881: * the result is a set although this cannot formally be specified
0882: * as the equals method is not defined for remote interfaces.
0883: */
0884: private Collection collectPredecessors(ExtActivityLocal fromAct,
0885: String origin) {
0886: Collection collActs = new ArrayList();
0887: collectPredecessors(collActs, new HashSet(), fromAct, origin);
0888: return collActs;
0889: }
0890:
0891: /**
0892: * Helper that collects the keys as well as the activities.
0893: * @param collected the collected activities.
0894: * @param collKeys the collected keys.
0895: * @param fromAct the activity to start from.
0896: * @param origin the activity to stop with.
0897: */
0898: private void collectPredecessors(Collection collected,
0899: Set collKeys, ExtActivityLocal fromAct, String origin) {
0900: try {
0901: if (fromAct.key().equals(origin)) {
0902: return;
0903: }
0904: collected.add(fromAct);
0905: collKeys.add(fromAct.key());
0906: Set pres = fromAct.threadInfo().predecessorsFor(origin);
0907: if (pres.contains(origin)) {
0908: return; // optimization, avoids unnecessary activity
0909: // lookups in loop, not required for proper function.
0910: }
0911: for (Iterator p = pres.iterator(); p.hasNext();) {
0912: String k = (String) p.next();
0913: if (collKeys.contains(k)) {
0914: continue;
0915: }
0916: ExtActivityLocal a = (ExtActivityLocal) myProcess
0917: .activityByKeyLocal(k);
0918: collectPredecessors(collected, collKeys, a, origin);
0919: }
0920: } catch (InvalidKeyException e) {
0921: // cannot happen
0922: throw new IllegalStateException();
0923: }
0924: }
0925:
0926: /**
0927: * Verify if an activity may be started, i.e. its predecessor(s)
0928: * are closed and the transition conditions evaluate to true.
0929: *
0930: * @param toTrans the transition leading to the activity to be
0931: * verified or <code>null</code> if the activity is to be
0932: * re-verified without a known trigger.
0933: * @param act the activity to be verified.
0934: * @param exceptions <code>null</code> if normal transitions are to
0935: * be evaluated, else the exceptions defined for the activity with
0936: * the first one being the triggered exception
0937: * @return the activities that trigger the given one or
0938: * <code>null</code> if the activity is not startable
0939: */
0940: private Collection isStartable(ExtTransitionLocal toTrans,
0941: ExtActivityLocal act) {
0942: Collection triggers = new ArrayList();
0943: // now things depend on the join mode
0944: if (logger.isDebugEnabled()) {
0945: logger.debug("Testing join for " + act + ", mode "
0946: + act.joinMode());
0947: }
0948: if (act.joinMode().isAND()) {
0949: ExtTransitionLocal trans = null;
0950: String curGroup = null;
0951: boolean groupOK = false;
0952: for (Iterator i = ((Collection) transByTo.get(act.key()))
0953: .iterator(); i.hasNext();) {
0954: trans = (ExtTransitionLocal) i.next();
0955: if (curGroup == null) {
0956: curGroup = trans.group();
0957: } else if (!curGroup.equals(trans.group())) {
0958: if (!groupOK) {
0959: break;
0960: }
0961: curGroup = trans.group();
0962: groupOK = false;
0963: }
0964: if (logger.isDebugEnabled()) {
0965: if (toTrans != null && trans.equals(toTrans)) {
0966: logger.debug("Transition " + trans
0967: + " is triggering (implicitly true)");
0968: }
0969: }
0970: if (!(toTrans != null && trans.equals(toTrans))
0971: && !trans.hasPendingToken()) {
0972: continue;
0973: }
0974: groupOK = true;
0975: addToTriggers(triggers, trans.from());
0976: }
0977: if (!groupOK) {
0978: if (logger.isDebugEnabled()) {
0979: logger.debug(act + " not startable");
0980: }
0981: return null;
0982: }
0983: } else { // act.joinMode().isXOR()
0984: addToTriggers(triggers, toTrans.from());
0985: }
0986: if (logger.isDebugEnabled()) {
0987: logger.debug(act + " is startable");
0988: }
0989: return triggers;
0990: }
0991:
0992: private void addToTriggers(Collection triggers, ActivityLocal act) {
0993: // do not record sources of asynchronous deadlines
0994: if (act.typedState().isSameOrSubState(State.CLOSED)) {
0995: if (act instanceof BAForExceptionHandling) {
0996: triggers.addAll(((BAForExceptionHandling) act)
0997: .predecessors());
0998: } else {
0999: triggers.add(act);
1000: }
1001: }
1002: }
1003:
1004: /**
1005: * Return the startable activities.
1006: * @return a collection of startable activities.
1007: */
1008: public Collection startableActivities() {
1009: Collection res = new ArrayList();
1010: for (Iterator i = startableActs.values().iterator(); i
1011: .hasNext();) {
1012: ExtActivityLocal a = (ExtActivityLocal) i.next();
1013: if (a.typedState().isSameOrSubState(
1014: NotRunningState.NOT_STARTED)) {
1015: res.add(a);
1016: }
1017: }
1018: return res;
1019: }
1020:
1021: /**
1022: * Given an activity from a set of activities started in a
1023: * deferred choice, reset all other (competing) activities.
1024: * @param act the activity
1025: */
1026: public void resetCompeting(ExtActivityLocal act)
1027: throws TransitionNotAllowedException {
1028: ThreadInfo threadInfo = act.threadInfo();
1029: Collection res = new ArrayList();
1030: Collection predecessors = (Collection) transByTo.get(act.key());
1031: if (predecessors.size() == 0) {
1032: throw new IllegalArgumentException(
1033: act
1034: + " has no predecessors, cannot be preliminarily chosen");
1035: }
1036: for (Iterator i = predecessors.iterator(); i.hasNext();) {
1037: ExtTransitionLocal trans = (ExtTransitionLocal) i.next();
1038: ExtActivityLocal fromAct = (ExtActivityLocal) trans.from();
1039: if (fromAct.deferChoiceOnSplit()
1040: && threadInfo.includes(fromAct.key())) {
1041: for (Iterator j = ((Collection) transByFrom.get(fromAct
1042: .key())).iterator(); j.hasNext();) {
1043: ExtTransitionLocal ft = (ExtTransitionLocal) j
1044: .next();
1045: res.add(ft.to());
1046: ft.setPendingToken(false);
1047: }
1048: }
1049: }
1050: if (logger.isDebugEnabled()) {
1051: StringBuffer s = new StringBuffer();
1052: s.append("Preliminary chosen with " + act + ": ");
1053: boolean first = true;
1054: for (Iterator i = res.iterator(); i.hasNext();) {
1055: ExtActivityLocal a = (ExtActivityLocal) i.next();
1056: if (first) {
1057: first = false;
1058: } else {
1059: s.append(", ");
1060: }
1061: s.append(a.toString());
1062: }
1063: logger.debug(s);
1064: }
1065: // terminate others
1066: for (Iterator i = res.iterator(); i.hasNext();) {
1067: ExtActivityLocal dcAct = (ExtActivityLocal) i.next();
1068: if (!dcAct.key().equals(act.key())) {
1069: try {
1070: if (dcAct.preliminarilyChosen()) {
1071: dcAct.withdrawPreliminaryChoice(true);
1072: update(dcAct);
1073: }
1074: } catch (TransitionNotAllowedException e) {
1075: logger.error("Inconsistent state: "
1076: + e.getMessage(), e);
1077: }
1078: }
1079: }
1080: act.withdrawPreliminaryChoice(false);
1081: }
1082:
1083: private void logState(String context) {
1084: logger.debug("Engine state for " + myProcess.toString()
1085: + "(context: " + context + "):");
1086: logger.debug(" Transition manager: " + this );
1087: logger.debug(" Process is at end: " + isAtEnd());
1088: for (Iterator i = startableActs.values().iterator(); i
1089: .hasNext();) {
1090: ExtActivityLocal a = (ExtActivityLocal) i.next();
1091: logger.debug(" Startable: " + a);
1092: }
1093: for (Iterator i = runningActs.values().iterator(); i.hasNext();) {
1094: ExtActivityLocal a = (ExtActivityLocal) i.next();
1095: logger.debug(" Running: " + a);
1096: }
1097: }
1098: }
|