001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.scxml.env;
018
019 import java.io.IOException;
020 import java.lang.reflect.InvocationTargetException;
021 import java.lang.reflect.Method;
022 import java.net.URL;
023
024 import org.apache.commons.logging.Log;
025 import org.apache.commons.logging.LogFactory;
026 import org.apache.commons.scxml.Context;
027 import org.apache.commons.scxml.Evaluator;
028 import org.apache.commons.scxml.SCXMLExecutor;
029 import org.apache.commons.scxml.SCXMLListener;
030 import org.apache.commons.scxml.TriggerEvent;
031 import org.apache.commons.scxml.env.jexl.JexlContext;
032 import org.apache.commons.scxml.env.jexl.JexlEvaluator;
033 import org.apache.commons.scxml.io.SCXMLParser;
034 import org.apache.commons.scxml.model.ModelException;
035 import org.apache.commons.scxml.model.SCXML;
036 import org.apache.commons.scxml.model.Transition;
037 import org.apache.commons.scxml.model.TransitionTarget;
038 import org.xml.sax.ErrorHandler;
039 import org.xml.sax.SAXException;
040
041 /**
042 * <p>This class demonstrates one approach for providing the base
043 * functionality needed by classes representing stateful entities,
044 * whose behaviors are defined via SCXML documents.</p>
045 *
046 * <p>SCXML documents (more generically, UML state chart diagrams) can be
047 * used to define stateful behavior of objects, and Commons SCXML enables
048 * developers to use this model directly into the corresponding code
049 * artifacts. The resulting artifacts tend to be much simpler, embody
050 * a useful separation of concerns and are easier to understand and
051 * maintain. As the size of the modeled entity grows, these benefits
052 * become more apparent.</p>
053 *
054 * <p>This approach functions by registering an SCXMLListener that gets
055 * notified onentry, and calls the namesake method for each state that
056 * has been entered.</p>
057 *
058 * <p>This class swallows all exceptions only to log them. Developers of
059 * subclasses should think of themselves as "component developers"
060 * catering to other end users, and therefore ensure that the subclasses
061 * are free of <code>ModelException</code>s and the like. Most methods
062 * are <code>protected</code> for ease of subclassing.</p>
063 *
064 */
065 public abstract class AbstractStateMachine {
066
067 /**
068 * The state machine that will drive the instances of this class.
069 */
070 private SCXML stateMachine;
071
072 /**
073 * The instance specific SCXML engine.
074 */
075 private SCXMLExecutor engine;
076
077 /**
078 * The log.
079 */
080 private Log log;
081
082 /**
083 * The method signature for the activities corresponding to each
084 * state in the SCXML document.
085 */
086 private static final Class[] SIGNATURE = new Class[0];
087
088 /**
089 * The method parameters for the activities corresponding to each
090 * state in the SCXML document.
091 */
092 private static final Object[] PARAMETERS = new Object[0];
093
094 /**
095 * Convenience constructor, object instantiation incurs parsing cost.
096 *
097 * @param scxmlDocument The URL pointing to the SCXML document that
098 * describes the "lifecycle" of the
099 * 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 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 // nothing to do
325 }
326
327 /**
328 * No-op.
329 *
330 * @param exited The transition target being exited.
331 */
332 public void onExit(final TransitionTarget exited) {
333 // nothing to do
334 }
335
336 }
337
338 }
339