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 }