Package de.danet.an.workflow.domain

Source Code of de.danet.an.workflow.domain.TransitionManager$EvaluationContext

/*
* 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);
  }
    }
}
TOP

Related Classes of de.danet.an.workflow.domain.TransitionManager$EvaluationContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.