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 "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.</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 "lifecycle" 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 "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) 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 "lifecycle" 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 "lifecycle" 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 "final"
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 "lifecycle" 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 "onentry"
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 "source" transition target.
308 * @param to The "destination" 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