View Javadoc

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