View Javadoc

    1   /*
    2    *
    3    *   Copyright 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.env;
    19
    20  import java.io.IOException;
    21  import java.lang.reflect.InvocationTargetException;
    22  import java.lang.reflect.Method;
    23  import java.net.URL;
    24
    25  import org.apache.commons.logging.Log;
    26  import org.apache.commons.logging.LogFactory;
    27  import org.apache.commons.scxml.Context;
    28  import org.apache.commons.scxml.Evaluator;
    29  import org.apache.commons.scxml.SCXMLExecutor;
    30  import org.apache.commons.scxml.SCXMLListener;
    31  import org.apache.commons.scxml.TriggerEvent;
    32  import org.apache.commons.scxml.env.jexl.JexlContext;
    33  import org.apache.commons.scxml.env.jexl.JexlEvaluator;
    34  import org.apache.commons.scxml.io.SCXMLDigester;
    35  import org.apache.commons.scxml.model.ModelException;
    36  import org.apache.commons.scxml.model.SCXML;
    37  import org.apache.commons.scxml.model.Transition;
    38  import org.apache.commons.scxml.model.TransitionTarget;
    39  import org.xml.sax.ErrorHandler;
    40  import org.xml.sax.SAXException;
    41
    42  /***
    43   * This class demonstrates one approach for providing the base
    44   * functionality needed by classes representing stateful entities,
    45   * whose behaviors are defined via SCXML documents.
    46   *
    47   * SCXML documents (more generically, UML state chart diagrams) can be
    48   * used to define stateful behavior of objects, and Commons SCXML enables
    49   * developers to use this model directly into the corresponding code
    50   * artifacts. The resulting artifacts tend to be much simpler, embody
    51   * a useful separation of concerns and are easier to understand and
    52   * maintain. As the size of the modeled entity grows, these benefits
    53   * become more apparent.
    54   *
    55   * This approach functions by registering an SCXMLListener that gets
    56   * notified onentry, and calls the namesake method for each state that
    57   * has been entered.
    58   *
    59   * This class swallows all exceptions only to log them. Developers of
    60   * subclasses should think of themselves as &quot;component developers&quot;
    61   * catering to other end users, and therefore ensure that the subclasses
    62   * are free of <code>ModelException</code>s and the like. Most methods
    63   * are <code>protected</code> for ease of subclassing.
    64   *
    65   */
    66  public abstract class AbstractStateMachine {
    67
    68      /***
    69       * The state machine that will drive the instances of this class.
    70       */
    71      private static SCXML stateMachine;
    72
    73      /***
    74       * The instance specific SCXML engine.
    75       */
    76      private SCXMLExecutor engine;
    77
    78      /***
    79       * The log.
    80       */
    81      private Log log;
    82
    83      /***
    84       * The method signature for the activities corresponding to each
    85       * state in the SCXML document.
    86       */
    87      private static final Class[] SIGNATURE = new Class[0];
    88
    89      /***
    90       * The method parameters for the activities corresponding to each
    91       * state in the SCXML document.
    92       */
    93      private static final Object[] PARAMETERS = new Object[0];
    94
    95      /***
    96       * Convenience constructor.
    97       *
    98       * @param scxmlDocument The URL pointing to the SCXML document that
    99       *                      describes the &quot;lifecycle&quot; of the
    100      *                      instances of this class.
    101      */
    102     public AbstractStateMachine(final URL scxmlDocument) {
    103         // default is JEXL
    104         this(scxmlDocument, new JexlContext(), new JexlEvaluator());
    105     }
    106
    107     /***
    108      * Primary constructor.
    109      *
    110      * @param scxmlDocument The URL pointing to the SCXML document that
    111      *                      describes the &quot;lifecycle&quot; of the
    112      *                      instances of this class.
    113      * @param rootCtx The root context for this instance.
    114      * @param evaluator The expression evaluator for this instance.
    115      *
    116      * @see Context
    117      * @see Evaluator
    118      */
    119     public AbstractStateMachine(final URL scxmlDocument,
    120             final Context rootCtx, final Evaluator evaluator) {
    121         log = LogFactory.getLog(this.getClass());
    122         if (stateMachine == null) {
    123             // parse only once per subclass
    124             ErrorHandler errHandler = new SimpleErrorHandler();
    125             try {
    126                 stateMachine = SCXMLDigester.digest(scxmlDocument,
    127                     errHandler);
    128             } catch (IOException ioe) {
    129                 logError(ioe);
    130             } catch (SAXException sae) {
    131                 logError(sae);
    132             } catch (ModelException me) {
    133                 logError(me);
    134             }
    135         }
    136         engine = new SCXMLExecutor(evaluator, new SimpleDispatcher(),
    137             new SimpleErrorReporter());
    138         engine.setStateMachine(stateMachine);
    139         engine.setSuperStep(true);
    140         engine.setRootContext(rootCtx);
    141         engine.addListener(stateMachine, new EntryListener());
    142         try {
    143             engine.go();
    144         } catch (ModelException me) {
    145             logError(me);
    146         }
    147     }
    148
    149     /***
    150      * Fire an event on the SCXML engine.
    151      *
    152      * @param event The event name.
    153      * @return Whether the state machine has reached a &quot;final&quot;
    154      *         configuration.
    155      */
    156     public boolean fireEvent(final String event) {
    157         TriggerEvent[] evts = {new TriggerEvent(event,
    158                 TriggerEvent.SIGNAL_EVENT, null)};
    159         try {
    160             engine.triggerEvents(evts);
    161         } catch (ModelException me) {
    162             logError(me);
    163         }
    164         return engine.getCurrentStatus().isFinal();
    165     }
    166
    167     /***
    168      * Get the SCXML object representing this state machine.
    169      *
    170      * @return Returns the stateMachine.
    171      */
    172     public static SCXML getStateMachine() {
    173         return stateMachine;
    174     }
    175
    176     /***
    177      * Get the SCXML engine driving the &quot;lifecycle&quot; of the
    178      * instances of this class.
    179      *
    180      * @return Returns the engine.
    181      */
    182     public SCXMLExecutor getEngine() {
    183         return engine;
    184     }
    185
    186     /***
    187      * Get the log for this class.
    188      *
    189      * @return Returns the log.
    190      */
    191     public Log getLog() {
    192         return log;
    193     }
    194
    195     /***
    196      * Set the log for this class.
    197      *
    198      * @param log The log to set.
    199      */
    200     public void setLog(final Log log) {
    201         this.log = log;
    202     }
    203
    204     /***
    205      * Invoke the no argument method with the following name.
    206      *
    207      * @param methodName The method to invoke.
    208      * @return Whether the invoke was successful.
    209      */
    210     public boolean invoke(final String methodName) {
    211         Class clas = this.getClass();
    212         try {
    213             Method method = clas.getDeclaredMethod(methodName, SIGNATURE);
    214             method.invoke(this, PARAMETERS);
    215         } catch (SecurityException se) {
    216             logError(se);
    217             return false;
    218         } catch (NoSuchMethodException nsme) {
    219             logError(nsme);
    220             return false;
    221         } catch (IllegalArgumentException iae) {
    222             logError(iae);
    223             return false;
    224         } catch (IllegalAccessException iae) {
    225             logError(iae);
    226             return false;
    227         } catch (InvocationTargetException ite) {
    228             logError(ite);
    229             return false;
    230         }
    231         return true;
    232     }
    233
    234     /***
    235      * Reset the state machine.
    236      *
    237      * @return Whether the reset was successful.
    238      */
    239     public boolean resetMachine() {
    240         try {
    241             engine.reset();
    242         } catch (ModelException me) {
    243             logError(me);
    244             return false;
    245         }
    246         return true;
    247     }
    248
    249     /***
    250      * Utility method for logging error.
    251      *
    252      * @param exception The exception leading to this error condition.
    253      */
    254     protected void logError(final Exception exception) {
    255         if (log.isErrorEnabled()) {
    256             log.error(exception.getMessage(), exception);
    257         }
    258     }
    259
    260     /***
    261      * A SCXMLListener that is only concerned about &quot;onentry&quot;
    262      * notifications.
    263      */
    264     protected class EntryListener implements SCXMLListener {
    265
    266         /***
    267          * {@inheritDoc}
    268          */
    269         public void onEntry(final TransitionTarget entered) {
    270             invoke(entered.getId());
    271         }
    272
    273         /***
    274          * No-op.
    275          *
    276          * @param from The &quot;source&quot; transition target.
    277          * @param to The &quot;destination&quot; transition target.
    278          * @param transition The transition being followed.
    279          */
    280         public void onTransition(final TransitionTarget from,
    281                 final TransitionTarget to, final Transition transition) {
    282             // nothing to do
    283         }
    284
    285         /***
    286          * No-op.
    287          *
    288          * @param exited The transition target being exited.
    289          */
    290         public void onExit(final TransitionTarget exited) {
    291             // nothing to do
    292         }
    293
    294     }
    295
    296 }
    297