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;
018    
019    import java.io.Serializable;
020    import java.util.ArrayList;
021    import java.util.Arrays;
022    import java.util.HashMap;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.Map;
026    
027    import org.apache.commons.logging.Log;
028    import org.apache.commons.logging.LogFactory;
029    import org.apache.commons.scxml.model.Datamodel;
030    import org.apache.commons.scxml.model.History;
031    import org.apache.commons.scxml.model.ModelException;
032    import org.apache.commons.scxml.model.SCXML;
033    import org.apache.commons.scxml.model.State;
034    import org.apache.commons.scxml.model.Transition;
035    import org.apache.commons.scxml.model.TransitionTarget;
036    import org.apache.commons.scxml.semantics.SCXMLSemanticsImpl;
037    
038    /**
039     * <p>The SCXML &quot;engine&quot; that executes SCXML documents. The
040     * particular semantics used by this engine for executing the SCXML are
041     * encapsulated in the SCXMLSemantics implementation that it uses.</p>
042     *
043     * <p>The default implementation is
044     * <code>org.apache.commons.scxml.semantics.SCXMLSemanticsImpl</code></p>
045     *
046     * @see SCXMLSemantics
047     */
048    public class SCXMLExecutor implements Serializable {
049    
050        /**
051         * Serial version UID.
052         */
053        private static final long serialVersionUID = 1L;
054    
055        /**
056         * The Logger for the SCXMLExecutor.
057         */
058        private Log log = LogFactory.getLog(SCXMLExecutor.class);
059    
060        /**
061         * The stateMachine being executed.
062         */
063        private SCXML stateMachine;
064    
065        /**
066         * The current status of the stateMachine.
067         */
068        private Status currentStatus;
069    
070        /**
071         * The event dispatcher to interface with external documents etc.
072         */
073        private EventDispatcher eventdispatcher;
074    
075        /**
076         * The environment specific error reporter.
077         */
078        private ErrorReporter errorReporter = null;
079    
080        /**
081         * Run-to-completion.
082         */
083        private boolean superStep = true;
084    
085        /**
086         *  Interpretation semantics.
087         */
088        private SCXMLSemantics semantics;
089    
090        /**
091         * The SCInstance.
092         */
093        private SCInstance scInstance;
094    
095        /**
096         * The worker method.
097         * Re-evaluates current status whenever any events are triggered.
098         *
099         * @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 targettype The target type (specified by "targettype"
495         *                   attribute of &lt;invoke&gt; tag).
496         * @param invokerClass The <code>Invoker</code> <code>Class</code>.
497         */
498        public void registerInvokerClass(final String targettype,
499                final Class invokerClass) {
500            scInstance.registerInvokerClass(targettype, invokerClass);
501        }
502    
503        /**
504         * Remove the <code>Invoker</code> registered for this target
505         * type (if there is one registered).
506         *
507         * @param targettype The target type (specified by "targettype"
508         *                   attribute of &lt;invoke&gt; tag).
509         */
510        public void unregisterInvokerClass(final String targettype) {
511            scInstance.unregisterInvokerClass(targettype);
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    }
605