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.SCXMLDigester;
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 * 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.
45 *
46 * 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.
53 *
54 * 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.
57 *
58 * 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.
63 *
64 */
65 public abstract class AbstractStateMachine {
66
67 /***
68 * The state machine that will drive the instances of this class.
69 */
70 private static 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.
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.
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 if (stateMachine == null) {
122
123 ErrorHandler errHandler = new SimpleErrorHandler();
124 try {
125 stateMachine = SCXMLDigester.digest(scxmlDocument,
126 errHandler);
127 } catch (IOException ioe) {
128 logError(ioe);
129 } catch (SAXException sae) {
130 logError(sae);
131 } catch (ModelException me) {
132 logError(me);
133 }
134 }
135 engine = new SCXMLExecutor(evaluator, new SimpleDispatcher(),
136 new SimpleErrorReporter());
137 engine.setStateMachine(stateMachine);
138 engine.setSuperStep(true);
139 engine.setRootContext(rootCtx);
140 engine.addListener(stateMachine, new EntryListener());
141 try {
142 engine.go();
143 } catch (ModelException me) {
144 logError(me);
145 }
146 }
147
148 /***
149 * Fire an event on the SCXML engine.
150 *
151 * @param event The event name.
152 * @return Whether the state machine has reached a "final"
153 * configuration.
154 */
155 public boolean fireEvent(final String event) {
156 TriggerEvent[] evts = {new TriggerEvent(event,
157 TriggerEvent.SIGNAL_EVENT, null)};
158 try {
159 engine.triggerEvents(evts);
160 } catch (ModelException me) {
161 logError(me);
162 }
163 return engine.getCurrentStatus().isFinal();
164 }
165
166 /***
167 * Get the SCXML object representing this state machine.
168 *
169 * @return Returns the stateMachine.
170 */
171 public static SCXML getStateMachine() {
172 return stateMachine;
173 }
174
175 /***
176 * Get the SCXML engine driving the "lifecycle" of the
177 * instances of this class.
178 *
179 * @return Returns the engine.
180 */
181 public SCXMLExecutor getEngine() {
182 return engine;
183 }
184
185 /***
186 * Get the log for this class.
187 *
188 * @return Returns the log.
189 */
190 public Log getLog() {
191 return log;
192 }
193
194 /***
195 * Set the log for this class.
196 *
197 * @param log The log to set.
198 */
199 public void setLog(final Log log) {
200 this.log = log;
201 }
202
203 /***
204 * Invoke the no argument method with the following name.
205 *
206 * @param methodName The method to invoke.
207 * @return Whether the invoke was successful.
208 */
209 public boolean invoke(final String methodName) {
210 Class clas = this.getClass();
211 try {
212 Method method = clas.getDeclaredMethod(methodName, SIGNATURE);
213 method.invoke(this, PARAMETERS);
214 } catch (SecurityException se) {
215 logError(se);
216 return false;
217 } catch (NoSuchMethodException nsme) {
218 logError(nsme);
219 return false;
220 } catch (IllegalArgumentException iae) {
221 logError(iae);
222 return false;
223 } catch (IllegalAccessException iae) {
224 logError(iae);
225 return false;
226 } catch (InvocationTargetException ite) {
227 logError(ite);
228 return false;
229 }
230 return true;
231 }
232
233 /***
234 * Reset the state machine.
235 *
236 * @return Whether the reset was successful.
237 */
238 public boolean resetMachine() {
239 try {
240 engine.reset();
241 } catch (ModelException me) {
242 logError(me);
243 return false;
244 }
245 return true;
246 }
247
248 /***
249 * Utility method for logging error.
250 *
251 * @param exception The exception leading to this error condition.
252 */
253 protected void logError(final Exception exception) {
254 if (log.isErrorEnabled()) {
255 log.error(exception.getMessage(), exception);
256 }
257 }
258
259 /***
260 * A SCXMLListener that is only concerned about "onentry"
261 * notifications.
262 */
263 protected class EntryListener implements SCXMLListener {
264
265 /***
266 * {@inheritDoc}
267 */
268 public void onEntry(final TransitionTarget entered) {
269 invoke(entered.getId());
270 }
271
272 /***
273 * No-op.
274 *
275 * @param from The "source" transition target.
276 * @param to The "destination" transition target.
277 * @param transition The transition being followed.
278 */
279 public void onTransition(final TransitionTarget from,
280 final TransitionTarget to, final Transition transition) {
281
282 }
283
284 /***
285 * No-op.
286 *
287 * @param exited The transition target being exited.
288 */
289 public void onExit(final TransitionTarget exited) {
290
291 }
292
293 }
294
295 }
296