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;
18  
19  import java.io.Serializable;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.commons.scxml.model.Datamodel;
30  import org.apache.commons.scxml.model.History;
31  import org.apache.commons.scxml.model.ModelException;
32  import org.apache.commons.scxml.model.SCXML;
33  import org.apache.commons.scxml.model.State;
34  import org.apache.commons.scxml.model.Transition;
35  import org.apache.commons.scxml.model.TransitionTarget;
36  import org.apache.commons.scxml.semantics.SCXMLSemanticsImpl;
37  
38  /***
39   * <p>The SCXML &quot;engine&quot; that executes SCXML documents. The
40   * particular semantics used by this engine for executing the SCXML are
41   * encapsulated in the SCXMLSemantics implementation that it uses.</p>
42   *
43   * <p>The default implementation is
44   * <code>org.apache.commons.scxml.semantics.SCXMLSemanticsImpl</code></p>
45   *
46   * @see SCXMLSemantics
47   */
48  public class SCXMLExecutor implements Serializable {
49  
50      /***
51       * Serial version UID.
52       */
53      private static final long serialVersionUID = 1L;
54  
55      /***
56       * The Logger for the SCXMLExecutor.
57       */
58      private Log log = LogFactory.getLog(SCXMLExecutor.class);
59  
60      /***
61       * The stateMachine being executed.
62       */
63      private SCXML stateMachine;
64  
65      /***
66       * The current status of the stateMachine.
67       */
68      private Status currentStatus;
69  
70      /***
71       * The event dispatcher to interface with external documents etc.
72       */
73      private EventDispatcher eventdispatcher;
74  
75      /***
76       * The environment specific error reporter.
77       */
78      private ErrorReporter errorReporter = null;
79  
80      /***
81       * Run-to-completion.
82       */
83      private boolean superStep = true;
84  
85      /***
86       *  Interpretation semantics.
87       */
88      private SCXMLSemantics semantics;
89  
90      /***
91       * The SCInstance.
92       */
93      private SCInstance scInstance;
94  
95      /***
96       * The worker method.
97       * Re-evaluates current status whenever any events are triggered.
98       *
99       * @param evts
100      *            an array of external events which triggered during the last
101      *            time quantum
102      * @throws ModelException in case there is a fatal SCXML object
103      *            model problem.
104      */
105     public synchronized void triggerEvents(final TriggerEvent[] evts)
106             throws ModelException {
107         // Set event data, saving old values
108         Object[] oldData = setEventData(evts);
109 
110         // Forward events (external only) to any existing invokes,
111         // and finalize processing
112         semantics.processInvokes(evts, errorReporter, scInstance);
113 
114         List evs = new ArrayList(Arrays.asList(evts));
115         Step step = null;
116 
117         do {
118             // CreateStep
119             step = new Step(evs, currentStatus);
120             // EnumerateReachableTransitions
121             semantics.enumerateReachableTransitions(stateMachine, step,
122                 errorReporter);
123             // FilterTransitionSet
124             semantics.filterTransitionsSet(step, eventdispatcher,
125                 errorReporter, scInstance);
126             // FollowTransitions
127             semantics.followTransitions(step, errorReporter, scInstance);
128             // UpdateHistoryStates
129             semantics.updateHistoryStates(step, errorReporter, scInstance);
130             // ExecuteActions
131             semantics.executeActions(step, stateMachine, eventdispatcher,
132                 errorReporter, scInstance);
133             // AssignCurrentStatus
134             updateStatus(step);
135             // ***Cleanup external events if superStep
136             if (superStep) {
137                 evs.clear();
138             }
139         } while (superStep && currentStatus.getEvents().size() > 0);
140 
141         // InitiateInvokes only after state machine has stabilized
142         semantics.initiateInvokes(step, errorReporter, scInstance);
143 
144         // Restore event data
145         restoreEventData(oldData);
146         logState();
147     }
148 
149     /***
150      * Convenience method when only one event needs to be triggered.
151      *
152      * @param evt
153      *            the external events which triggered during the last
154      *            time quantum
155      * @throws ModelException in case there is a fatal SCXML object
156      *            model problem.
157      */
158     public void triggerEvent(final TriggerEvent evt)
159             throws ModelException {
160         triggerEvents(new TriggerEvent[] {evt});
161     }
162 
163     /***
164      * Constructor.
165      *
166      * @param expEvaluator The expression evaluator
167      * @param evtDisp The event dispatcher
168      * @param errRep The error reporter
169      */
170     public SCXMLExecutor(final Evaluator expEvaluator,
171             final EventDispatcher evtDisp, final ErrorReporter errRep) {
172         this(expEvaluator, evtDisp, errRep, null);
173     }
174 
175     /***
176      * Convenience constructor.
177      */
178     public SCXMLExecutor() {
179         this(null, null, null, null);
180     }
181 
182     /***
183      * Constructor.
184      *
185      * @param expEvaluator The expression evaluator
186      * @param evtDisp The event dispatcher
187      * @param errRep The error reporter
188      * @param semantics The SCXML semantics
189      */
190     public SCXMLExecutor(final Evaluator expEvaluator,
191             final EventDispatcher evtDisp, final ErrorReporter errRep,
192             final SCXMLSemantics semantics) {
193         this.eventdispatcher = evtDisp;
194         this.errorReporter = errRep;
195         this.currentStatus = new Status();
196         this.stateMachine = null;
197         if (semantics == null) {
198             // Use default semantics, if none provided
199             this.semantics = new SCXMLSemanticsImpl();
200         } else {
201             this.semantics = semantics;
202         }
203         this.scInstance = new SCInstance(this);
204         this.scInstance.setEvaluator(expEvaluator);
205     }
206 
207     /***
208      * Clear all state and begin from &quot;initialstate&quot; indicated
209      * on root SCXML element.
210      *
211      * @throws ModelException in case there is a fatal SCXML object
212      *         model problem.
213      */
214     public synchronized void reset() throws ModelException {
215         // Reset all variable contexts
216         Context rootCtx = scInstance.getRootContext();
217         // Clone root datamodel
218         if (stateMachine == null) {
219             log.error(ERR_NO_STATE_MACHINE);
220             throw new ModelException(ERR_NO_STATE_MACHINE);
221         } else {
222             Datamodel rootdm = stateMachine.getDatamodel();
223             SCXMLHelper.cloneDatamodel(rootdm, rootCtx,
224                 scInstance.getEvaluator(), log);
225         }
226         // all states and parallels, only states have variable contexts
227         for (Iterator i = stateMachine.getTargets().values().iterator();
228                 i.hasNext();) {
229             TransitionTarget tt = (TransitionTarget) i.next();
230             if (tt instanceof State) {
231                 Context context = scInstance.lookupContext(tt);
232                 if (context != null) {
233                     context.reset();
234                     Datamodel dm = tt.getDatamodel();
235                     if (dm != null) {
236                         SCXMLHelper.cloneDatamodel(dm, context,
237                             scInstance.getEvaluator(), log);
238                     }
239                 }
240             } else if (tt instanceof History) {
241                 scInstance.reset((History) tt);
242             }
243         }
244         // CreateEmptyStatus
245         currentStatus = new Status();
246         Step step = new Step(null, currentStatus);
247         // DetermineInitialStates
248         semantics.determineInitialStates(stateMachine,
249                 step.getAfterStatus().getStates(),
250                 step.getEntryList(), errorReporter, scInstance);
251         // ExecuteActions
252         semantics.executeActions(step, stateMachine, eventdispatcher,
253                 errorReporter, scInstance);
254         // AssignCurrentStatus
255         updateStatus(step);
256         // Execute Immediate Transitions
257         if (superStep && currentStatus.getEvents().size() > 0) {
258             this.triggerEvents(new TriggerEvent[0]);
259         } else {
260             // InitiateInvokes only after state machine has stabilized
261             semantics.initiateInvokes(step, errorReporter, scInstance);
262             logState();
263         }
264     }
265 
266     /***
267      * Get the current status.
268      *
269      * @return The current Status
270      */
271     public synchronized Status getCurrentStatus() {
272         return currentStatus;
273     }
274 
275     /***
276      * Set the expression evaluator.
277      *
278      * @param evaluator The evaluator to set.
279      */
280     public void setEvaluator(final Evaluator evaluator) {
281         this.scInstance.setEvaluator(evaluator);
282     }
283 
284     /***
285      * Get the expression evaluator in use.
286      *
287      * @return Evaluator The evaluator in use.
288      */
289     public Evaluator getEvaluator() {
290         return scInstance.getEvaluator();
291     }
292 
293     /***
294      * Set the root context for this execution.
295      *
296      * @param rootContext The Context that ties to the host environment.
297      */
298     public void setRootContext(final Context rootContext) {
299         this.scInstance.setRootContext(rootContext);
300     }
301 
302     /***
303      * Get the root context for this execution.
304      *
305      * @return Context The root context.
306      */
307     public Context getRootContext() {
308         return scInstance.getRootContext();
309     }
310 
311     /***
312      * Get the state machine that is being executed.
313      *
314      * @return Returns the stateMachine.
315      */
316     public SCXML getStateMachine() {
317         return stateMachine;
318     }
319 
320     /***
321      * Set the state machine to be executed.
322      *
323      * @param stateMachine The stateMachine to set.
324      */
325     public void setStateMachine(final SCXML stateMachine) {
326         // NormalizeStateMachine
327         SCXML sm = semantics.normalizeStateMachine(stateMachine,
328                 errorReporter);
329         // StoreStateMachine
330         this.stateMachine = sm;
331     }
332 
333     /***
334      * Initiate state machine execution.
335      *
336      * @throws ModelException in case there is a fatal SCXML object
337      *  model problem.
338      */
339     public void go() throws ModelException {
340         // same as reset
341         this.reset();
342     }
343 
344     /***
345      * Get the environment specific error reporter.
346      *
347      * @return Returns the errorReporter.
348      */
349     public ErrorReporter getErrorReporter() {
350         return errorReporter;
351     }
352 
353     /***
354      * Set the environment specific error reporter.
355      *
356      * @param errorReporter The errorReporter to set.
357      */
358     public void setErrorReporter(final ErrorReporter errorReporter) {
359         this.errorReporter = errorReporter;
360     }
361 
362     /***
363      * Get the event dispatcher.
364      *
365      * @return Returns the eventdispatcher.
366      */
367     public EventDispatcher getEventdispatcher() {
368         return eventdispatcher;
369     }
370 
371     /***
372      * Set the event dispatcher.
373      *
374      * @param eventdispatcher The eventdispatcher to set.
375      */
376     public void setEventdispatcher(final EventDispatcher eventdispatcher) {
377         this.eventdispatcher = eventdispatcher;
378     }
379 
380     /***
381      * Use &quot;super-step&quot;, default is <code>true</code>
382      * (that is, run-to-completion is default).
383      *
384      * @return Returns the superStep property.
385      * @see #setSuperStep(boolean)
386      */
387     public boolean isSuperStep() {
388         return superStep;
389     }
390 
391     /***
392      * Set the super step.
393      *
394      * @param superStep
395      * if true, the internal derived events are also processed
396      *    (run-to-completion);
397      * if false, the internal derived events are stored in the
398      * CurrentStatus property and processed within the next
399      * triggerEvents() invocation, also the immediate (empty event) transitions
400      * are deferred until the next step
401       */
402     public void setSuperStep(final boolean superStep) {
403         this.superStep = superStep;
404     }
405 
406     /***
407      * Add a listener to the document root.
408      *
409      * @param scxml The document root to attach listener to.
410      * @param listener The SCXMLListener.
411      */
412     public void addListener(final SCXML scxml, final SCXMLListener listener) {
413         Object observable = scxml;
414         scInstance.getNotificationRegistry().addListener(observable, listener);
415     }
416 
417     /***
418      * Remove this listener from the document root.
419      *
420      * @param scxml The document root.
421      * @param listener The SCXMLListener to be removed.
422      */
423     public void removeListener(final SCXML scxml,
424             final SCXMLListener listener) {
425         Object observable = scxml;
426         scInstance.getNotificationRegistry().removeListener(observable,
427             listener);
428     }
429 
430     /***
431      * Add a listener to this transition target.
432      *
433      * @param transitionTarget The <code>TransitionTarget</code> to
434      *                         attach listener to.
435      * @param listener The SCXMLListener.
436      */
437     public void addListener(final TransitionTarget transitionTarget,
438             final SCXMLListener listener) {
439         Object observable = transitionTarget;
440         scInstance.getNotificationRegistry().addListener(observable, listener);
441     }
442 
443     /***
444      * Remove this listener for this transition target.
445      *
446      * @param transitionTarget The <code>TransitionTarget</code>.
447      * @param listener The SCXMLListener to be removed.
448      */
449     public void removeListener(final TransitionTarget transitionTarget,
450             final SCXMLListener listener) {
451         Object observable = transitionTarget;
452         scInstance.getNotificationRegistry().removeListener(observable,
453             listener);
454     }
455 
456     /***
457      * Add a listener to this transition.
458      *
459      * @param transition The <code>Transition</code> to attach listener to.
460      * @param listener The SCXMLListener.
461      */
462     public void addListener(final Transition transition,
463             final SCXMLListener listener) {
464         Object observable = transition;
465         scInstance.getNotificationRegistry().addListener(observable, listener);
466     }
467 
468     /***
469      * Remove this listener for this transition.
470      *
471      * @param transition The <code>Transition</code>.
472      * @param listener The SCXMLListener to be removed.
473      */
474     public void removeListener(final Transition transition,
475             final SCXMLListener listener) {
476         Object observable = transition;
477         scInstance.getNotificationRegistry().removeListener(observable,
478             listener);
479     }
480 
481     /***
482      * Register an <code>Invoker</code> for this target type.
483      *
484      * @param targettype The target type (specified by "targettype"
485      *                   attribute of &lt;invoke&gt; tag).
486      * @param invokerClass The <code>Invoker</code> <code>Class</code>.
487      */
488     public void registerInvokerClass(final String targettype,
489             final Class invokerClass) {
490         scInstance.registerInvokerClass(targettype, invokerClass);
491     }
492 
493     /***
494      * Remove the <code>Invoker</code> registered for this target
495      * type (if there is one registered).
496      *
497      * @param targettype The target type (specified by "targettype"
498      *                   attribute of &lt;invoke&gt; tag).
499      */
500     public void unregisterInvokerClass(final String targettype) {
501         scInstance.unregisterInvokerClass(targettype);
502     }
503 
504     /***
505      * Get the state chart instance for this executor.
506      *
507      * @return The SCInstance for this executor.
508      */
509     SCInstance getSCInstance() {
510         return scInstance;
511     }
512 
513     /***
514      * Log the current set of active states.
515      */
516     private void logState() {
517         if (log.isInfoEnabled()) {
518             Iterator si = currentStatus.getStates().iterator();
519             StringBuffer sb = new StringBuffer("Current States: [");
520             while (si.hasNext()) {
521                 State s = (State) si.next();
522                 sb.append(s.getId());
523                 if (si.hasNext()) {
524                     sb.append(", ");
525                 }
526             }
527             sb.append(']');
528             log.info(sb.toString());
529         }
530     }
531 
532     /***
533      * @param step The most recent Step
534      */
535     private void updateStatus(final Step step) {
536         currentStatus = step.getAfterStatus();
537         scInstance.getRootContext().setLocal("_ALL_STATES",
538             SCXMLHelper.getAncestorClosure(currentStatus.getStates(), null));
539     }
540 
541     /***
542      * @param evts The events being triggered.
543      * @return Object[] Previous values.
544      */
545     private Object[] setEventData(final TriggerEvent[] evts) {
546         Context rootCtx = scInstance.getRootContext();
547         Object[] oldData = {rootCtx.get(EVENT_DATA),
548             rootCtx.get(EVENT_DATA_MAP)};
549         Object eventData = null;
550         Map payloadMap = new HashMap();
551         int len = evts.length;
552         for (int i = 0; i < len; i++) {
553             TriggerEvent te = evts[i];
554             payloadMap.put(te.getName(), te.getPayload());
555         }
556         if (len == 1) {
557             // we have only one event
558             eventData = evts[0].getPayload();
559         }
560         rootCtx.setLocal(EVENT_DATA, eventData);
561         rootCtx.setLocal(EVENT_DATA_MAP, payloadMap);
562         return oldData;
563     }
564 
565     /***
566      * @param oldData The old values to restore to.
567      */
568     private void restoreEventData(final Object[] oldData) {
569         scInstance.getRootContext().setLocal(EVENT_DATA, oldData[0]);
570         scInstance.getRootContext().setLocal(EVENT_DATA_MAP, oldData[1]);
571     }
572 
573     /***
574      * The special variable for storing single event data / payload.
575      */
576     private static final String EVENT_DATA = "_eventdata";
577 
578     /***
579      * The special variable for storing event data / payload,
580      * when multiple events are triggered, keyed by event name.
581      */
582     private static final String EVENT_DATA_MAP = "_eventdatamap";
583 
584     /***
585      * SCXMLExecutor put into motion without setting a model (state machine).
586      */
587     private static final String ERR_NO_STATE_MACHINE =
588         "SCXMLExecutor: State machine not set";
589 
590 }
591