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 "engine" 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 }