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.SCXMLDigester;
    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   * 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.
    45   *
    46   * 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.
    53   *
    54   * 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.
    57   *
    58   * 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.
    63   *
    64   */
    65  public abstract class AbstractStateMachine {
    66
    67      /***
    68       * The state machine that will drive the instances of this class.
    69       */
    70      private static 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.
    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.
    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         if (stateMachine == null) {
    122             // parse only once per subclass
    123             ErrorHandler errHandler = new SimpleErrorHandler();
    124             try {
    125                 stateMachine = SCXMLDigester.digest(scxmlDocument,
    126                     errHandler);
    127             } catch (IOException ioe) {
    128                 logError(ioe);
    129             } catch (SAXException sae) {
    130                 logError(sae);
    131             } catch (ModelException me) {
    132                 logError(me);
    133             }
    134         }
    135         engine = new SCXMLExecutor(evaluator, new SimpleDispatcher(),
    136             new SimpleErrorReporter());
    137         engine.setStateMachine(stateMachine);
    138         engine.setSuperStep(true);
    139         engine.setRootContext(rootCtx);
    140         engine.addListener(stateMachine, new EntryListener());
    141         try {
    142             engine.go();
    143         } catch (ModelException me) {
    144             logError(me);
    145         }
    146     }
    147
    148     /***
    149      * Fire an event on the SCXML engine.
    150      *
    151      * @param event The event name.
    152      * @return Whether the state machine has reached a &quot;final&quot;
    153      *         configuration.
    154      */
    155     public boolean fireEvent(final String event) {
    156         TriggerEvent[] evts = {new TriggerEvent(event,
    157                 TriggerEvent.SIGNAL_EVENT, null)};
    158         try {
    159             engine.triggerEvents(evts);
    160         } catch (ModelException me) {
    161             logError(me);
    162         }
    163         return engine.getCurrentStatus().isFinal();
    164     }
    165
    166     /***
    167      * Get the SCXML object representing this state machine.
    168      *
    169      * @return Returns the stateMachine.
    170      */
    171     public static SCXML getStateMachine() {
    172         return stateMachine;
    173     }
    174
    175     /***
    176      * Get the SCXML engine driving the &quot;lifecycle&quot; of the
    177      * instances of this class.
    178      *
    179      * @return Returns the engine.
    180      */
    181     public SCXMLExecutor getEngine() {
    182         return engine;
    183     }
    184
    185     /***
    186      * Get the log for this class.
    187      *
    188      * @return Returns the log.
    189      */
    190     public Log getLog() {
    191         return log;
    192     }
    193
    194     /***
    195      * Set the log for this class.
    196      *
    197      * @param log The log to set.
    198      */
    199     public void setLog(final Log log) {
    200         this.log = log;
    201     }
    202
    203     /***
    204      * Invoke the no argument method with the following name.
    205      *
    206      * @param methodName The method to invoke.
    207      * @return Whether the invoke was successful.
    208      */
    209     public boolean invoke(final String methodName) {
    210         Class clas = this.getClass();
    211         try {
    212             Method method = clas.getDeclaredMethod(methodName, SIGNATURE);
    213             method.invoke(this, PARAMETERS);
    214         } catch (SecurityException se) {
    215             logError(se);
    216             return false;
    217         } catch (NoSuchMethodException nsme) {
    218             logError(nsme);
    219             return false;
    220         } catch (IllegalArgumentException iae) {
    221             logError(iae);
    222             return false;
    223         } catch (IllegalAccessException iae) {
    224             logError(iae);
    225             return false;
    226         } catch (InvocationTargetException ite) {
    227             logError(ite);
    228             return false;
    229         }
    230         return true;
    231     }
    232
    233     /***
    234      * Reset the state machine.
    235      *
    236      * @return Whether the reset was successful.
    237      */
    238     public boolean resetMachine() {
    239         try {
    240             engine.reset();
    241         } catch (ModelException me) {
    242             logError(me);
    243             return false;
    244         }
    245         return true;
    246     }
    247
    248     /***
    249      * Utility method for logging error.
    250      *
    251      * @param exception The exception leading to this error condition.
    252      */
    253     protected void logError(final Exception exception) {
    254         if (log.isErrorEnabled()) {
    255             log.error(exception.getMessage(), exception);
    256         }
    257     }
    258
    259     /***
    260      * A SCXMLListener that is only concerned about &quot;onentry&quot;
    261      * notifications.
    262      */
    263     protected class EntryListener implements SCXMLListener {
    264
    265         /***
    266          * {@inheritDoc}
    267          */
    268         public void onEntry(final TransitionTarget entered) {
    269             invoke(entered.getId());
    270         }
    271
    272         /***
    273          * No-op.
    274          *
    275          * @param from The &quot;source&quot; transition target.
    276          * @param to The &quot;destination&quot; transition target.
    277          * @param transition The transition being followed.
    278          */
    279         public void onTransition(final TransitionTarget from,
    280                 final TransitionTarget to, final Transition transition) {
    281             // nothing to do
    282         }
    283
    284         /***
    285          * No-op.
    286          *
    287          * @param exited The transition target being exited.
    288          */
    289         public void onExit(final TransitionTarget exited) {
    290             // nothing to do
    291         }
    292
    293     }
    294
    295 }
    296