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 */
017package org.apache.commons.scxml2.env;
018
019import java.io.IOException;
020import java.lang.reflect.InvocationTargetException;
021import java.lang.reflect.Method;
022import java.net.URL;
023
024import javax.xml.stream.XMLStreamException;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028import org.apache.commons.scxml2.Context;
029import org.apache.commons.scxml2.Evaluator;
030import org.apache.commons.scxml2.SCXMLExecutor;
031import org.apache.commons.scxml2.SCXMLListener;
032import org.apache.commons.scxml2.TriggerEvent;
033import org.apache.commons.scxml2.env.jexl.JexlContext;
034import org.apache.commons.scxml2.env.jexl.JexlEvaluator;
035import org.apache.commons.scxml2.io.SCXMLReader;
036import org.apache.commons.scxml2.model.EnterableState;
037import org.apache.commons.scxml2.model.ModelException;
038import org.apache.commons.scxml2.model.SCXML;
039import org.apache.commons.scxml2.model.Transition;
040import org.apache.commons.scxml2.model.TransitionTarget;
041
042/**
043 * <p>This class demonstrates one approach for providing the base
044 * functionality needed by classes representing stateful entities,
045 * whose behaviors are defined via SCXML documents.</p>
046 *
047 * <p>SCXML documents (more generically, UML state chart diagrams) can be
048 * used to define stateful behavior of objects, and Commons SCXML enables
049 * developers to use this model directly into the corresponding code
050 * artifacts. The resulting artifacts tend to be much simpler, embody
051 * a useful separation of concerns and are easier to understand and
052 * maintain. As the size of the modeled entity grows, these benefits
053 * become more apparent.</p>
054 *
055 * <p>This approach functions by registering an SCXMLListener that gets
056 * notified onentry, and calls the namesake method for each state that
057 * has been entered.</p>
058 *
059 * <p>This class swallows all exceptions only to log them. Developers of
060 * subclasses should think of themselves as &quot;component developers&quot;
061 * catering to other end users, and therefore ensure that the subclasses
062 * are free of <code>ModelException</code>s and the like. Most methods
063 * are <code>protected</code> for ease of subclassing.</p>
064 *
065 */
066public abstract class AbstractStateMachine {
067
068    /**
069     * The state machine that will drive the instances of this class.
070     */
071    private SCXML stateMachine;
072
073    /**
074     * The instance specific SCXML engine.
075     */
076    private SCXMLExecutor engine;
077
078    /**
079     * The log.
080     */
081    private Log log;
082
083    /**
084     * The method signature for the activities corresponding to each
085     * state in the SCXML document.
086     */
087    private static final Class<?>[] SIGNATURE = new Class[0];
088
089    /**
090     * The method parameters for the activities corresponding to each
091     * state in the SCXML document.
092     */
093    private static final Object[] PARAMETERS = new Object[0];
094
095    /**
096     * Convenience constructor, object instantiation incurs parsing cost.
097     *
098     * @param scxmlDocument The URL pointing to the SCXML document that
099     *                      describes the &quot;lifecycle&quot; of the
100     *                      instances of this class.
101     */
102    public AbstractStateMachine(final URL scxmlDocument) throws ModelException {
103        // default is JEXL
104        this(scxmlDocument, new JexlContext(), new JexlEvaluator());
105    }
106
107    /**
108     * Primary constructor, object instantiation incurs parsing cost.
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) throws ModelException {
121        log = LogFactory.getLog(this.getClass());
122        try {
123            stateMachine = SCXMLReader.read(scxmlDocument);
124        } catch (IOException ioe) {
125            logError(ioe);
126        } catch (XMLStreamException xse) {
127            logError(xse);
128        } catch (ModelException me) {
129            logError(me);
130        }
131        initialize(stateMachine, rootCtx, evaluator);
132    }
133
134    /**
135     * Convenience constructor.
136     *
137     * @param stateMachine The parsed SCXML instance that
138     *                     describes the &quot;lifecycle&quot; of the
139     *                     instances of this class.
140     *
141     * @since 0.7
142     */
143    public AbstractStateMachine(final SCXML stateMachine) throws ModelException {
144        // default is JEXL
145        this(stateMachine, new JexlContext(), new JexlEvaluator());
146    }
147
148    /**
149     * Primary constructor.
150     *
151     * @param stateMachine The parsed SCXML instance that
152     *                     describes the &quot;lifecycle&quot; of the
153     *                     instances of this class.
154     * @param rootCtx The root context for this instance.
155     * @param evaluator The expression evaluator for this instance.
156     *
157     * @see Context
158     * @see Evaluator
159     *
160     * @since 0.7
161     */
162    public AbstractStateMachine(final SCXML stateMachine,
163            final Context rootCtx, final Evaluator evaluator) throws ModelException {
164        log = LogFactory.getLog(this.getClass());
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) throws ModelException {
177        engine = new SCXMLExecutor(evaluator, new SimpleDispatcher(),
178            new SimpleErrorReporter());
179        engine.setStateMachine(stateMachine);
180        engine.setRootContext(rootCtx);
181        engine.addListener(stateMachine, new EntryListener());
182        try {
183            engine.go();
184        } catch (ModelException me) {
185            logError(me);
186        }
187    }
188
189    /**
190     * Fire an event on the SCXML engine.
191     *
192     * @param event The event name.
193     * @return Whether the state machine has reached a &quot;final&quot;
194     *         configuration.
195     */
196    public boolean fireEvent(final String event) {
197        TriggerEvent[] evts = {new TriggerEvent(event,
198                TriggerEvent.SIGNAL_EVENT)};
199        try {
200            engine.triggerEvents(evts);
201        } catch (ModelException me) {
202            logError(me);
203        }
204        return engine.getStatus().isFinal();
205    }
206
207    /**
208     * Get the SCXML engine driving the &quot;lifecycle&quot; of the
209     * instances of this class.
210     *
211     * @return Returns the engine.
212     */
213    public SCXMLExecutor getEngine() {
214        return engine;
215    }
216
217    /**
218     * Get the log for this class.
219     *
220     * @return Returns the log.
221     */
222    public Log getLog() {
223        return log;
224    }
225
226    /**
227     * Set the log for this class.
228     *
229     * @param log The log to set.
230     */
231    public void setLog(final Log log) {
232        this.log = log;
233    }
234
235    /**
236     * Invoke the no argument method with the following name.
237     *
238     * @param methodName The method to invoke.
239     * @return Whether the invoke was successful.
240     */
241    public boolean invoke(final String methodName) {
242        Class<?> clas = this.getClass();
243        try {
244            Method method = clas.getDeclaredMethod(methodName, SIGNATURE);
245            method.invoke(this, PARAMETERS);
246        } catch (SecurityException se) {
247            logError(se);
248            return false;
249        } catch (NoSuchMethodException nsme) {
250            logError(nsme);
251            return false;
252        } catch (IllegalArgumentException iae) {
253            logError(iae);
254            return false;
255        } catch (IllegalAccessException iae) {
256            logError(iae);
257            return false;
258        } catch (InvocationTargetException ite) {
259            logError(ite);
260            return false;
261        }
262        return true;
263    }
264
265    /**
266     * Reset the state machine.
267     *
268     * @return Whether the reset was successful.
269     */
270    public boolean resetMachine() {
271        try {
272            engine.reset();
273        } catch (ModelException me) {
274            logError(me);
275            return false;
276        }
277        return true;
278    }
279
280    /**
281     * Utility method for logging error.
282     *
283     * @param exception The exception leading to this error condition.
284     */
285    protected void logError(final Exception exception) {
286        if (log.isErrorEnabled()) {
287            log.error(exception.getMessage(), exception);
288        }
289    }
290
291    /**
292     * A SCXMLListener that is only concerned about &quot;onentry&quot;
293     * notifications.
294     */
295    protected class EntryListener implements SCXMLListener {
296
297        /**
298         * {@inheritDoc}
299         */
300        public void onEntry(final EnterableState entered) {
301            invoke(entered.getId());
302        }
303
304        /**
305         * No-op.
306         *
307         * @param from The &quot;source&quot; transition target.
308         * @param to The &quot;destination&quot; transition target.
309         * @param transition The transition being followed.
310         * @param event The event triggering the transition
311         */
312        public void onTransition(final TransitionTarget from,
313                final TransitionTarget to, final Transition transition, final String event) {
314            // nothing to do
315        }
316
317        /**
318         * No-op.
319         *
320         * @param exited The state being exited.
321         */
322        public void onExit(final EnterableState exited) {
323            // nothing to do
324        }
325
326    }
327
328}
329