1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.SCXMLParser;
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 * <p>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.</p>
45 *
46 * <p>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.</p>
53 *
54 * <p>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.</p>
57 *
58 * <p>This class swallows all exceptions only to log them. Developers of
59 * subclasses should think of themselves as "component developers"
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.</p>
63 *
64 */
65 public abstract class AbstractStateMachine {
66
67 /***
68 * The state machine that will drive the instances of this class.
69 */
70 private 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, object instantiation incurs parsing cost.
96 *
97 * @param scxmlDocument The URL pointing to the SCXML document that
98 * describes the "lifecycle" of the
99 * instances of this class.
100 */
101 public AbstractStateMachine(final URL scxmlDocument) {
102
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
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
325 }
326
327 /***
328 * No-op.
329 *
330 * @param exited The transition target being exited.
331 */
332 public void onExit(final TransitionTarget exited) {
333
334 }
335
336 }
337
338 }
339