View Javadoc

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 &quot;component developers&quot;
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 &quot;lifecycle&quot; 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 &quot;lifecycle&quot; 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 &quot;lifecycle&quot; 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 &quot;lifecycle&quot; 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 &quot;final&quot;
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 &quot;lifecycle&quot; 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 &quot;onentry&quot;
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 &quot;source&quot; transition target.
319          * @param to The &quot;destination&quot; 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