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.ArrayList;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.LinkedList;
23  import java.util.Map;
24  import java.util.Queue;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.commons.scxml2.env.SimpleDispatcher;
29  import org.apache.commons.scxml2.env.SimpleErrorReporter;
30  import org.apache.commons.scxml2.invoke.Invoker;
31  import org.apache.commons.scxml2.invoke.InvokerException;
32  import org.apache.commons.scxml2.invoke.SimpleSCXMLInvoker;
33  import org.apache.commons.scxml2.model.Invoke;
34  import org.apache.commons.scxml2.model.ModelException;
35  import org.apache.commons.scxml2.model.SCXML;
36  
37  /**
38   * SCXMLExecutionContext provides all the services and internal data used during the interpretation of an SCXML
39   * statemachine across micro and macro steps
40   */
41  public class SCXMLExecutionContext implements SCXMLIOProcessor {
42  
43      /**
44       * Default and required supported SCXML Processor Invoker service URI
45       */
46      public static final String SCXML_INVOKER_TYPE_URI = "http://www.w3.org/TR/scxml/";
47      /**
48       * Alias for {@link #SCXML_INVOKER_TYPE_URI}
49       */
50      public static final String SCXML_INVOKER_TYPE = "scxml";
51  
52      /**
53       * SCXML Execution Logger for the application.
54       */
55      private Log appLog = LogFactory.getLog(SCXMLExecutionContext.class);
56  
57      /**
58       * The action execution context instance, providing restricted access to this execution context
59       */
60      private final ActionExecutionContext actionExecutionContext;
61  
62      /**
63       * The SCXMLExecutor of this SCXMLExecutionContext
64       */
65      private final SCXMLExecutor scxmlExecutor;
66  
67      /**
68       * The SCInstance.
69       */
70      private SCInstance scInstance;
71  
72      /**
73       * The evaluator for expressions.
74       */
75      private Evaluator evaluator;
76  
77      /**
78       * The external IOProcessor for Invokers to communicate back on
79       */
80      private SCXMLIOProcessor externalIOProcessor;
81  
82      /**
83       * The event dispatcher to interface with external documents etc.
84       */
85      private EventDispatcher eventdispatcher;
86  
87      /**
88       * The environment specific error reporter.
89       */
90      private ErrorReporter errorReporter = null;
91  
92      /**
93       * The notification registry.
94       */
95      private NotificationRegistry notificationRegistry;
96  
97      /**
98       * The internal event queue
99       */
100     private final Queue<TriggerEvent> internalEventQueue = new LinkedList<TriggerEvent>();
101 
102     /**
103      * The Invoker classes map, keyed by invoke target types (specified using "type" attribute).
104      */
105     private final Map<String, Class<? extends Invoker>> invokerClasses = new HashMap<String, Class<? extends Invoker>>();
106 
107     /**
108      * The map storing the unique invokeId for an Invoke with an active Invoker
109      */
110     private final Map<Invoke, String> invokeIds = new HashMap<Invoke, String>();
111 
112     /**
113      * The Map of active Invoker, keyed by their unique invokeId.
114      */
115     private final Map<String, Invoker> invokers = new HashMap<String, Invoker>();
116 
117     /**
118      * The Map of the current ioProcessors
119      */
120     private final Map<String, SCXMLIOProcessor> ioProcessors = new HashMap<String, SCXMLIOProcessor>();
121 
122     /**
123      * Flag indicating if the SCXML configuration should be checked before execution (default = true)
124      */
125     private boolean checkLegalConfiguration = true;
126 
127     /**
128      * Local cache of the SCInstance sessionId, to be able to check against clear/reinitialization
129      */
130     private String sessionId;
131 
132     /**
133      * Constructor
134      *
135      * @param scxmlExecutor The SCXMLExecutor of this SCXMLExecutionContext
136      * @param evaluator The evaluator
137      * @param eventDispatcher The event dispatcher, if null a SimpleDispatcher instance will be used
138      * @param errorReporter The error reporter, if null a SimpleErrorReporter instance will be used
139      */
140     protected SCXMLExecutionContext(SCXMLExecutor scxmlExecutor, Evaluator evaluator,
141                                     EventDispatcher eventDispatcher, ErrorReporter errorReporter) {
142         this.scxmlExecutor = scxmlExecutor;
143         this.externalIOProcessor = scxmlExecutor;
144         this.evaluator = evaluator;
145         this.eventdispatcher = eventDispatcher != null ? eventDispatcher : new SimpleDispatcher();
146         this.errorReporter = errorReporter != null ? errorReporter : new SimpleErrorReporter();
147         this.notificationRegistry = new NotificationRegistry();
148 
149         this.scInstance = new SCInstance(this, this.evaluator, this.errorReporter);
150         this.actionExecutionContext = new ActionExecutionContext(this);
151 
152         ioProcessors.put(SCXMLIOProcessor.DEFAULT_EVENT_PROCESSOR, getExternalIOProcessor());
153         ioProcessors.put(SCXMLIOProcessor.SCXML_EVENT_PROCESSOR, getExternalIOProcessor());
154         ioProcessors.put(SCXMLIOProcessor.INTERNAL_EVENT_PROCESSOR, getInternalIOProcessor());
155         if (scxmlExecutor.getParentSCXMLExecutor() != null) {
156             ioProcessors.put(SCXMLIOProcessor.PARENT_EVENT_PROCESSOR, scxmlExecutor.getParentSCXMLExecutor());
157         }
158         initializeIOProcessors();
159         registerInvokerClass(SCXML_INVOKER_TYPE_URI, SimpleSCXMLInvoker.class);
160         registerInvokerClass(SCXML_INVOKER_TYPE, SimpleSCXMLInvoker.class);
161     }
162 
163     public SCXMLExecutor getSCXMLExecutor() {
164         return scxmlExecutor;
165     }
166 
167     public SCXMLIOProcessor getExternalIOProcessor() {
168         return externalIOProcessor;
169     }
170 
171     public SCXMLIOProcessor getInternalIOProcessor() {
172         return this;
173     }
174 
175     /**
176      * @return Returns the restricted execution context for actions
177      */
178     public ActionExecutionContext getActionExecutionContext() {
179         return actionExecutionContext;
180     }
181 
182     /**
183      * @return Returns true if this state machine is running
184      */
185     public boolean isRunning() {
186         return scInstance.isRunning();
187     }
188 
189     /**
190      * Stop a running state machine
191      */
192     public void stopRunning() {
193         scInstance.setRunning(false);
194     }
195 
196     /**
197      * Set if the SCXML configuration should be checked before execution (default = true)
198      * @param checkLegalConfiguration flag to set
199      */
200     public void setCheckLegalConfiguration(boolean checkLegalConfiguration) {
201         this.checkLegalConfiguration = checkLegalConfiguration;
202     }
203 
204     /**
205      * @return if the SCXML configuration will be checked before execution
206      */
207     public boolean isCheckLegalConfiguration() {
208         return checkLegalConfiguration;
209     }
210 
211     /**
212      * Initialize method which will cancel all current active Invokers, clear the internal event queue and mark the
213      * state machine process as running (again).
214      *
215      * @throws ModelException if the state machine instance failed to initialize.
216      */
217     public void initialize() throws ModelException {
218         if (!invokeIds.isEmpty()) {
219             for (Invoke invoke : new ArrayList<Invoke>(invokeIds.keySet())) {
220                 cancelInvoker(invoke);
221             }
222         }
223         internalEventQueue.clear();
224         scInstance.initialize();
225         initializeIOProcessors();
226         scInstance.setRunning(true);
227     }
228 
229     /**
230      * @return Returns the SCXML Execution Logger for the application
231      */
232     public Log getAppLog() {
233         return appLog;
234     }
235 
236     /**
237      * @return Returns the state machine
238      */
239     public SCXML getStateMachine() {
240         return scInstance.getStateMachine();
241     }
242 
243     /**
244      * Set or replace the state machine to be executed
245      * <p>
246      * If the state machine instance has been initialized before, it will be initialized again, destroying all existing
247      * state!
248      * </p>
249      * @param stateMachine The state machine to set
250      * @throws ModelException if attempting to set a null value or the state machine instance failed to re-initialize
251      */
252     protected void setStateMachine(SCXML stateMachine) throws ModelException {
253         scInstance.setStateMachine(stateMachine);
254         // synchronize possible derived evaluator
255         this.evaluator = scInstance.getEvaluator();
256         initializeIOProcessors();
257     }
258 
259     /**
260      * The SCXML specification section "C.1.1 _ioprocessors Value" states that the SCXMLEventProcessor <em>must</em>
261      * maintain a 'location' field inside its entry in the _ioprocessors environment variable.
262      * @return the 'location' of the SCXMLEventProcessor
263      */
264     public String getLocation() {
265         return null;
266     }
267 
268     /**
269      * @return Returns the SCInstance
270      */
271     public SCInstance getScInstance() {
272         return scInstance;
273     }
274 
275     /**
276      * @return Returns The evaluator.
277      */
278     public Evaluator getEvaluator() {
279         return evaluator;
280     }
281 
282     /**
283      * Set or replace the evaluator
284      * <p>
285      * If the state machine instance has been initialized before, it will be initialized again, destroying all existing
286      * state!
287      * </p>
288      * @param evaluator The evaluator to set
289      * @throws ModelException if attempting to set a null value or the state machine instance failed to re-initialize
290      */
291     protected void setEvaluator(Evaluator evaluator) throws ModelException {
292         scInstance.setEvaluator(evaluator, false);
293         // synchronize possible derived evaluator
294         this.evaluator = scInstance.getEvaluator();
295         initializeIOProcessors();
296     }
297 
298     /**
299      * @return Returns the error reporter
300      */
301     public ErrorReporter getErrorReporter() {
302         return errorReporter;
303     }
304 
305     /**
306      * Set or replace the error reporter
307      *
308      * @param errorReporter The error reporter to set, if null a SimpleErrorReporter instance will be used instead
309      */
310     protected void setErrorReporter(ErrorReporter errorReporter) {
311         this.errorReporter = errorReporter != null ? errorReporter : new SimpleErrorReporter();
312         try {
313             scInstance.setErrorReporter(errorReporter);
314         }
315         catch (ModelException me) {
316             // won't happen
317         }
318     }
319 
320     /**
321      * @return Returns the event dispatcher
322      */
323     public EventDispatcher getEventDispatcher() {
324         return eventdispatcher;
325     }
326 
327     /**
328      * Set or replace the event dispatch
329      *
330      * @param eventdispatcher The event dispatcher to set, if null a SimpleDispatcher instance will be used instead
331      */
332     protected void setEventdispatcher(EventDispatcher eventdispatcher) {
333         this.eventdispatcher = eventdispatcher != null ? eventdispatcher : new SimpleDispatcher();
334     }
335 
336     /**
337      * @return Returns the notification registry
338      */
339     public NotificationRegistry getNotificationRegistry() {
340         return notificationRegistry;
341     }
342 
343     /**
344      * Initialize the _ioprocessors environment variable, which only can be done when the evaluator is available
345      */
346     protected void initializeIOProcessors() {
347         if (scInstance.getEvaluator() != null) {
348             // lazy register/reset #_scxml_sessionId event target
349             String currentSessionId = (String)getScInstance().getSystemContext().get(SCXMLSystemContext.SESSIONID_KEY);
350             if (sessionId != null && !sessionId.equals(currentSessionId)) {
351                 // remove possible old/stale #_scxml_sessionId target
352                 ioProcessors.remove(SCXMLIOProcessor.SCXML_SESSION_EVENT_PROCESSOR_PREFIX+sessionId);
353             }
354             sessionId = currentSessionId;
355             if (!ioProcessors.containsKey(SCXMLIOProcessor.SCXML_SESSION_EVENT_PROCESSOR_PREFIX+sessionId)) {
356                 ioProcessors.put(SCXMLIOProcessor.SCXML_SESSION_EVENT_PROCESSOR_PREFIX+sessionId, getExternalIOProcessor());
357             }
358             getScInstance().getSystemContext().setLocal(SCXMLSystemContext.IOPROCESSORS_KEY, Collections.unmodifiableMap(ioProcessors));
359         }
360     }
361 
362     /**
363      * Detach the current SCInstance to allow external serialization.
364      * <p>
365      * {@link #attachInstance(SCInstance)} can be used to re-attach a previously detached instance
366      * </p>
367      * @return the detached instance
368      */
369     protected SCInstance detachInstance() {
370         SCInstance instance = scInstance;
371         scInstance.detach();
372         Map<String, Object> systemVars = scInstance.getSystemContext().getVars();
373         systemVars.remove(SCXMLSystemContext.IOPROCESSORS_KEY);
374         systemVars.remove(SCXMLSystemContext.EVENT_KEY);
375         scInstance = null;
376         return instance;
377     }
378 
379     /**
380      * Re-attach a previously detached SCInstance.
381      * <p>
382      * Note: an already attached instance will get overwritten (and thus lost).
383      * </p>
384      * @param instance An previously detached SCInstance
385      */
386     protected void attachInstance(SCInstance instance) {
387         if (scInstance != null ) {
388             scInstance.detach();
389         }
390         scInstance = instance;
391         if (scInstance != null) {
392             scInstance.detach();
393             try {
394                 scInstance.setInternalIOProcessor(this);
395                 scInstance.setEvaluator(evaluator, true);
396                 scInstance.setErrorReporter(errorReporter);
397                 initializeIOProcessors();
398             }
399             catch (ModelException me) {
400                 // should not happen
401             }
402         }
403     }
404 
405     /**
406      * Register an Invoker for this target type.
407      *
408      * @param type The target type (specified by "type" attribute of the invoke element).
409      * @param invokerClass The Invoker class.
410      */
411     protected void registerInvokerClass(final String type, final Class<? extends Invoker> invokerClass) {
412         invokerClasses.put(type, invokerClass);
413     }
414 
415     /**
416      * Remove the Invoker registered for this target type (if there is one registered).
417      *
418      * @param type The target type (specified by "type" attribute of the invoke element).
419      */
420     protected void unregisterInvokerClass(final String type) {
421         invokerClasses.remove(type);
422     }
423 
424     /**
425      * Create a new {@link Invoker}
426      *
427      * @param type The type of the target being invoked.
428      * @return An {@link Invoker} for the specified type, if an
429      *         invoker class is registered against that type,
430      *         <code>null</code> otherwise.
431      * @throws InvokerException When a suitable {@link Invoker} cannot be instantiated.
432      */
433     public Invoker newInvoker(final String type) throws InvokerException {
434         Class<? extends Invoker> invokerClass = invokerClasses.get(type);
435         if (invokerClass == null) {
436             throw new InvokerException("No Invoker registered for type \"" + type + "\"");
437         }
438         try {
439             return invokerClass.newInstance();
440         } catch (InstantiationException ie) {
441             throw new InvokerException(ie.getMessage(), ie.getCause());
442         } catch (IllegalAccessException iae) {
443             throw new InvokerException(iae.getMessage(), iae.getCause());
444         }
445     }
446 
447     /**
448      * Get the {@link Invoker} for this {@link Invoke}.
449      * May return <code>null</code>. A non-null {@link Invoker} will be
450      * returned if and only if the {@link Invoke} parent TransitionalState is
451      * currently active and contains the &lt;invoke&gt; child.
452      *
453      * @param invoke The <code>Invoke</code>.
454      * @return The Invoker.
455      */
456     public Invoker getInvoker(final Invoke invoke) {
457         return invokers.get(invokeIds.get(invoke));
458     }
459 
460     /**
461      * Register the active {@link Invoker} for a {@link Invoke}
462      *
463      * @param invoke The Invoke.
464      * @param invoker The Invoker.
465      * @throws InvokerException when the Invoker doesn't have an invokerId
466      */
467     public void registerInvoker(final Invoke invoke, final Invoker invoker) throws InvokerException {
468         String invokeId = invoker.getInvokeId();
469         if (invokeId == null) {
470             throw new InvokerException("Registering an Invoker without invokerId");
471         }
472         invokeIds.put(invoke, invokeId);
473         invokers.put(invokeId, invoker);
474         ioProcessors.put(SCXMLIOProcessor.EVENT_PROCESSOR_ALIAS_PREFIX+invoke.getId(), invoker.getChildIOProcessor());
475         initializeIOProcessors();
476     }
477 
478     /**
479      * Remove a previously active Invoker, which must already have been canceled
480      * @param invoke The Invoke for the Invoker to remove
481      */
482     public void removeInvoker(final Invoke invoke) {
483         invokers.remove(invokeIds.remove(invoke));
484         ioProcessors.remove(SCXMLIOProcessor.EVENT_PROCESSOR_ALIAS_PREFIX+invoke.getId());
485         initializeIOProcessors();
486     }
487 
488     /**
489      * @return Returns the map of current active Invokes and their invokeId
490      */
491     public Map<Invoke, String> getInvokeIds() {
492         return invokeIds;
493     }
494 
495 
496     /**
497      * Cancel and remove an active Invoker
498      *
499      * @param invoke The Invoke for the Invoker to cancel
500      */
501     public void cancelInvoker(Invoke invoke) {
502         String invokeId = invokeIds.get(invoke);
503         if (invokeId != null) {
504             try {
505                 invokers.get(invokeId).cancel();
506             } catch (InvokerException ie) {
507                 TriggerEvent te = new TriggerEvent("failed.invoke.cancel."+invokeId, TriggerEvent.ERROR_EVENT);
508                 addEvent(te);
509             }
510             removeInvoker(invoke);
511         }
512     }
513 
514     /**
515      * Add an event to the internal event queue
516      * @param event The event
517      */
518     @Override
519     public void addEvent(TriggerEvent event) {
520         internalEventQueue.add(event);
521     }
522 
523     /**
524      * @return Returns the next event from the internal event queue, if available
525      */
526     public TriggerEvent nextInternalEvent() {
527         return internalEventQueue.poll();
528     }
529 
530     /**
531      * @return Returns true if the internal event queue isn't empty
532      */
533     public boolean hasPendingInternalEvent() {
534         return !internalEventQueue.isEmpty();
535     }
536 }