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.SCXMLDigester;
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: * 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.
045: *
046: * 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.
053: *
054: * 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.
057: *
058: * 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.
063: *
064: */
065: public abstract class AbstractStateMachine {
066:
067: /**
068: * The state machine that will drive the instances of this class.
069: */
070: private static 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.
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.
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: // parse only once per subclass
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,
213: 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: // nothing to do
283: }
284:
285: /**
286: * No-op.
287: *
288: * @param exited The transition target being exited.
289: */
290: public void onExit(final TransitionTarget exited) {
291: // nothing to do
292: }
293:
294: }
295:
296: }
|