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