All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.webflow.engine.impl.FlowExecutionImpl Maven / Gradle / Ivy

There is a newer version: 1.0.6
Show newest version
/*
 * Copyright 2002-2006 the original author or authors.
 *
 * Licensed 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.springframework.webflow.engine.impl;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.LinkedList;
import java.util.ListIterator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.webflow.context.ExternalContext;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.core.collection.CollectionUtils;
import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.RequestControlContext;
import org.springframework.webflow.engine.State;
import org.springframework.webflow.engine.ViewState;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.FlowExecutionListener;
import org.springframework.webflow.execution.FlowSession;
import org.springframework.webflow.execution.FlowSessionStatus;
import org.springframework.webflow.execution.ViewSelection;

/**
 * Default implementation of FlowExecution that uses a stack-based data
 * structure to manage spawned flow sessions. This class is closely coupled with
 * package-private FlowSessionImpl and
 * RequestControlContextImpl. The three classes work together to
 * form a complete flow execution implementation based on a finite state
 * machine.
 * 

* This implementation of FlowExecution is serializable so it can be safely * stored in an HTTP session or other persistent store such as a file, database, * or client-side form field. Once deserialized, the * {@link FlowExecutionImplStateRestorer} strategy is expected to be used to * restore the execution to a usable state. * * @see FlowExecutionImplFactory * @see FlowExecutionImplStateRestorer * * @author Keith Donald * @author Erwin Vervaet * @author Ben Hale */ public class FlowExecutionImpl implements FlowExecution, Externalizable { private static final Log logger = LogFactory.getLog(FlowExecutionImpl.class); /** * The execution's root flow; the top level flow that acts as the starting * point for this flow execution. *

* Transient to support restoration by the {@link FlowExecutionImplStateRestorer}. */ private transient Flow flow; /** * The stack of active, currently executing flow sessions. As subflows are * spawned, they are pushed onto the stack. As they end, they are popped off * the stack. */ private LinkedList flowSessions; /** * A thread-safe listener list, holding listeners monitoring the lifecycle * of this flow execution. *

* Transient to support restoration by the {@link FlowExecutionImplStateRestorer}. */ private transient FlowExecutionListeners listeners; /** * A data structure for attributes shared by all flow sessions. *

* Transient to support restoration by the {@link FlowExecutionImplStateRestorer}. */ private transient MutableAttributeMap conversationScope; /** * A data structure for runtime system execution attributes. *

* Transient to support restoration by the {@link FlowExecutionImplStateRestorer}. */ private transient AttributeMap attributes; /** * Set so the transient {@link #flow} field can be restored by the * {@link FlowExecutionImplStateRestorer}. */ private String flowId; /** * Default constructor required for externalizable serialization. Should NOT * be called programmatically. */ public FlowExecutionImpl() { } /** * Create a new flow execution executing the provided flow. This constructor * is mainly used for testing. * @param flow the root flow of this flow execution */ public FlowExecutionImpl(Flow flow) { this(flow, new FlowExecutionListener[0], null); } /** * Create a new flow execution executing the provided flow. * @param flow the root flow of this flow execution * @param listeners the listeners interested in flow execution lifecycle * events * @param attributes flow execution system attributes */ public FlowExecutionImpl(Flow flow, FlowExecutionListener[] listeners, AttributeMap attributes) { setFlow(flow); this.flowSessions = new LinkedList(); this.listeners = new FlowExecutionListeners(listeners); this.attributes = (attributes != null ? attributes : CollectionUtils.EMPTY_ATTRIBUTE_MAP); this.conversationScope = new LocalAttributeMap(); if (logger.isDebugEnabled()) { logger.debug("Created new execution of flow '" + flow.getId() + "'"); } } public String getCaption() { return "execution of '" + flowId + "'"; } // implementing FlowExecutionContext public FlowDefinition getDefinition() { return flow; } public boolean isActive() { return !flowSessions.isEmpty(); } public FlowSession getActiveSession() { return getActiveSessionInternal(); } public MutableAttributeMap getConversationScope() { return conversationScope; } public AttributeMap getAttributes() { return attributes; } // methods implementing FlowExecution public ViewSelection start(MutableAttributeMap input, ExternalContext externalContext) throws FlowExecutionException { Assert.state(!isActive(), "This flow is already executing -- you cannot call 'start()' more than once"); RequestControlContext context = createControlContext(externalContext); getListeners().fireRequestSubmitted(context); try { try { // launch a flow session for the root flow ViewSelection selectedView = context.start(flow, input); return pause(context, selectedView); } catch (FlowExecutionException e) { return pause(context, handleException(e, context)); } } finally { getListeners().fireRequestProcessed(context); } } public ViewSelection signalEvent(String eventId, ExternalContext externalContext) throws FlowExecutionException { assertActive(); if (logger.isDebugEnabled()) { logger.debug("Resuming this execution on user event '" + eventId + "'"); } RequestControlContext context = createControlContext(externalContext); context.getFlashScope().clear(); getListeners().fireRequestSubmitted(context); try { try { resume(context); Event event = new Event(externalContext, eventId, externalContext.getRequestParameterMap().asAttributeMap()); ViewSelection selectedView = context.signalEvent(event); return pause(context, selectedView); } catch (FlowExecutionException e) { return pause(context, handleException(e, context)); } } finally { getListeners().fireRequestProcessed(context); } } public ViewSelection refresh(ExternalContext externalContext) throws FlowExecutionException { assertActive(); if (logger.isDebugEnabled()) { logger.debug("Resuming this execution for refresh"); } RequestControlContext context = createControlContext(externalContext); getListeners().fireRequestSubmitted(context); try { try { resume(context); State currentState = getCurrentState(); if (!(currentState instanceof ViewState)) { throw new IllegalStateException("Current state is not a view state - cannot refresh; " + "perhaps an unhandled exception occured in another state?"); } ViewSelection selectedView = ((ViewState)currentState).refresh(context); return pause(context, selectedView); } catch (FlowExecutionException e) { return pause(context, handleException(e, context)); } } finally { getListeners().fireRequestProcessed(context); } } /** * Returns the listener list. * @return the attached execution listeners. */ FlowExecutionListeners getListeners() { return listeners; } /** * Resume this flow execution. * @param context the state request context */ protected void resume(RequestControlContext context) { getActiveSessionInternal().setStatus(FlowSessionStatus.ACTIVE); getListeners().fireResumed(context); } /** * Pause this flow execution. * @param context the request control context * @param selectedView the initial selected view to render * @return the selected view to render */ protected ViewSelection pause(RequestControlContext context, ViewSelection selectedView) { if (!isActive()) { // view selected by an end state return selectedView; } getActiveSessionInternal().setStatus(FlowSessionStatus.PAUSED); getListeners().firePaused(context, selectedView); if (logger.isDebugEnabled()) { if (selectedView != null) { logger.debug("Paused to render " + selectedView + " and wait for user input"); } else { logger.debug("Paused to wait for user input"); } } return selectedView; } /** * Handles an exception that occured performing an operation on this flow * execution. First trys the set of exception handlers associated with the * offending state, then the handlers at the flow level. * @param exception the exception that occured * @param context the request control context the exception occured in * @return the selected error view, never null * @throws FlowExecutionException rethrows the exception if it was not handled * at the state or flow level */ protected ViewSelection handleException(FlowExecutionException exception, RequestControlContext context) throws FlowExecutionException { getListeners().fireExceptionThrown(context, exception); if (logger.isDebugEnabled()) { logger.debug("Attempting to handle [" + exception + "]"); } // the state could be null if the flow was attempting a start operation ViewSelection selectedView = tryStateHandlers(exception, context); if (selectedView != null) { return selectedView; } selectedView = tryFlowHandlers(exception, context); if (selectedView != null) { return selectedView; } if (logger.isDebugEnabled()) { logger.debug("Rethrowing unhandled flow execution exception"); } throw exception; } /** * Try to handle given exception using execution exception handlers registered * at the state level. Returns null if no handler handled the exception. */ private ViewSelection tryStateHandlers(FlowExecutionException exception, RequestControlContext context) { ViewSelection selectedView = null; if (exception.getStateId() != null) { selectedView = getActiveFlow().getStateInstance(exception.getStateId()).handleException(exception, context); if (selectedView != null) { if (logger.isDebugEnabled()) { logger.debug("State '" + exception.getStateId() + "' handled exception"); } } } return selectedView; } /** * Try to handle given exception using execution exception handlers registered * at the flow level. Returns null if no handler handled the exception. */ private ViewSelection tryFlowHandlers(FlowExecutionException exception, RequestControlContext context) { ViewSelection selectedView = getActiveFlow().handleException(exception, context); if (selectedView != null) { if (logger.isDebugEnabled()) { logger.debug("Flow '" + exception.getFlowId() + "' handled exception"); } } return selectedView; } // internal helpers /** * Create a flow execution control context. * @param externalContext the external context triggering this request */ protected RequestControlContext createControlContext(ExternalContext externalContext) { return new RequestControlContextImpl(this, externalContext); } /** * Returns the currently active flow session. * @throws IllegalStateException this execution is not active */ FlowSessionImpl getActiveSessionInternal() throws IllegalStateException { assertActive(); return (FlowSessionImpl)flowSessions.getLast(); } /** * Set the state that is currently active in this flow execution. * @param newState the new current state */ protected void setCurrentState(State newState) { getActiveSessionInternal().setState(newState); } /** * Activate a new FlowSession for the flow definition. * Pushes the new flow session onto the stack. * @param flow the flow definition * @return the new flow session */ protected FlowSession activateSession(Flow flow) { FlowSessionImpl session; if (!flowSessions.isEmpty()) { FlowSessionImpl parent = getActiveSessionInternal(); parent.setStatus(FlowSessionStatus.SUSPENDED); session = createFlowSession(flow, parent); } else { session = createFlowSession(flow, null); } flowSessions.add(session); session.setStatus(FlowSessionStatus.STARTING); if (logger.isDebugEnabled()) { logger.debug("Starting " + session); } return session; } /** * Create a new flow session object. Subclasses can override this to return * a special implementation if required. * @param flow the flow that should be associated with the flow session * @param parent the flow session that should be the parent of the newly * created flow session (may be null) * @return the newly created flow session */ FlowSessionImpl createFlowSession(Flow flow, FlowSessionImpl parent) { return new FlowSessionImpl(flow, parent); } /** * End the active flow session, popping it of the stack. * @return the ended session */ public FlowSession endActiveFlowSession() { FlowSessionImpl endingSession = (FlowSessionImpl)flowSessions.removeLast(); endingSession.setStatus(FlowSessionStatus.ENDED); if (!flowSessions.isEmpty()) { if (logger.isDebugEnabled()) { logger.debug("Resuming session '" + getActiveSessionInternal().getDefinition().getId() + "' in state '" + getActiveSessionInternal().getState().getId() + "'"); } getActiveSessionInternal().setStatus(FlowSessionStatus.ACTIVE); } else { if (logger.isDebugEnabled()) { logger.debug("[Ended] - this execution is now inactive"); } } return endingSession; } /** * Make sure that this flow execution is active and throw an exception if it's * not. */ private void assertActive() throws IllegalStateException { if (!isActive()) { throw new IllegalStateException( "This flow execution is not active, it has either ended or has never been started."); } } /** * Returns the currently active flow. */ private Flow getActiveFlow() { return (Flow)getActiveSessionInternal().getDefinition(); } /** * Returns the current state of this flow execution. */ private State getCurrentState() { return (State)getActiveSessionInternal().getState(); } // custom serialization (implementation of Externalizable for optimized // storage) public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { flowId = (String)in.readObject(); flowSessions = (LinkedList)in.readObject(); } public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(flowId); out.writeObject(flowSessions); } public String toString() { if (!isActive()) { return "[Inactive " + getCaption() + "]"; } else { if (flow != null) { return new ToStringCreator(this).append("flow", flow.getId()).append("flowSessions", flowSessions) .toString(); } else { return "[Unhydrated " + getCaption() + "]"; } } } // package private setters for restoring transient state // used by FlowExecutionImplStateRestorer /** * Restore the flow definition of this flow execution. */ void setFlow(Flow flow) { Assert.notNull(flow, "The root flow definition is required"); this.flow = flow; this.flowId = flow.getId(); } /** * Restore the listeners of this flow execution. */ void setListeners(FlowExecutionListeners listeners) { Assert.notNull(listeners, "The execution listener list is required"); this.listeners = listeners; } /** * Restore the execution attributes of this flow execution. */ void setAttributes(AttributeMap attributes) { Assert.notNull(conversationScope, "The execution attribute map is required"); this.attributes = attributes; } /** * Restore conversation scope for this flow execution. */ void setConversationScope(MutableAttributeMap conversationScope) { Assert.notNull(conversationScope, "The conversation scope map is required"); this.conversationScope = conversationScope; } /** * Returns the flow definition id of this flow execution. */ String getFlowId() { return flowId; } /** * Returns the list of flow session maintained by this flow execution. */ LinkedList getFlowSessions() { return flowSessions; } /** * Are there any flow sessions in this flow execution? */ boolean hasSessions() { return !flowSessions.isEmpty(); } /** * Are there any sessions for sub flows in this flow execution? */ boolean hasSubflowSessions() { return flowSessions.size() > 1; } /** * Returns the flow session for the root flow of this flow execution. */ FlowSessionImpl getRootSession() { return (FlowSessionImpl)flowSessions.getFirst(); } /** * Returns an iterator looping over the subflow sessions * in this flow execution. */ ListIterator getSubflowSessionIterator() { return flowSessions.listIterator(1); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy