1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.scxml.env;
19
20 import java.io.IOException;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.net.URL;
24
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27 import org.apache.commons.scxml.Context;
28 import org.apache.commons.scxml.Evaluator;
29 import org.apache.commons.scxml.SCXMLExecutor;
30 import org.apache.commons.scxml.SCXMLListener;
31 import org.apache.commons.scxml.TriggerEvent;
32 import org.apache.commons.scxml.env.jexl.JexlContext;
33 import org.apache.commons.scxml.env.jexl.JexlEvaluator;
34 import org.apache.commons.scxml.io.SCXMLDigester;
35 import org.apache.commons.scxml.model.ModelException;
36 import org.apache.commons.scxml.model.SCXML;
37 import org.apache.commons.scxml.model.Transition;
38 import org.apache.commons.scxml.model.TransitionTarget;
39 import org.xml.sax.ErrorHandler;
40 import org.xml.sax.SAXException;
41
42 /***
43 * 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.
46 *
47 * 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.
54 *
55 * 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.
58 *
59 * This class swallows all exceptions only to log them. Developers of
60 * subclasses should think of themselves as "component developers"
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.
64 *
65 */
66 public abstract class AbstractStateMachine {
67
68 /***
69 * The state machine that will drive the instances of this class.
70 */
71 private static 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.
97 *
98 * @param scxmlDocument The URL pointing to the SCXML document that
99 * describes the "lifecycle" of the
100 * instances of this class.
101 */
102 public AbstractStateMachine(final URL scxmlDocument) {
103
104 this(scxmlDocument, new JexlContext(), new JexlEvaluator());
105 }
106
107 /***
108 * Primary constructor.
109 *
110 * @param scxmlDocument The URL pointing to the SCXML document that
111 * describes the "lifecycle" 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) {
121 log = LogFactory.getLog(this.getClass());
122 if (stateMachine == null) {
123
124 ErrorHandler errHandler = new SimpleErrorHandler();
125 try {
126 stateMachine = SCXMLDigester.digest(scxmlDocument,
127 errHandler);
128 } catch (IOException ioe) {
129 logError(ioe);
130 } catch (SAXException sae) {
131 logError(sae);
132 } catch (ModelException me) {
133 logError(me);
134 }
135 }
136 engine = new SCXMLExecutor(evaluator, new SimpleDispatcher(),
137 new SimpleErrorReporter());
138 engine.setStateMachine(stateMachine);
139 engine.setSuperStep(true);
140 engine.setRootContext(rootCtx);
141 engine.addListener(stateMachine, new EntryListener());
142 try {
143 engine.go();
144 } catch (ModelException me) {
145 logError(me);
146 }
147 }
148
149 /***
150 * Fire an event on the SCXML engine.
151 *
152 * @param event The event name.
153 * @return Whether the state machine has reached a "final"
154 * configuration.
155 */
156 public boolean fireEvent(final String event) {
157 TriggerEvent[] evts = {new TriggerEvent(event,
158 TriggerEvent.SIGNAL_EVENT, null)};
159 try {
160 engine.triggerEvents(evts);
161 } catch (ModelException me) {
162 logError(me);
163 }
164 return engine.getCurrentStatus().isFinal();
165 }
166
167 /***
168 * Get the SCXML object representing this state machine.
169 *
170 * @return Returns the stateMachine.
171 */
172 public static SCXML getStateMachine() {
173 return stateMachine;
174 }
175
176 /***
177 * Get the SCXML engine driving the "lifecycle" of the
178 * instances of this class.
179 *
180 * @return Returns the engine.
181 */
182 public SCXMLExecutor getEngine() {
183 return engine;
184 }
185
186 /***
187 * Get the log for this class.
188 *
189 * @return Returns the log.
190 */
191 public Log getLog() {
192 return log;
193 }
194
195 /***
196 * Set the log for this class.
197 *
198 * @param log The log to set.
199 */
200 public void setLog(final Log log) {
201 this.log = log;
202 }
203
204 /***
205 * Invoke the no argument method with the following name.
206 *
207 * @param methodName The method to invoke.
208 * @return Whether the invoke was successful.
209 */
210 public boolean invoke(final String methodName) {
211 Class clas = this.getClass();
212 try {
213 Method method = clas.getDeclaredMethod(methodName, SIGNATURE);
214 method.invoke(this, PARAMETERS);
215 } catch (SecurityException se) {
216 logError(se);
217 return false;
218 } catch (NoSuchMethodException nsme) {
219 logError(nsme);
220 return false;
221 } catch (IllegalArgumentException iae) {
222 logError(iae);
223 return false;
224 } catch (IllegalAccessException iae) {
225 logError(iae);
226 return false;
227 } catch (InvocationTargetException ite) {
228 logError(ite);
229 return false;
230 }
231 return true;
232 }
233
234 /***
235 * Reset the state machine.
236 *
237 * @return Whether the reset was successful.
238 */
239 public boolean resetMachine() {
240 try {
241 engine.reset();
242 } catch (ModelException me) {
243 logError(me);
244 return false;
245 }
246 return true;
247 }
248
249 /***
250 * Utility method for logging error.
251 *
252 * @param exception The exception leading to this error condition.
253 */
254 protected void logError(final Exception exception) {
255 if (log.isErrorEnabled()) {
256 log.error(exception.getMessage(), exception);
257 }
258 }
259
260 /***
261 * A SCXMLListener that is only concerned about "onentry"
262 * notifications.
263 */
264 protected class EntryListener implements SCXMLListener {
265
266 /***
267 * {@inheritDoc}
268 */
269 public void onEntry(final TransitionTarget entered) {
270 invoke(entered.getId());
271 }
272
273 /***
274 * No-op.
275 *
276 * @param from The "source" transition target.
277 * @param to The "destination" transition target.
278 * @param transition The transition being followed.
279 */
280 public void onTransition(final TransitionTarget from,
281 final TransitionTarget to, final Transition transition) {
282
283 }
284
285 /***
286 * No-op.
287 *
288 * @param exited The transition target being exited.
289 */
290 public void onExit(final TransitionTarget exited) {
291
292 }
293
294 }
295
296 }
297