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.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 // default is JEXL
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 // default is JEXL
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 log = LogFactory.getLog(this.getClass());
166 initialize(stateMachine, rootCtx, evaluator);
167 }
168
169 /**
170 * Instantiate and initialize the underlying executor instance.
171 *
172 * @param stateMachine The state machine
173 * @param rootCtx The root context
174 * @param evaluator The expression evaluator
175 */
176 private void initialize(final SCXML stateMachine,
177 final Context rootCtx, final Evaluator evaluator) {
178 engine = new SCXMLExecutor(evaluator, new SimpleDispatcher(),
179 new SimpleErrorReporter());
180 engine.setStateMachine(stateMachine);
181 engine.setSuperStep(true);
182 engine.setRootContext(rootCtx);
183 engine.addListener(stateMachine, new EntryListener());
184 try {
185 engine.go();
186 } catch (ModelException me) {
187 logError(me);
188 }
189 }
190
191 /**
192 * Fire an event on the SCXML engine.
193 *
194 * @param event The event name.
195 * @return Whether the state machine has reached a "final"
196 * configuration.
197 */
198 public boolean fireEvent(final String event) {
199 TriggerEvent[] evts = {new TriggerEvent(event,
200 TriggerEvent.SIGNAL_EVENT, null)};
201 try {
202 engine.triggerEvents(evts);
203 } catch (ModelException me) {
204 logError(me);
205 }
206 return engine.getCurrentStatus().isFinal();
207 }
208
209 /**
210 * Get the SCXML object representing this state machine.
211 *
212 * @return Returns the stateMachine.
213 * @deprecated Returns null, use getEngine().getStateMachine() instead
214 */
215 public static SCXML getStateMachine() {
216 return null;
217 }
218
219 /**
220 * Get the SCXML engine driving the "lifecycle" of the
221 * instances of this class.
222 *
223 * @return Returns the engine.
224 */
225 public SCXMLExecutor getEngine() {
226 return engine;
227 }
228
229 /**
230 * Get the log for this class.
231 *
232 * @return Returns the log.
233 */
234 public Log getLog() {
235 return log;
236 }
237
238 /**
239 * Set the log for this class.
240 *
241 * @param log The log to set.
242 */
243 public void setLog(final Log log) {
244 this.log = log;
245 }
246
247 /**
248 * Invoke the no argument method with the following name.
249 *
250 * @param methodName The method to invoke.
251 * @return Whether the invoke was successful.
252 */
253 public boolean invoke(final String methodName) {
254 Class clas = this.getClass();
255 try {
256 Method method = clas.getDeclaredMethod(methodName, SIGNATURE);
257 method.invoke(this, PARAMETERS);
258 } catch (SecurityException se) {
259 logError(se);
260 return false;
261 } catch (NoSuchMethodException nsme) {
262 logError(nsme);
263 return false;
264 } catch (IllegalArgumentException iae) {
265 logError(iae);
266 return false;
267 } catch (IllegalAccessException iae) {
268 logError(iae);
269 return false;
270 } catch (InvocationTargetException ite) {
271 logError(ite);
272 return false;
273 }
274 return true;
275 }
276
277 /**
278 * Reset the state machine.
279 *
280 * @return Whether the reset was successful.
281 */
282 public boolean resetMachine() {
283 try {
284 engine.reset();
285 } catch (ModelException me) {
286 logError(me);
287 return false;
288 }
289 return true;
290 }
291
292 /**
293 * Utility method for logging error.
294 *
295 * @param exception The exception leading to this error condition.
296 */
297 protected void logError(final Exception exception) {
298 if (log.isErrorEnabled()) {
299 log.error(exception.getMessage(), exception);
300 }
301 }
302
303 /**
304 * A SCXMLListener that is only concerned about "onentry"
305 * notifications.
306 */
307 protected class EntryListener implements SCXMLListener {
308
309 /**
310 * {@inheritDoc}
311 */
312 public void onEntry(final TransitionTarget entered) {
313 invoke(entered.getId());
314 }
315
316 /**
317 * No-op.
318 *
319 * @param from The "source" transition target.
320 * @param to The "destination" transition target.
321 * @param transition The transition being followed.
322 */
323 public void onTransition(final TransitionTarget from,
324 final TransitionTarget to, final Transition transition) {
325 // nothing to do
326 }
327
328 /**
329 * No-op.
330 *
331 * @param exited The transition target being exited.
332 */
333 public void onExit(final TransitionTarget exited) {
334 // nothing to do
335 }
336
337 }
338
339 }
340