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.scxml2.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 javax.xml.stream.XMLStreamException;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.commons.scxml2.Context;
29  import org.apache.commons.scxml2.Evaluator;
30  import org.apache.commons.scxml2.SCXMLExecutor;
31  import org.apache.commons.scxml2.SCXMLListener;
32  import org.apache.commons.scxml2.TriggerEvent;
33  import org.apache.commons.scxml2.env.jexl.JexlContext;
34  import org.apache.commons.scxml2.env.jexl.JexlEvaluator;
35  import org.apache.commons.scxml2.io.SCXMLReader;
36  import org.apache.commons.scxml2.model.EnterableState;
37  import org.apache.commons.scxml2.model.ModelException;
38  import org.apache.commons.scxml2.model.SCXML;
39  import org.apache.commons.scxml2.model.Transition;
40  import org.apache.commons.scxml2.model.TransitionTarget;
41  
42  /**
43   * <p>This class demonstrates one approach for providing the base
44   * functionality needed by classes representing stateful entities,
45   * whose behaviors are defined via SCXML documents.</p>
46   *
47   * <p>SCXML documents (more generically, UML state chart diagrams) can be
48   * used to define stateful behavior of objects, and Commons SCXML enables
49   * developers to use this model directly into the corresponding code
50   * artifacts. The resulting artifacts tend to be much simpler, embody
51   * a useful separation of concerns and are easier to understand and
52   * maintain. As the size of the modeled entity grows, these benefits
53   * become more apparent.</p>
54   *
55   * <p>This approach functions by registering an SCXMLListener that gets
56   * notified onentry, and calls the namesake method for each state that
57   * has been entered.</p>
58   *
59   * <p>This class swallows all exceptions only to log them. Developers of
60   * subclasses should think of themselves as &quot;component developers&quot;
61   * catering to other end users, and therefore ensure that the subclasses
62   * are free of <code>ModelException</code>s and the like. Most methods
63   * are <code>protected</code> for ease of subclassing.</p>
64   *
65   */
66  public abstract class AbstractStateMachine {
67  
68      /**
69       * The state machine that will drive the instances of this class.
70       */
71      private SCXML stateMachine;
72  
73      /**
74       * The instance specific SCXML engine.
75       */
76      private SCXMLExecutor engine;
77  
78      /**
79       * The log.
80       */
81      private Log log;
82  
83      /**
84       * The method signature for the activities corresponding to each
85       * state in the SCXML document.
86       */
87      private static final Class<?>[] SIGNATURE = new Class[0];
88  
89      /**
90       * The method parameters for the activities corresponding to each
91       * state in the SCXML document.
92       */
93      private static final Object[] PARAMETERS = new Object[0];
94  
95      /**
96       * Convenience constructor, object instantiation incurs parsing cost.
97       *
98       * @param scxmlDocument The URL pointing to the SCXML document that
99       *                      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