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.scxml2;
18  
19  import java.util.HashSet;
20  import java.util.Queue;
21  import java.util.Set;
22  import java.util.concurrent.ConcurrentLinkedQueue;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.apache.commons.scxml2.invoke.Invoker;
27  import org.apache.commons.scxml2.model.EnterableState;
28  import org.apache.commons.scxml2.model.ModelException;
29  import org.apache.commons.scxml2.model.Observable;
30  import org.apache.commons.scxml2.model.SCXML;
31  import org.apache.commons.scxml2.model.TransitionTarget;
32  import org.apache.commons.scxml2.semantics.SCXMLSemanticsImpl;
33  
34  /**
35   * <p>The SCXML &quot;engine&quot; that executes SCXML documents. The
36   * particular semantics used by this engine for executing the SCXML are
37   * encapsulated in the SCXMLSemantics implementation that it uses.</p>
38   *
39   * <p>The default implementation is
40   * <code>org.apache.commons.scxml2.semantics.SCXMLSemanticsImpl</code></p>
41   *
42   * <p>The executor uses SCXMLExecutionContext to manage the state and
43   * provide all the services to the SCXMLSemantics implementation.</p>
44   *
45   * @see SCXMLSemantics
46   */
47  public class SCXMLExecutor implements SCXMLIOProcessor {
48  
49      /**
50       * The Logger for the SCXMLExecutor.
51       */
52      private Log log = LogFactory.getLog(SCXMLExecutor.class);
53  
54      /**
55       * Parent SCXMLExecutor
56       */
57      private SCXMLExecutor parentSCXMLExecutor;
58  
59      /**
60       *  Interpretation semantics.
61       */
62      private SCXMLSemantics semantics;
63  
64      /**
65       * The state machine execution context
66       */
67      private SCXMLExecutionContext exctx;
68  
69      /**
70       * The external event queue
71       */
72      private final Queue<TriggerEvent> externalEventQueue = new ConcurrentLinkedQueue<TriggerEvent>();
73  
74      /**
75       * Convenience constructor.
76       */
77      public SCXMLExecutor() {
78          this(null, null, null, null);
79      }
80  
81      /**
82       * Constructor.
83       *
84       * @param expEvaluator The expression evaluator
85       * @param evtDisp The event dispatcher
86       * @param errRep The error reporter
87       */
88      public SCXMLExecutor(final Evaluator expEvaluator,
89                           final EventDispatcher evtDisp, final ErrorReporter errRep) {
90          this(expEvaluator, evtDisp, errRep, null);
91      }
92  
93      /**
94       * Constructor.
95       *
96       * @param expEvaluator The expression evaluator
97       * @param evtDisp The event dispatcher
98       * @param errRep The error reporter
99       * @param semantics The SCXML semantics
100      */
101     public SCXMLExecutor(final Evaluator expEvaluator,
102                          final EventDispatcher evtDisp, final ErrorReporter errRep,
103                          final SCXMLSemantics semantics) {
104         this.semantics = semantics != null ? semantics : new SCXMLSemanticsImpl();
105         this.exctx = new SCXMLExecutionContext(this, expEvaluator, evtDisp, errRep);
106     }
107 
108     /**
109      * Constructor using a parent SCXMLExecutor
110      *
111      * @param parentSCXMLExecutor the parent SCXMLExecutor
112      */
113     public SCXMLExecutor(final SCXMLExecutor parentSCXMLExecutor) {
114         this.parentSCXMLExecutor = parentSCXMLExecutor;
115         this.semantics = parentSCXMLExecutor.semantics;
116         this.exctx = new SCXMLExecutionContext(this, parentSCXMLExecutor.getEvaluator(),
117                 parentSCXMLExecutor.getEventdispatcher(), parentSCXMLExecutor.getErrorReporter());
118     }
119 
120     /**
121      * @return the parent SCXMLExecutor (if any)
122      */
123     protected SCXMLExecutor getParentSCXMLExecutor() {
124         return parentSCXMLExecutor;
125     }
126 
127     /**
128      * Get the current state machine instance status.
129      *
130      * @return The current Status
131      */
132     public synchronized Status getStatus() {
133         return exctx.getScInstance().getCurrentStatus();
134     }
135 
136     /**
137      * Initializes the state machine with a specific active configuration
138      * <p>
139      * This will first (re)initialize the current state machine: clearing all variable contexts, histories and current
140      * status, and clones the SCXML root datamodel into the root context.
141      * </p>
142      * @param atomicStateIds The set of atomic state ids for the state machine
143      * @throws ModelException when the state machine hasn't been properly configured yet, when an unknown or illegal
144      * stateId is specified, or when the specified active configuration does not represent a legal configuration.
145      * @see {@link SCInstance#initialize()}
146      * @see {@link SCXMLSemantics#isLegalConfiguration(java.util.Set, ErrorReporter)}
147      */
148     public synchronized void setConfiguration(Set<String> atomicStateIds) throws ModelException {
149         exctx.initialize();
150         Set<EnterableState> states = new HashSet<EnterableState>();
151         for (String stateId : atomicStateIds) {
152             TransitionTarget tt = getStateMachine().getTargets().get(stateId);
153             if (tt instanceof EnterableState && ((EnterableState)tt).isAtomicState()) {
154                 EnterableState es = (EnterableState)tt;
155                 while (es != null && !states.add(es)) {
156                     es = es.getParent();
157                 }
158             }
159             else {
160                 throw new ModelException("Illegal atomic stateId "+stateId+": state unknown or not an atomic state");
161             }
162         }
163         if (semantics.isLegalConfiguration(states, getErrorReporter())) {
164             for (EnterableState es : states) {
165                 exctx.getScInstance().getStateConfiguration().enterState(es);
166             }
167             logState();
168         }
169         else {
170             throw new ModelException("Illegal state machine configuration!");
171         }
172     }
173 
174     /**
175      * Set or replace the expression evaluator
176      * <p>
177      * If the state machine instance has been initialized before, it will be initialized again, destroying all existing
178      * state!
179      * </p>
180      * <p>
181      * Also the external event queue will be cleared.
182      * </p>
183      * @param evaluator The evaluator to set
184      * @throws ModelException if attempting to set a null value or the state machine instance failed to re-initialize
185      */
186     public void setEvaluator(final Evaluator evaluator) throws ModelException {
187         exctx.setEvaluator(evaluator);
188     }
189 
190     /**
191      * Get the expression evaluator in use.
192      *
193      * @return Evaluator The evaluator in use.
194      */
195     public Evaluator getEvaluator() {
196         return exctx.getEvaluator();
197     }
198 
199     /**
200      * Get the root context for the state machine execution.
201      * <p>
202      * The root context can be used for providing external data to the state machine
203      * </p>
204      *
205      * @return Context The root context.
206      */
207     public Context getRootContext() {
208         return exctx.getScInstance().getRootContext();
209     }
210 
211     /**
212      * Get the global context for the state machine execution.
213      * <p>
214      * The global context is the top level context within the state machine itself and should be regarded and treated
215      * "read-only" from external usage.
216      * </p>
217      * @return Context The global context.
218      */
219     public Context getGlobalContext() {
220         return exctx.getScInstance().getGlobalContext();
221     }
222 
223     /**
224      * Set the root context for the state machine execution.
225      * <b>NOTE:</b> Should only be used before the executor is set in motion.
226      *
227      * @param rootContext The Context that ties to the host environment.
228      */
229     public void setRootContext(final Context rootContext) {
230         exctx.getScInstance().setRootContext(rootContext);
231     }
232 
233     public void setSingleContext(boolean singleContext) throws ModelException {
234         getSCInstance().setSingleContext(singleContext);
235     }
236 
237     public boolean isSingleContext() {
238         return getSCInstance().isSingleContext();
239     }
240 
241     /**
242      * Get the state machine that is being executed.
243      * <b>NOTE:</b> This is the state machine definition or model used by this
244      * executor instance. It may be shared across multiple executor instances
245      * and should not be altered once in use. Also note that
246      * manipulation of instance data for the executor should happen through
247      * its root context or state contexts only, never through the direct
248      * manipulation of any {@link org.apache.commons.scxml2.model.Datamodel}s associated with this state
249      * machine definition.
250      *
251      * @return Returns the stateMachine.
252      */
253     public SCXML getStateMachine() {
254         return exctx.getStateMachine();
255     }
256 
257     /**
258      * Set or replace the state machine to be executed
259      * <p>
260      * If the state machine instance has been initialized before, it will be initialized again, destroying all existing
261      * state!
262      * </p>
263      * <p>
264      * Also the external event queue will be cleared.
265      * </p>
266      * @param stateMachine The state machine to set
267      * @throws ModelException if attempting to set a null value or the state machine instance failed to re-initialize
268      */
269     public void setStateMachine(final SCXML stateMachine) throws ModelException {
270         exctx.setStateMachine(semantics.normalizeStateMachine(stateMachine, exctx.getErrorReporter()));
271         externalEventQueue.clear();
272     }
273 
274     /**
275      * Get the environment specific error reporter.
276      *
277      * @return Returns the errorReporter.
278      */
279     public ErrorReporter getErrorReporter() {
280         return exctx.getErrorReporter();
281     }
282 
283     /**
284      * Set or replace the error reporter
285      *
286      * @param errorReporter The error reporter to set, if null a SimpleErrorReporter instance will be used instead
287      */
288     public void setErrorReporter(final ErrorReporter errorReporter) {
289         exctx.setErrorReporter(errorReporter);
290     }
291 
292     /**
293      * Get the event dispatcher.
294      *
295      * @return Returns the eventdispatcher.
296      */
297     public EventDispatcher getEventdispatcher() {
298         return exctx.getEventDispatcher();
299     }
300 
301     /**
302      * Set or replace the event dispatch
303      *
304      * @param eventdispatcher The event dispatcher to set, if null a SimpleDispatcher instance will be used instead
305      */
306     public void setEventdispatcher(final EventDispatcher eventdispatcher) {
307         exctx.setEventdispatcher(eventdispatcher);
308     }
309 
310     /**
311      * Set if the SCXML configuration should be checked before execution (default = true)
312      * @param checkLegalConfiguration flag to set
313      */
314     public void setCheckLegalConfiguration(boolean checkLegalConfiguration) {
315         this.exctx.setCheckLegalConfiguration(checkLegalConfiguration);
316     }
317 
318     /**
319      * @return if the SCXML configuration will be checked before execution
320      */
321     public boolean isCheckLegalConfiguration() {
322         return exctx.isCheckLegalConfiguration();
323     }
324 
325     /**
326      * Get the notification registry.
327      *
328      * @return The notification registry.
329      */
330     public NotificationRegistry getNotificationRegistry() {
331         return exctx.getNotificationRegistry();
332     }
333 
334     /**
335      * Add a listener to the {@link Observable}.
336      *
337      * @param observable The {@link Observable} to attach the listener to.
338      * @param listener The SCXMLListener.
339      */
340     public void addListener(final Observable observable, final SCXMLListener listener) {
341         exctx.getNotificationRegistry().addListener(observable, listener);
342     }
343 
344     /**
345      * Remove this listener from the {@link Observable}.
346      *
347      * @param observable The {@link Observable}.
348      * @param listener The SCXMLListener to be removed.
349      */
350     public void removeListener(final Observable observable,
351                                final SCXMLListener listener) {
352         exctx.getNotificationRegistry().removeListener(observable, listener);
353     }
354 
355     /**
356      * Register an Invoker for this target type.
357      *
358      * @param type The target type (specified by "type" attribute of the invoke element).
359      * @param invokerClass The Invoker class.
360      */
361     public void registerInvokerClass(final String type, final Class<? extends Invoker> invokerClass) {
362         exctx.registerInvokerClass(type, invokerClass);
363     }
364 
365     /**
366      * Remove the Invoker registered for this target type (if there is one registered).
367      *
368      * @param type The target type (specified by "type" attribute of the invoke element).
369      */
370     public void unregisterInvokerClass(final String type) {
371         exctx.unregisterInvokerClass(type);
372     }
373 
374     /**
375      * Detach the current SCInstance to allow external serialization.
376      * <p>
377      * {@link #attachInstance(SCInstance)} can be used to re-attach a previously detached instance
378      * </p>
379      * <p>
380      * Note: until an instance is re-attached, no operations are allowed (and probably throw exceptions) except
381      * for {@link #addEvent(TriggerEvent)} which might still be used (concurrently) by running Invokers, or
382      * {@link #hasPendingEvents()} to check for possible pending events.
383      * </p>
384      * @return the detached instance
385      */
386     public SCInstance detachInstance() {
387         return exctx.detachInstance();
388     }
389 
390     /**
391      * Re-attach a previously detached SCInstance.
392      * <p>
393      * Note: an already attached instance will get overwritten (and thus lost).
394      * </p>
395      * @param instance An previously detached SCInstance
396      */
397     public void attachInstance(SCInstance instance) {
398         exctx.attachInstance(instance);
399     }
400 
401     /**
402      * @return Returns true if the state machine is running
403      */
404     public boolean isRunning() {
405         return exctx.isRunning();
406     }
407 
408     /**
409      * Initiate state machine execution.
410      *
411      * @throws ModelException in case there is a fatal SCXML object
412      *  model problem.
413      */
414     public void go() throws ModelException {
415         // same as reset
416         this.reset();
417     }
418 
419     /**
420      * Clear all state and begin executing the state machine
421      *
422      * @throws ModelException if the state machine instance failed to initialize
423      */
424     public void reset() throws ModelException {
425         // clear any pending external events
426         externalEventQueue.clear();
427 
428         // go
429         semantics.firstStep(exctx);
430         logState();
431     }
432 
433     /**
434      * Add a new external event, which may be done concurrently, and even when the current SCInstance is detached.
435      * <p>
436      * No processing of the vent will be done, until the next triggerEvent methods is invoked.
437      * </p>
438      * @param evt an external event
439      */
440     public void addEvent(final TriggerEvent evt) {
441         if (evt != null) {
442             externalEventQueue.add(evt);
443         }
444     }
445 
446     /**
447      * @return Returns true if there are pending external events to be processed.
448      */
449     public boolean hasPendingEvents() {
450         return !externalEventQueue.isEmpty();
451     }
452 
453     /**
454      * @return Returns the current number of pending external events to be processed.
455      */
456     public int getPendingEvents() {
457         return externalEventQueue.size();
458     }
459 
460     /**
461      * Convenience method when only one event needs to be triggered.
462      *
463      * @param evt
464      *            the external events which triggered during the last
465      *            time quantum
466      * @throws ModelException in case there is a fatal SCXML object
467      *            model problem.
468      */
469     public void triggerEvent(final TriggerEvent evt)
470             throws ModelException {
471         addEvent(evt);
472         triggerEvents();
473     }
474 
475     /**
476      * The worker method.
477      * Re-evaluates current status whenever any events are triggered.
478      *
479      * @param evts
480      *            an array of external events which triggered during the last
481      *            time quantum
482      * @throws ModelException in case there is a fatal SCXML object
483      *            model problem.
484      */
485     public void triggerEvents(final TriggerEvent[] evts)
486             throws ModelException {
487         if (evts != null) {
488             for (TriggerEvent evt : evts) {
489                 addEvent(evt);
490             }
491         }
492         triggerEvents();
493     }
494 
495     /**
496      * Trigger all pending and incoming events, until there are no more pending events
497      * @throws ModelException in case there is a fatal SCXML object model problem.
498      */
499     public void triggerEvents() throws ModelException {
500         TriggerEvent evt;
501         while (exctx.isRunning() && (evt = externalEventQueue.poll()) != null) {
502             eventStep(evt);
503         }
504     }
505 
506     protected void eventStep(TriggerEvent event) throws ModelException {
507         semantics.nextStep(exctx, event);
508         logState();
509     }
510 
511     /**
512      * Get the state chart instance for this executor.
513      *
514      * @return The SCInstance for this executor.
515      */
516     protected SCInstance getSCInstance() {
517         return exctx.getScInstance();
518     }
519 
520     /**
521      * Log the current set of active states.
522      */
523     protected void logState() {
524         if (log.isDebugEnabled()) {
525             StringBuilder sb = new StringBuilder("Current States: [ ");
526             for (EnterableState es : getStatus().getStates()) {
527                 sb.append(es.getId()).append(", ");
528             }
529             int length = sb.length();
530             sb.delete(length - 2, length).append(" ]");
531             log.debug(sb.toString());
532         }
533     }
534 }