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