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 "engine" 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 "initialstate" 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 "super-step", 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 <invoke> 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 <invoke> 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