| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.scxml2; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.Map; |
| import java.util.Queue; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.commons.scxml2.env.SimpleDispatcher; |
| import org.apache.commons.scxml2.env.SimpleErrorReporter; |
| import org.apache.commons.scxml2.invoke.Invoker; |
| import org.apache.commons.scxml2.invoke.InvokerException; |
| import org.apache.commons.scxml2.invoke.SimpleSCXMLInvoker; |
| import org.apache.commons.scxml2.model.Invoke; |
| import org.apache.commons.scxml2.model.ModelException; |
| import org.apache.commons.scxml2.model.SCXML; |
| |
| /** |
| * SCXMLExecutionContext provides all the services and internal data used during the interpretation of an SCXML |
| * statemachine across micro and macro steps |
| */ |
| public class SCXMLExecutionContext implements SCXMLIOProcessor { |
| |
| /** |
| * Default and required supported SCXML Processor Invoker service URI |
| */ |
| public static final String SCXML_INVOKER_TYPE_URI = "http://www.w3.org/TR/scxml/"; |
| /** |
| * Alias for {@link #SCXML_INVOKER_TYPE_URI} |
| */ |
| public static final String SCXML_INVOKER_TYPE = "scxml"; |
| |
| /** |
| * SCXML Execution Logger for the application. |
| */ |
| private Log appLog = LogFactory.getLog(SCXMLExecutionContext.class); |
| |
| /** |
| * The action execution context instance, providing restricted access to this execution context |
| */ |
| private final ActionExecutionContext actionExecutionContext; |
| |
| /** |
| * The SCXMLExecutor of this SCXMLExecutionContext |
| */ |
| private final SCXMLExecutor scxmlExecutor; |
| |
| /** |
| * The SCInstance. |
| */ |
| private SCInstance scInstance; |
| |
| /** |
| * The evaluator for expressions. |
| */ |
| private Evaluator evaluator; |
| |
| /** |
| * The external IOProcessor for Invokers to communicate back on |
| */ |
| private SCXMLIOProcessor externalIOProcessor; |
| |
| /** |
| * The event dispatcher to interface with external documents etc. |
| */ |
| private EventDispatcher eventdispatcher; |
| |
| /** |
| * The environment specific error reporter. |
| */ |
| private ErrorReporter errorReporter = null; |
| |
| /** |
| * The notification registry. |
| */ |
| private NotificationRegistry notificationRegistry; |
| |
| /** |
| * The internal event queue |
| */ |
| private final Queue<TriggerEvent> internalEventQueue = new LinkedList<TriggerEvent>(); |
| |
| /** |
| * The Invoker classes map, keyed by invoke target types (specified using "type" attribute). |
| */ |
| private final Map<String, Class<? extends Invoker>> invokerClasses = new HashMap<String, Class<? extends Invoker>>(); |
| |
| /** |
| * The map storing the unique invokeId for an Invoke with an active Invoker |
| */ |
| private final Map<Invoke, String> invokeIds = new HashMap<Invoke, String>(); |
| |
| /** |
| * The Map of active Invoker, keyed by their unique invokeId. |
| */ |
| private final Map<String, Invoker> invokers = new HashMap<String, Invoker>(); |
| |
| /** |
| * The Map of the current ioProcessors |
| */ |
| private final Map<String, SCXMLIOProcessor> ioProcessors = new HashMap<String, SCXMLIOProcessor>(); |
| |
| /** |
| * Flag indicating if the SCXML configuration should be checked before execution (default = true) |
| */ |
| private boolean checkLegalConfiguration = true; |
| |
| /** |
| * Local cache of the SCInstance sessionId, to be able to check against clear/reinitialization |
| */ |
| private String sessionId; |
| |
| /** |
| * Constructor |
| * |
| * @param scxmlExecutor The SCXMLExecutor of this SCXMLExecutionContext |
| * @param evaluator The evaluator |
| * @param eventDispatcher The event dispatcher, if null a SimpleDispatcher instance will be used |
| * @param errorReporter The error reporter, if null a SimpleErrorReporter instance will be used |
| */ |
| protected SCXMLExecutionContext(SCXMLExecutor scxmlExecutor, Evaluator evaluator, |
| EventDispatcher eventDispatcher, ErrorReporter errorReporter) { |
| this.scxmlExecutor = scxmlExecutor; |
| this.externalIOProcessor = scxmlExecutor; |
| this.evaluator = evaluator; |
| this.eventdispatcher = eventDispatcher != null ? eventDispatcher : new SimpleDispatcher(); |
| this.errorReporter = errorReporter != null ? errorReporter : new SimpleErrorReporter(); |
| this.notificationRegistry = new NotificationRegistry(); |
| |
| this.scInstance = new SCInstance(this, this.evaluator, this.errorReporter); |
| this.actionExecutionContext = new ActionExecutionContext(this); |
| |
| ioProcessors.put(SCXMLIOProcessor.DEFAULT_EVENT_PROCESSOR, getExternalIOProcessor()); |
| ioProcessors.put(SCXMLIOProcessor.SCXML_EVENT_PROCESSOR, getExternalIOProcessor()); |
| ioProcessors.put(SCXMLIOProcessor.INTERNAL_EVENT_PROCESSOR, getInternalIOProcessor()); |
| if (scxmlExecutor.getParentSCXMLExecutor() != null) { |
| ioProcessors.put(SCXMLIOProcessor.PARENT_EVENT_PROCESSOR, scxmlExecutor.getParentSCXMLExecutor()); |
| } |
| initializeIOProcessors(); |
| registerInvokerClass(SCXML_INVOKER_TYPE_URI, SimpleSCXMLInvoker.class); |
| registerInvokerClass(SCXML_INVOKER_TYPE, SimpleSCXMLInvoker.class); |
| } |
| |
| public SCXMLExecutor getSCXMLExecutor() { |
| return scxmlExecutor; |
| } |
| |
| public SCXMLIOProcessor getExternalIOProcessor() { |
| return externalIOProcessor; |
| } |
| |
| public SCXMLIOProcessor getInternalIOProcessor() { |
| return this; |
| } |
| |
| /** |
| * @return Returns the restricted execution context for actions |
| */ |
| public ActionExecutionContext getActionExecutionContext() { |
| return actionExecutionContext; |
| } |
| |
| /** |
| * @return Returns true if this state machine is running |
| */ |
| public boolean isRunning() { |
| return scInstance.isRunning(); |
| } |
| |
| /** |
| * Stop a running state machine |
| */ |
| public void stopRunning() { |
| scInstance.setRunning(false); |
| } |
| |
| /** |
| * Set if the SCXML configuration should be checked before execution (default = true) |
| * @param checkLegalConfiguration flag to set |
| */ |
| public void setCheckLegalConfiguration(boolean checkLegalConfiguration) { |
| this.checkLegalConfiguration = checkLegalConfiguration; |
| } |
| |
| /** |
| * @return if the SCXML configuration will be checked before execution |
| */ |
| public boolean isCheckLegalConfiguration() { |
| return checkLegalConfiguration; |
| } |
| |
| /** |
| * Initialize method which will cancel all current active Invokers, clear the internal event queue and mark the |
| * state machine process as running (again). |
| * |
| * @throws ModelException if the state machine instance failed to initialize. |
| */ |
| public void initialize() throws ModelException { |
| if (!invokeIds.isEmpty()) { |
| for (Invoke invoke : new ArrayList<Invoke>(invokeIds.keySet())) { |
| cancelInvoker(invoke); |
| } |
| } |
| internalEventQueue.clear(); |
| scInstance.initialize(); |
| initializeIOProcessors(); |
| scInstance.setRunning(true); |
| } |
| |
| /** |
| * @return Returns the SCXML Execution Logger for the application |
| */ |
| public Log getAppLog() { |
| return appLog; |
| } |
| |
| /** |
| * @return Returns the state machine |
| */ |
| public SCXML getStateMachine() { |
| return scInstance.getStateMachine(); |
| } |
| |
| /** |
| * Set or replace the state machine to be executed |
| * <p> |
| * If the state machine instance has been initialized before, it will be initialized again, destroying all existing |
| * state! |
| * </p> |
| * @param stateMachine The state machine to set |
| * @throws ModelException if attempting to set a null value or the state machine instance failed to re-initialize |
| */ |
| protected void setStateMachine(SCXML stateMachine) throws ModelException { |
| scInstance.setStateMachine(stateMachine); |
| // synchronize possible derived evaluator |
| this.evaluator = scInstance.getEvaluator(); |
| initializeIOProcessors(); |
| } |
| |
| /** |
| * The SCXML specification section "C.1.1 _ioprocessors Value" states that the SCXMLEventProcessor <em>must</em> |
| * maintain a 'location' field inside its entry in the _ioprocessors environment variable. |
| * @return the 'location' of the SCXMLEventProcessor |
| */ |
| public String getLocation() { |
| return null; |
| } |
| |
| /** |
| * @return Returns the SCInstance |
| */ |
| public SCInstance getScInstance() { |
| return scInstance; |
| } |
| |
| /** |
| * @return Returns The evaluator. |
| */ |
| public Evaluator getEvaluator() { |
| return evaluator; |
| } |
| |
| /** |
| * Set or replace the evaluator |
| * <p> |
| * If the state machine instance has been initialized before, it will be initialized again, destroying all existing |
| * state! |
| * </p> |
| * @param evaluator The evaluator to set |
| * @throws ModelException if attempting to set a null value or the state machine instance failed to re-initialize |
| */ |
| protected void setEvaluator(Evaluator evaluator) throws ModelException { |
| scInstance.setEvaluator(evaluator, false); |
| // synchronize possible derived evaluator |
| this.evaluator = scInstance.getEvaluator(); |
| initializeIOProcessors(); |
| } |
| |
| /** |
| * @return Returns the error reporter |
| */ |
| public ErrorReporter getErrorReporter() { |
| return errorReporter; |
| } |
| |
| /** |
| * Set or replace the error reporter |
| * |
| * @param errorReporter The error reporter to set, if null a SimpleErrorReporter instance will be used instead |
| */ |
| protected void setErrorReporter(ErrorReporter errorReporter) { |
| this.errorReporter = errorReporter != null ? errorReporter : new SimpleErrorReporter(); |
| try { |
| scInstance.setErrorReporter(errorReporter); |
| } |
| catch (ModelException me) { |
| // won't happen |
| } |
| } |
| |
| /** |
| * @return Returns the event dispatcher |
| */ |
| public EventDispatcher getEventDispatcher() { |
| return eventdispatcher; |
| } |
| |
| /** |
| * Set or replace the event dispatch |
| * |
| * @param eventdispatcher The event dispatcher to set, if null a SimpleDispatcher instance will be used instead |
| */ |
| protected void setEventdispatcher(EventDispatcher eventdispatcher) { |
| this.eventdispatcher = eventdispatcher != null ? eventdispatcher : new SimpleDispatcher(); |
| } |
| |
| /** |
| * @return Returns the notification registry |
| */ |
| public NotificationRegistry getNotificationRegistry() { |
| return notificationRegistry; |
| } |
| |
| /** |
| * Initialize the _ioprocessors environment variable, which only can be done when the evaluator is available |
| */ |
| protected void initializeIOProcessors() { |
| if (scInstance.getEvaluator() != null) { |
| // lazy register/reset #_scxml_sessionId event target |
| String currentSessionId = (String)getScInstance().getSystemContext().get(SCXMLSystemContext.SESSIONID_KEY); |
| if (sessionId != null && !sessionId.equals(currentSessionId)) { |
| // remove possible old/stale #_scxml_sessionId target |
| ioProcessors.remove(SCXMLIOProcessor.SCXML_SESSION_EVENT_PROCESSOR_PREFIX+sessionId); |
| } |
| sessionId = currentSessionId; |
| if (!ioProcessors.containsKey(SCXMLIOProcessor.SCXML_SESSION_EVENT_PROCESSOR_PREFIX+sessionId)) { |
| ioProcessors.put(SCXMLIOProcessor.SCXML_SESSION_EVENT_PROCESSOR_PREFIX+sessionId, getExternalIOProcessor()); |
| } |
| getScInstance().getSystemContext().setLocal(SCXMLSystemContext.IOPROCESSORS_KEY, Collections.unmodifiableMap(ioProcessors)); |
| } |
| } |
| |
| /** |
| * Detach the current SCInstance to allow external serialization. |
| * <p> |
| * {@link #attachInstance(SCInstance)} can be used to re-attach a previously detached instance |
| * </p> |
| * @return the detached instance |
| */ |
| protected SCInstance detachInstance() { |
| SCInstance instance = scInstance; |
| scInstance.detach(); |
| Map<String, Object> systemVars = scInstance.getSystemContext().getVars(); |
| systemVars.remove(SCXMLSystemContext.IOPROCESSORS_KEY); |
| systemVars.remove(SCXMLSystemContext.EVENT_KEY); |
| scInstance = null; |
| return instance; |
| } |
| |
| /** |
| * Re-attach a previously detached SCInstance. |
| * <p> |
| * Note: an already attached instance will get overwritten (and thus lost). |
| * </p> |
| * @param instance An previously detached SCInstance |
| */ |
| protected void attachInstance(SCInstance instance) { |
| if (scInstance != null ) { |
| scInstance.detach(); |
| } |
| scInstance = instance; |
| if (scInstance != null) { |
| scInstance.detach(); |
| try { |
| scInstance.setInternalIOProcessor(this); |
| scInstance.setEvaluator(evaluator, true); |
| scInstance.setErrorReporter(errorReporter); |
| initializeIOProcessors(); |
| } |
| catch (ModelException me) { |
| // should not happen |
| } |
| } |
| } |
| |
| /** |
| * Register an Invoker for this target type. |
| * |
| * @param type The target type (specified by "type" attribute of the invoke element). |
| * @param invokerClass The Invoker class. |
| */ |
| protected void registerInvokerClass(final String type, final Class<? extends Invoker> invokerClass) { |
| invokerClasses.put(type, invokerClass); |
| } |
| |
| /** |
| * Remove the Invoker registered for this target type (if there is one registered). |
| * |
| * @param type The target type (specified by "type" attribute of the invoke element). |
| */ |
| protected void unregisterInvokerClass(final String type) { |
| invokerClasses.remove(type); |
| } |
| |
| /** |
| * Create a new {@link Invoker} |
| * |
| * @param type The type of the target being invoked. |
| * @return An {@link Invoker} for the specified type, if an |
| * invoker class is registered against that type, |
| * <code>null</code> otherwise. |
| * @throws InvokerException When a suitable {@link Invoker} cannot be instantiated. |
| */ |
| public Invoker newInvoker(final String type) throws InvokerException { |
| Class<? extends Invoker> invokerClass = invokerClasses.get(type); |
| if (invokerClass == null) { |
| throw new InvokerException("No Invoker registered for type \"" + type + "\""); |
| } |
| try { |
| return invokerClass.newInstance(); |
| } catch (InstantiationException ie) { |
| throw new InvokerException(ie.getMessage(), ie.getCause()); |
| } catch (IllegalAccessException iae) { |
| throw new InvokerException(iae.getMessage(), iae.getCause()); |
| } |
| } |
| |
| /** |
| * Get the {@link Invoker} for this {@link Invoke}. |
| * May return <code>null</code>. A non-null {@link Invoker} will be |
| * returned if and only if the {@link Invoke} parent TransitionalState is |
| * currently active and contains the <invoke> child. |
| * |
| * @param invoke The <code>Invoke</code>. |
| * @return The Invoker. |
| */ |
| public Invoker getInvoker(final Invoke invoke) { |
| return invokers.get(invokeIds.get(invoke)); |
| } |
| |
| /** |
| * Register the active {@link Invoker} for a {@link Invoke} |
| * |
| * @param invoke The Invoke. |
| * @param invoker The Invoker. |
| * @throws InvokerException when the Invoker doesn't have an invokerId |
| */ |
| public void registerInvoker(final Invoke invoke, final Invoker invoker) throws InvokerException { |
| String invokeId = invoker.getInvokeId(); |
| if (invokeId == null) { |
| throw new InvokerException("Registering an Invoker without invokerId"); |
| } |
| invokeIds.put(invoke, invokeId); |
| invokers.put(invokeId, invoker); |
| ioProcessors.put(SCXMLIOProcessor.EVENT_PROCESSOR_ALIAS_PREFIX+invoke.getId(), invoker.getChildIOProcessor()); |
| initializeIOProcessors(); |
| } |
| |
| /** |
| * Remove a previously active Invoker, which must already have been canceled |
| * @param invoke The Invoke for the Invoker to remove |
| */ |
| public void removeInvoker(final Invoke invoke) { |
| invokers.remove(invokeIds.remove(invoke)); |
| ioProcessors.remove(SCXMLIOProcessor.EVENT_PROCESSOR_ALIAS_PREFIX+invoke.getId()); |
| initializeIOProcessors(); |
| } |
| |
| /** |
| * @return Returns the map of current active Invokes and their invokeId |
| */ |
| public Map<Invoke, String> getInvokeIds() { |
| return invokeIds; |
| } |
| |
| |
| /** |
| * Cancel and remove an active Invoker |
| * |
| * @param invoke The Invoke for the Invoker to cancel |
| */ |
| public void cancelInvoker(Invoke invoke) { |
| String invokeId = invokeIds.get(invoke); |
| if (invokeId != null) { |
| try { |
| invokers.get(invokeId).cancel(); |
| } catch (InvokerException ie) { |
| TriggerEvent te = new TriggerEvent("failed.invoke.cancel."+invokeId, TriggerEvent.ERROR_EVENT); |
| addEvent(te); |
| } |
| removeInvoker(invoke); |
| } |
| } |
| |
| /** |
| * Add an event to the internal event queue |
| * @param event The event |
| */ |
| @Override |
| public void addEvent(TriggerEvent event) { |
| internalEventQueue.add(event); |
| } |
| |
| /** |
| * @return Returns the next event from the internal event queue, if available |
| */ |
| public TriggerEvent nextInternalEvent() { |
| return internalEventQueue.poll(); |
| } |
| |
| /** |
| * @return Returns true if the internal event queue isn't empty |
| */ |
| public boolean hasPendingInternalEvent() { |
| return !internalEventQueue.isEmpty(); |
| } |
| } |