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 */ 017package org.apache.commons.scxml2; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.LinkedList; 023import java.util.Map; 024import java.util.Queue; 025 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028import org.apache.commons.scxml2.env.SimpleDispatcher; 029import org.apache.commons.scxml2.env.SimpleErrorReporter; 030import org.apache.commons.scxml2.invoke.Invoker; 031import org.apache.commons.scxml2.invoke.InvokerException; 032import org.apache.commons.scxml2.invoke.SimpleSCXMLInvoker; 033import org.apache.commons.scxml2.model.Invoke; 034import org.apache.commons.scxml2.model.ModelException; 035import org.apache.commons.scxml2.model.SCXML; 036 037/** 038 * SCXMLExecutionContext provides all the services and internal data used during the interpretation of an SCXML 039 * statemachine across micro and macro steps 040 */ 041public class SCXMLExecutionContext implements SCXMLIOProcessor { 042 043 /** 044 * Default and required supported SCXML Processor Invoker service URI 045 */ 046 public static final String SCXML_INVOKER_TYPE_URI = "http://www.w3.org/TR/scxml/"; 047 /** 048 * Alias for {@link #SCXML_INVOKER_TYPE_URI} 049 */ 050 public static final String SCXML_INVOKER_TYPE = "scxml"; 051 052 /** 053 * SCXML Execution Logger for the application. 054 */ 055 private Log appLog = LogFactory.getLog(SCXMLExecutionContext.class); 056 057 /** 058 * The action execution context instance, providing restricted access to this execution context 059 */ 060 private final ActionExecutionContext actionExecutionContext; 061 062 /** 063 * The SCXMLExecutor of this SCXMLExecutionContext 064 */ 065 private final SCXMLExecutor scxmlExecutor; 066 067 /** 068 * The SCInstance. 069 */ 070 private SCInstance scInstance; 071 072 /** 073 * The evaluator for expressions. 074 */ 075 private Evaluator evaluator; 076 077 /** 078 * The external IOProcessor for Invokers to communicate back on 079 */ 080 private SCXMLIOProcessor externalIOProcessor; 081 082 /** 083 * The event dispatcher to interface with external documents etc. 084 */ 085 private EventDispatcher eventdispatcher; 086 087 /** 088 * The environment specific error reporter. 089 */ 090 private ErrorReporter errorReporter = null; 091 092 /** 093 * The notification registry. 094 */ 095 private NotificationRegistry notificationRegistry; 096 097 /** 098 * The internal event queue 099 */ 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 <invoke> 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}