    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *     http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.scxml.env;
    018
    019    import java.io.IOException;
    020    import java.lang.reflect.InvocationTargetException;
    021    import java.lang.reflect.Method;
    022    import java.net.URL;
    023
    024    import org.apache.commons.logging.Log;
    025    import org.apache.commons.logging.LogFactory;
    026    import org.apache.commons.scxml.Context;
    027    import org.apache.commons.scxml.Evaluator;
    028    import org.apache.commons.scxml.SCXMLExecutor;
    029    import org.apache.commons.scxml.SCXMLListener;
    030    import org.apache.commons.scxml.TriggerEvent;
    031    import org.apache.commons.scxml.env.jexl.JexlContext;
    032    import org.apache.commons.scxml.env.jexl.JexlEvaluator;
    033    import org.apache.commons.scxml.io.SCXMLParser;
    034    import org.apache.commons.scxml.model.ModelException;
    035    import org.apache.commons.scxml.model.SCXML;
    036    import org.apache.commons.scxml.model.Transition;
    037    import org.apache.commons.scxml.model.TransitionTarget;
    038    import org.xml.sax.ErrorHandler;
    039    import org.xml.sax.SAXException;
    040
    041    /**
    042     * <p>This class demonstrates one approach for providing the base
    043     * functionality needed by classes representing stateful entities,
    044     * whose behaviors are defined via SCXML documents.</p>
    045     *
    046     * <p>SCXML documents (more generically, UML state chart diagrams) can be
    047     * used to define stateful behavior of objects, and Commons SCXML enables
    048     * developers to use this model directly into the corresponding code
    049     * artifacts. The resulting artifacts tend to be much simpler, embody
    050     * a useful separation of concerns and are easier to understand and
    051     * maintain. As the size of the modeled entity grows, these benefits
    052     * become more apparent.</p>
    053     *
    054     * <p>This approach functions by registering an SCXMLListener that gets
    055     * notified onentry, and calls the namesake method for each state that
    056     * has been entered.</p>
    057     *
    058     * <p>This class swallows all exceptions only to log them. Developers of
    059     * subclasses should think of themselves as "component developers"
    060     * catering to other end users, and therefore ensure that the subclasses
    061     * are free of <code>ModelException</code>s and the like. Most methods
    062     * are <code>protected</code> for ease of subclassing.</p>
    063     *
    064     */
    065    public abstract class AbstractStateMachine {
    066
    067        /**
    068         * The state machine that will drive the instances of this class.
    069         */
    070        private SCXML stateMachine;
    071
    072        /**
    073         * The instance specific SCXML engine.
    074         */
    075        private SCXMLExecutor engine;
    076
    077        /**
    078         * The log.
    079         */
    080        private Log log;
    081
    082        /**
    083         * The method signature for the activities corresponding to each
    084         * state in the SCXML document.
    085         */
    086        private static final Class[] SIGNATURE = new Class[0];
    087
    088        /**
    089         * The method parameters for the activities corresponding to each
    090         * state in the SCXML document.
    091         */
    092        private static final Object[] PARAMETERS = new Object[0];
    093
    094        /**
    095         * Convenience constructor, object instantiation incurs parsing cost.
    096         *
    097         * @param scxmlDocument The URL pointing to the SCXML document that
    098         *                      describes the "lifecycle" of the
    099         *                      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 "lifecycle" 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 "lifecycle" 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 "lifecycle" 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 "final"
    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 "lifecycle" 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 "onentry"
    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 "source" transition target.
    319             * @param to The "destination" 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