View Javadoc

    1   /*
    2    *
    3    *   Copyright 2005-2006 The Apache Software Foundation.
    4    *
    5    *  Licensed under the Apache License, Version 2.0 (the "License");
    6    *  you may not use this file except in compliance with the License.
    7    *  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   *  Unless required by applicable law or agreed to in writing, software
    12   *  distributed under the License is distributed on an "AS IS" BASIS,
    13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   *  See the License for the specific language governing permissions and
    15   *  limitations under the License.
    16   *
    17   */
    18  package org.apache.commons.scxml;
    19
    20  import java.util.ArrayList;
    21  import java.util.Arrays;
    22  import java.util.HashMap;
    23  import java.util.Iterator;
    24  import java.util.List;
    25  import java.util.Map;
    26
    27  import org.apache.commons.logging.Log;
    28  import org.apache.commons.logging.LogFactory;
    29  import org.apache.commons.scxml.model.Datamodel;
    30  import org.apache.commons.scxml.model.History;
    31  import org.apache.commons.scxml.model.ModelException;
    32  import org.apache.commons.scxml.model.SCXML;
    33  import org.apache.commons.scxml.model.State;
    34  import org.apache.commons.scxml.model.Transition;
    35  import org.apache.commons.scxml.model.TransitionTarget;
    36  import org.apache.commons.scxml.semantics.SCXMLSemanticsImpl;
    37
    38  /***
    39   * <p>The SCXML &quot;engine&quot; that executes SCXML documents. The
    40   * particular semantics used by this engine for executing the SCXML are
    41   * encapsulated in the SCXMLSemantics implementation that it uses.</p>
    42   *
    43   * <p>The default implementation is
    44   * <code>org.apache.commons.scxml.semantics.SCXMLSemanticsImpl</code></p>
    45   *
    46   * @see SCXMLSemantics
    47   */
    48  public class SCXMLExecutor {
    49
    50      /***
    51       * The Logger for the SCXMLExecutor.
    52       */
    53      private Log log = LogFactory.getLog(SCXMLExecutor.class);
    54
    55      /***
    56       * The stateMachine being executed.
    57       */
    58      private SCXML stateMachine;
    59
    60      /***
    61       * The current status of the stateMachine.
    62       */
    63      private Status currentStatus;
    64
    65      /***
    66       * The event dispatcher to interface with external documents etc.
    67       */
    68      private EventDispatcher eventdispatcher;
    69
    70      /***
    71       * The environment specific error reporter.
    72       */
    73      private ErrorReporter errorReporter = null;
    74
    75      /***
    76       * Run-to-completion.
    77       */
    78      private boolean superStep = true;
    79
    80      /***
    81       *  Interpretation semantics.
    82       */
    83      private SCXMLSemantics semantics;
    84
    85      /***
    86       * The SCInstance.
    87       */
    88      private SCInstance scInstance;
    89
    90      /***
    91       * The worker method.
    92       * Re-evaluates current status whenever any events are triggered.
    93       *
    94       * @param evts
    95       *            an array of external events which triggered during the last
    96       *            time quantum
    97       * @throws ModelException in case there is a fatal SCXML object
    98       *            model problem.
    99       */
    100     public void triggerEvents(final TriggerEvent[] evts)
    101             throws ModelException {
    102         // Set event data, saving old values
    103         Object[] oldData = setEventData(evts);
    104
    105         // Forward events (external only) to any existing invokes,
    106         // and finalize processing
    107         semantics.processInvokes(evts, errorReporter, scInstance);
    108
    109         List evs = new ArrayList(Arrays.asList(evts));
    110         Step step = null;
    111
    112         do {
    113             // CreateStep
    114             step = new Step(evs, currentStatus);
    115             // EnumerateReachableTransitions
    116             semantics.enumerateReachableTransitions(stateMachine, step,
    117                 errorReporter);
    118             // FilterTransitionSet
    119             semantics.filterTransitionsSet(step, eventdispatcher,
    120                 errorReporter, scInstance);
    121             // FollowTransitions
    122             semantics.followTransitions(step, errorReporter, scInstance);
    123             // UpdateHistoryStates
    124             semantics.updateHistoryStates(step, errorReporter, scInstance);
    125             // ExecuteActions
    126             semantics.executeActions(step, stateMachine, eventdispatcher,
    127                 errorReporter, scInstance);
    128             // AssignCurrentStatus
    129             updateStatus(step);
    130             // ***Cleanup external events if superStep
    131             if (superStep) {
    132                 evs.clear();
    133             }
    134         } while (superStep && currentStatus.getEvents().size() > 0);
    135
    136         // InitiateInvokes only after state machine has stabilized
    137         semantics.initiateInvokes(step, errorReporter, scInstance);
    138
    139         // Restore event data
    140         restoreEventData(oldData);
    141         logState();
    142     }
    143
    144     /***
    145      * Convenience method when only one event needs to be triggered.
    146      *
    147      * @param evt
    148      *            the external events which triggered during the last
    149      *            time quantum
    150      * @throws ModelException in case there is a fatal SCXML object
    151      *            model problem.
    152      */
    153     public void triggerEvent(final TriggerEvent evt)
    154             throws ModelException {
    155         triggerEvents(new TriggerEvent[] {evt});
    156     }
    157
    158     /***
    159      * Constructor.
    160      *
    161      * @param expEvaluator The expression evaluator
    162      * @param evtDisp The event dispatcher
    163      * @param errRep The error reporter
    164      */
    165     public SCXMLExecutor(final Evaluator expEvaluator,
    166             final EventDispatcher evtDisp, final ErrorReporter errRep) {
    167         this(expEvaluator, evtDisp, errRep, null);
    168     }
    169
    170     /***
    171      * Convenience constructor.
    172      */
    173     public SCXMLExecutor() {
    174         this(null, null, null, null);
    175     }
    176
    177     /***
    178      * Constructor.
    179      *
    180      * @param expEvaluator The expression evaluator
    181      * @param evtDisp The event dispatcher
    182      * @param errRep The error reporter
    183      * @param semantics The SCXML semantics
    184      */
    185     public SCXMLExecutor(final Evaluator expEvaluator,
    186             final EventDispatcher evtDisp, final ErrorReporter errRep,
    187             final SCXMLSemantics semantics) {
    188         this.eventdispatcher = evtDisp;
    189         this.errorReporter = errRep;
    190         this.currentStatus = null;
    191         this.stateMachine = null;
    192         if (semantics == null) {
    193             // Use default semantics, if none provided
    194             this.semantics = new SCXMLSemanticsImpl();
    195         } else {
    196             this.semantics = semantics;
    197         }
    198         this.currentStatus = null;
    199         this.stateMachine = null;
    200         this.scInstance = new SCInstance(this);
    201         this.scInstance.setEvaluator(expEvaluator);
    202     }
    203
    204     /***
    205      * Clear all state and begin from &quot;initialstate&quot; indicated
    206      * on root SCXML element.
    207      *
    208      * @throws ModelException in case there is a fatal SCXML object
    209      *         model problem.
    210      */
    211     public void reset() throws ModelException {
    212         // Reset all variable contexts
    213         Context rootCtx = scInstance.getRootContext();
    214         // Clone root datamodel
    215         if (stateMachine == null) {
    216             log.error(ERR_NO_STATE_MACHINE);
    217             throw new ModelException(ERR_NO_STATE_MACHINE);
    218         } else {
    219             Datamodel rootdm = stateMachine.getDatamodel();
    220             SCXMLHelper.cloneDatamodel(rootdm, rootCtx,
    221                 scInstance.getEvaluator(), log);
    222         }
    223         // all states and parallels, only states have variable contexts
    224         for (Iterator i = stateMachine.getTargets().values().iterator();
    225                 i.hasNext();) {
    226             TransitionTarget tt = (TransitionTarget) i.next();
    227             if (tt instanceof State) {
    228                 Context context = scInstance.lookupContext(tt);
    229                 if (context != null) {
    230                     context.reset();
    231                     Datamodel dm = tt.getDatamodel();
    232                     if (dm != null) {
    233                         SCXMLHelper.cloneDatamodel(dm, context,
    234                             scInstance.getEvaluator(), log);
    235                     }
    236                 }
    237             } else if (tt instanceof History) {
    238                 scInstance.reset((History) tt);
    239             }
    240         }
    241         // CreateEmptyStatus
    242         currentStatus = new Status();
    243         Step step = new Step(null, currentStatus);
    244         // DetermineInitialStates
    245         semantics.determineInitialStates(stateMachine,
    246                 step.getAfterStatus().getStates(),
    247                 step.getEntryList(), errorReporter, scInstance);
    248         // ExecuteActions
    249         semantics.executeActions(step, stateMachine, eventdispatcher,
    250                 errorReporter, scInstance);
    251         // AssignCurrentStatus
    252         updateStatus(step);
    253         // Execute Immediate Transitions
    254         if (superStep && currentStatus.getEvents().size() > 0) {
    255             this.triggerEvents(new TriggerEvent[0]);
    256         } else {
    257             // InitiateInvokes only after state machine has stabilized
    258             semantics.initiateInvokes(step, errorReporter, scInstance);
    259             logState();
    260         }
    261     }
    262
    263     /***
    264      * Get the current status.
    265      *
    266      * @return The current Status
    267      */
    268     public Status getCurrentStatus() {
    269         return currentStatus;
    270     }
    271
    272     /***
    273      * Set the expression evaluator.
    274      *
    275      * @param evaluator The evaluator to set.
    276      */
    277     public void setEvaluator(final Evaluator evaluator) {
    278         this.scInstance.setEvaluator(evaluator);
    279     }
    280
    281     /***
    282      * Get the expression evaluator in use.
    283      *
    284      * @return Evaluator The evaluator in use.
    285      */
    286     public Evaluator getEvaluator() {
    287         return scInstance.getEvaluator();
    288     }
    289
    290     /***
    291      * Set the root context for this execution.
    292      *
    293      * @param rootContext The Context that ties to the host environment.
    294      */
    295     public void setRootContext(final Context rootContext) {
    296         this.scInstance.setRootContext(rootContext);
    297     }
    298
    299     /***
    300      * Get the root context for this execution.
    301      *
    302      * @return Context The root context.
    303      */
    304     public Context getRootContext() {
    305         return scInstance.getRootContext();
    306     }
    307
    308     /***
    309      * Get the state machine that is being executed.
    310      *
    311      * @return Returns the stateMachine.
    312      */
    313     public SCXML getStateMachine() {
    314         return stateMachine;
    315     }
    316
    317     /***
    318      * Set the state machine to be executed.
    319      *
    320      * @param stateMachine The stateMachine to set.
    321      */
    322     public void setStateMachine(final SCXML stateMachine) {
    323         // NormalizeStateMachine
    324         SCXML sm = semantics.normalizeStateMachine(stateMachine,
    325                 errorReporter);
    326         // StoreStateMachine
    327         this.stateMachine = sm;
    328     }
    329
    330     /***
    331      * Initiate state machine execution.
    332      *
    333      * @throws ModelException in case there is a fatal SCXML object
    334      *  model problem.
    335      */
    336     public void go() throws ModelException {
    337         // same as reset
    338         this.reset();
    339     }
    340
    341     /***
    342      * Get the environment specific error reporter.
    343      *
    344      * @return Returns the errorReporter.
    345      */
    346     public ErrorReporter getErrorReporter() {
    347         return errorReporter;
    348     }
    349
    350     /***
    351      * Set the environment specific error reporter.
    352      *
    353      * @param errorReporter The errorReporter to set.
    354      */
    355     public void setErrorReporter(final ErrorReporter errorReporter) {
    356         this.errorReporter = errorReporter;
    357     }
    358
    359     /***
    360      * Get the event dispatcher.
    361      *
    362      * @return Returns the eventdispatcher.
    363      */
    364     public EventDispatcher getEventdispatcher() {
    365         return eventdispatcher;
    366     }
    367
    368     /***
    369      * Set the event dispatcher.
    370      *
    371      * @param eventdispatcher The eventdispatcher to set.
    372      */
    373     public void setEventdispatcher(final EventDispatcher eventdispatcher) {
    374         this.eventdispatcher = eventdispatcher;
    375     }
    376
    377     /***
    378      * Use &quot;super-step&quot;, default is <code>true</code>
    379      * (that is, run-to-completion is default).
    380      *
    381      * @return Returns the superStep property.
    382      * @see #setSuperStep(boolean)
    383      */
    384     public boolean isSuperStep() {
    385         return superStep;
    386     }
    387
    388     /***
    389      * Set the super step.
    390      *
    391      * @param superStep
    392      * if true, the internal derived events are also processed
    393      *    (run-to-completion);
    394      * if false, the internal derived events are stored in the
    395      * CurrentStatus property and processed within the next
    396      * triggerEvents() invocation, also the immediate (empty event) transitions
    397      * are deferred until the next step
    398       */
    399     public void setSuperStep(final boolean superStep) {
    400         this.superStep = superStep;
    401     }
    402
    403     /***
    404      * Add a listener to the document root.
    405      *
    406      * @param scxml The document root to attach listener to.
    407      * @param listener The SCXMLListener.
    408      */
    409     public void addListener(final SCXML scxml, final SCXMLListener listener) {
    410         Object observable = scxml;
    411         scInstance.getNotificationRegistry().addListener(observable, listener);
    412     }
    413
    414     /***
    415      * Remove this listener from the document root.
    416      *
    417      * @param scxml The document root.
    418      * @param listener The SCXMLListener to be removed.
    419      */
    420     public void removeListener(final SCXML scxml,
    421             final SCXMLListener listener) {
    422         Object observable = scxml;
    423         scInstance.getNotificationRegistry().removeListener(observable,
    424             listener);
    425     }
    426
    427     /***
    428      * Add a listener to this transition target.
    429      *
    430      * @param transitionTarget The <code>TransitionTarget</code> to
    431      *                         attach listener to.
    432      * @param listener The SCXMLListener.
    433      */
    434     public void addListener(final TransitionTarget transitionTarget,
    435             final SCXMLListener listener) {
    436         Object observable = transitionTarget;
    437         scInstance.getNotificationRegistry().addListener(observable, listener);
    438     }
    439
    440     /***
    441      * Remove this listener for this transition target.
    442      *
    443      * @param transitionTarget The <code>TransitionTarget</code>.
    444      * @param listener The SCXMLListener to be removed.
    445      */
    446     public void removeListener(final TransitionTarget transitionTarget,
    447             final SCXMLListener listener) {
    448         Object observable = transitionTarget;
    449         scInstance.getNotificationRegistry().removeListener(observable,
    450             listener);
    451     }
    452
    453     /***
    454      * Add a listener to this transition.
    455      *
    456      * @param transition The <code>Transition</code> to attach listener to.
    457      * @param listener The SCXMLListener.
    458      */
    459     public void addListener(final Transition transition,
    460             final SCXMLListener listener) {
    461         Object observable = transition;
    462         scInstance.getNotificationRegistry().addListener(observable, listener);
    463     }
    464
    465     /***
    466      * Remove this listener for this transition.
    467      *
    468      * @param transition The <code>Transition</code>.
    469      * @param listener The SCXMLListener to be removed.
    470      */
    471     public void removeListener(final Transition transition,
    472             final SCXMLListener listener) {
    473         Object observable = transition;
    474         scInstance.getNotificationRegistry().removeListener(observable,
    475             listener);
    476     }
    477
    478     /***
    479      * Register an <code>Invoker</code> for this target type.
    480      *
    481      * @param targettype The target type (specified by "targettype"
    482      *                   attribute of &lt;invoke&gt; tag).
    483      * @param invokerClass The <code>Invoker</code> <code>Class</code>.
    484      */
    485     public void registerInvokerClass(final String targettype,
    486             final Class invokerClass) {
    487         scInstance.registerInvokerClass(targettype, invokerClass);
    488     }
    489
    490     /***
    491      * Remove the <code>Invoker</code> registered for this target
    492      * type (if there is one registered).
    493      *
    494      * @param targettype The target type (specified by "targettype"
    495      *                   attribute of &lt;invoke&gt; tag).
    496      */
    497     public void unregisterInvokerClass(final String targettype) {
    498         scInstance.unregisterInvokerClass(targettype);
    499     }
    500
    501     /***
    502      * Get the state chart instance for this executor.
    503      *
    504      * @return The SCInstance for this executor.
    505      */
    506     SCInstance getSCInstance() {
    507         return scInstance;
    508     }
    509
    510     /***
    511      * Log the current set of active states.
    512      */
    513     private void logState() {
    514         if (log.isInfoEnabled()) {
    515             Iterator si = currentStatus.getStates().iterator();
    516             StringBuffer sb = new StringBuffer("Current States: [");
    517             while (si.hasNext()) {
    518                 State s = (State) si.next();
    519                 sb.append(s.getId());
    520                 if (si.hasNext()) {
    521                     sb.append(", ");
    522                 }
    523             }
    524             sb.append(']');
    525             log.info(sb.toString());
    526         }
    527     }
    528
    529     /***
    530      * @param step The most recent Step
    531      */
    532     private void updateStatus(final Step step) {
    533         currentStatus = step.getAfterStatus();
    534         scInstance.getRootContext().setLocal("_ALL_STATES",
    535             SCXMLHelper.getAncestorClosure(currentStatus.getStates(), null));
    536     }
    537
    538     /***
    539      * @param evts The events being triggered.
    540      * @return Object[] Previous values.
    541      */
    542     private Object[] setEventData(final TriggerEvent[] evts) {
    543         Context rootCtx = scInstance.getRootContext();
    544         Object[] oldData = {rootCtx.get(EVENT_DATA),
    545             rootCtx.get(EVENT_DATA_MAP)};
    546         Object eventData = null;
    547         Map payloadMap = new HashMap();
    548         int len = evts.length;
    549         for (int i = 0; i < len; i++) {
    550             TriggerEvent te = evts[i];
    551             payloadMap.put(te.getName(), te.getPayload());
    552         }
    553         if (len == 1) {
    554             // we have only one event
    555             eventData = evts[0].getPayload();
    556         }
    557         rootCtx.setLocal(EVENT_DATA, eventData);
    558         rootCtx.setLocal(EVENT_DATA_MAP, payloadMap);
    559         return oldData;
    560     }
    561
    562     /***
    563      * @param oldData The old values to restore to.
    564      */
    565     private void restoreEventData(final Object[] oldData) {
    566         scInstance.getRootContext().setLocal(EVENT_DATA, oldData[0]);
    567         scInstance.getRootContext().setLocal(EVENT_DATA_MAP, oldData[1]);
    568     }
    569
    570     /***
    571      * The special variable for storing single event data / payload.
    572      */
    573     private static final String EVENT_DATA = "_eventdata";
    574
    575     /***
    576      * The special variable for storing event data / payload,
    577      * when multiple events are triggered, keyed by event name.
    578      */
    579     private static final String EVENT_DATA_MAP = "_eventdatamap";
    580
    581     /***
    582      * SCXMLExecutor put into motion without setting a model (state machine).
    583      */
    584     private static final String ERR_NO_STATE_MACHINE =
    585         "SCXMLExecutor: State machine not set";
    586
    587 }
    588