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 &quot;component developers&quot;
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 &quot;lifecycle&quot; 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 &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