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      * <b>NOTE:</b> Should only be used before the executor is set in motion.
278      *
279      * @param evaluator The evaluator to set.
280      */
281     public void setEvaluator(final Evaluator evaluator) {
282         this.scInstance.setEvaluator(evaluator);
283     }
284 
285     /**
286      * Get the expression evaluator in use.
287      *
288      * @return Evaluator The evaluator in use.
289      */
290     public Evaluator getEvaluator() {
291         return scInstance.getEvaluator();
292     }
293 
294     /**
295      * Set the root context for this execution.
296      * <b>NOTE:</b> Should only be used before the executor is set in motion.
297      *
298      * @param rootContext The Context that ties to the host environment.
299      */
300     public void setRootContext(final Context rootContext) {
301         this.scInstance.setRootContext(rootContext);
302     }
303 
304     /**
305      * Get the root context for this execution.
306      *
307      * @return Context The root context.
308      */
309     public Context getRootContext() {
310         return scInstance.getRootContext();
311     }
312 
313     /**
314      * Get the state machine that is being executed.
315      * <b>NOTE:</b> This is the state machine definition or model used by this
316      * executor instance. It may be shared across multiple executor instances
317      * and as a best practice, should not be altered. Also note that
318      * manipulation of instance data for the executor should happen through
319      * its root context or state contexts only, never through the direct
320      * manipulation of any {@link Datamodel}s associated with this state
321      * machine definition.
322      *
323      * @return Returns the stateMachine.
324      */
325     public SCXML getStateMachine() {
326         return stateMachine;
327     }
328 
329     /**
330      * Set the state machine to be executed.
331      * <b>NOTE:</b> Should only be used before the executor is set in motion.
332      *
333      * @param stateMachine The stateMachine to set.
334      */
335     public void setStateMachine(final SCXML stateMachine) {
336         // NormalizeStateMachine
337         SCXML sm = semantics.normalizeStateMachine(stateMachine,
338                 errorReporter);
339         // StoreStateMachine
340         this.stateMachine = sm;
341     }
342 
343     /**
344      * Initiate state machine execution.
345      *
346      * @throws ModelException in case there is a fatal SCXML object
347      *  model problem.
348      */
349     public void go() throws ModelException {
350         // same as reset
351         this.reset();
352     }
353 
354     /**
355      * Get the environment specific error reporter.
356      *
357      * @return Returns the errorReporter.
358      */
359     public ErrorReporter getErrorReporter() {
360         return errorReporter;
361     }
362 
363     /**
364      * Set the environment specific error reporter.
365      *
366      * @param errorReporter The errorReporter to set.
367      */
368     public void setErrorReporter(final ErrorReporter errorReporter) {
369         this.errorReporter = errorReporter;
370     }
371 
372     /**
373      * Get the event dispatcher.
374      *
375      * @return Returns the eventdispatcher.
376      */
377     public EventDispatcher getEventdispatcher() {
378         return eventdispatcher;
379     }
380 
381     /**
382      * Set the event dispatcher.
383      *
384      * @param eventdispatcher The eventdispatcher to set.
385      */
386     public void setEventdispatcher(final EventDispatcher eventdispatcher) {
387         this.eventdispatcher = eventdispatcher;
388     }
389 
390     /**
391      * Use &quot;super-step&quot;, default is <code>true</code>
392      * (that is, run-to-completion is default).
393      *
394      * @return Returns the superStep property.
395      * @see #setSuperStep(boolean)
396      */
397     public boolean isSuperStep() {
398         return superStep;
399     }
400 
401     /**
402      * Set the super step.
403      *
404      * @param superStep
405      * if true, the internal derived events are also processed
406      *    (run-to-completion);
407      * if false, the internal derived events are stored in the
408      * CurrentStatus property and processed within the next
409      * triggerEvents() invocation, also the immediate (empty event) transitions
410      * are deferred until the next step
411       */
412     public void setSuperStep(final boolean superStep) {
413         this.superStep = superStep;
414     }
415 
416     /**
417      * Add a listener to the document root.
418      *
419      * @param scxml The document root to attach listener to.
420      * @param listener The SCXMLListener.
421      */
422     public void addListener(final SCXML scxml, final SCXMLListener listener) {
423         Object observable = scxml;
424         scInstance.getNotificationRegistry().addListener(observable, listener);
425     }
426 
427     /**
428      * Remove this listener from the document root.
429      *
430      * @param scxml The document root.
431      * @param listener The SCXMLListener to be removed.
432      */
433     public void removeListener(final SCXML scxml,
434             final SCXMLListener listener) {
435         Object observable = scxml;
436         scInstance.getNotificationRegistry().removeListener(observable,
437             listener);
438     }
439 
440     /**
441      * Add a listener to this transition target.
442      *
443      * @param transitionTarget The <code>TransitionTarget</code> to
444      *                         attach listener to.
445      * @param listener The SCXMLListener.
446      */
447     public void addListener(final TransitionTarget transitionTarget,
448             final SCXMLListener listener) {
449         Object observable = transitionTarget;
450         scInstance.getNotificationRegistry().addListener(observable, listener);
451     }
452 
453     /**
454      * Remove this listener for this transition target.
455      *
456      * @param transitionTarget The <code>TransitionTarget</code>.
457      * @param listener The SCXMLListener to be removed.
458      */
459     public void removeListener(final TransitionTarget transitionTarget,
460             final SCXMLListener listener) {
461         Object observable = transitionTarget;
462         scInstance.getNotificationRegistry().removeListener(observable,
463             listener);
464     }
465 
466     /**
467      * Add a listener to this transition.
468      *
469      * @param transition The <code>Transition</code> to attach listener to.
470      * @param listener The SCXMLListener.
471      */
472     public void addListener(final Transition transition,
473             final SCXMLListener listener) {
474         Object observable = transition;
475         scInstance.getNotificationRegistry().addListener(observable, listener);
476     }
477 
478     /**
479      * Remove this listener for this transition.
480      *
481      * @param transition The <code>Transition</code>.
482      * @param listener The SCXMLListener to be removed.
483      */
484     public void removeListener(final Transition transition,
485             final SCXMLListener listener) {
486         Object observable = transition;
487         scInstance.getNotificationRegistry().removeListener(observable,
488             listener);
489     }
490 
491     /**
492      * Register an <code>Invoker</code> for this target type.
493      *
494      * @param type The target type (specified by "type"
495      *             attribute of &lt;invoke&gt; tag).
496      * @param invokerClass The <code>Invoker</code> <code>Class</code>.
497      */
498     public void registerInvokerClass(final String type,
499             final Class invokerClass) {
500         scInstance.registerInvokerClass(type, invokerClass);
501     }
502 
503     /**
504      * Remove the <code>Invoker</code> registered for this target
505      * type (if there is one registered).
506      *
507      * @param type The target type (specified by "type"
508      *             attribute of &lt;invoke&gt; tag).
509      */
510     public void unregisterInvokerClass(final String type) {
511         scInstance.unregisterInvokerClass(type);
512     }
513 
514     /**
515      * Get the state chart instance for this executor.
516      *
517      * @return The SCInstance for this executor.
518      */
519     SCInstance getSCInstance() {
520         return scInstance;
521     }
522 
523     /**
524      * Log the current set of active states.
525      */
526     private void logState() {
527         if (log.isDebugEnabled()) {
528             Iterator si = currentStatus.getStates().iterator();
529             StringBuffer sb = new StringBuffer("Current States: [");
530             while (si.hasNext()) {
531                 State s = (State) si.next();
532                 sb.append(s.getId());
533                 if (si.hasNext()) {
534                     sb.append(", ");
535                 }
536             }
537             sb.append(']');
538             log.debug(sb.toString());
539         }
540     }
541 
542     /**
543      * @param step The most recent Step
544      */
545     private void updateStatus(final Step step) {
546         currentStatus = step.getAfterStatus();
547         scInstance.getRootContext().setLocal("_ALL_STATES",
548             SCXMLHelper.getAncestorClosure(currentStatus.getStates(), null));
549         setEventData((TriggerEvent[]) currentStatus.getEvents().
550             toArray(new TriggerEvent[0]));
551     }
552 
553     /**
554      * @param evts The events being triggered.
555      * @return Object[] Previous values.
556      */
557     private Object[] setEventData(final TriggerEvent[] evts) {
558         Context rootCtx = scInstance.getRootContext();
559         Object[] oldData = {rootCtx.get(EVENT_DATA),
560             rootCtx.get(EVENT_DATA_MAP)};
561         int len = evts.length;
562         if (len > 0) { // 0 has retry semantics (eg: see usage in reset())
563             Object eventData = null;
564             Map payloadMap = new HashMap();
565             for (int i = 0; i < len; i++) {
566                 TriggerEvent te = evts[i];
567                 payloadMap.put(te.getName(), te.getPayload());
568             }
569             if (len == 1) {
570                 // we have only one event
571                 eventData = evts[0].getPayload();
572             }
573             rootCtx.setLocal(EVENT_DATA, eventData);
574             rootCtx.setLocal(EVENT_DATA_MAP, payloadMap);
575         }
576         return oldData;
577     }
578 
579     /**
580      * @param oldData The old values to restore to.
581      */
582     private void restoreEventData(final Object[] oldData) {
583         scInstance.getRootContext().setLocal(EVENT_DATA, oldData[0]);
584         scInstance.getRootContext().setLocal(EVENT_DATA_MAP, oldData[1]);
585     }
586 
587     /**
588      * The special variable for storing single event data / payload.
589      */
590     private static final String EVENT_DATA = "_eventdata";
591 
592     /**
593      * The special variable for storing event data / payload,
594      * when multiple events are triggered, keyed by event name.
595      */
596     private static final String EVENT_DATA_MAP = "_eventdatamap";
597 
598     /**
599      * SCXMLExecutor put into motion without setting a model (state machine).
600      */
601     private static final String ERR_NO_STATE_MACHINE =
602         "SCXMLExecutor: State machine not set";
603 
604 }