diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java index 13aab825..bf08c425 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java @@ -84,9 +84,9 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { private transient Flow flow; /** - * A flag indicating if this execution has started. + * A enum tracking the status of this flow execution. */ - private boolean started; + private FlowExecutionStatus status; /** * The stack of active, currently executing flow sessions. As subflows are spawned, they are pushed onto the stack. @@ -144,11 +144,12 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { public FlowExecutionImpl(Flow flow) { Assert.notNull(flow, "The flow definition is required"); this.flow = flow; - this.listeners = new FlowExecutionListeners(); - this.attributes = CollectionUtils.EMPTY_ATTRIBUTE_MAP; - this.flowSessions = new LinkedList(); - this.conversationScope = new LocalAttributeMap(); - this.conversationScope.put(FLASH_SCOPE_ATTRIBUTE, new LocalAttributeMap()); + status = FlowExecutionStatus.NOT_STARTED; + listeners = new FlowExecutionListeners(); + attributes = CollectionUtils.EMPTY_ATTRIBUTE_MAP; + flowSessions = new LinkedList(); + conversationScope = new LocalAttributeMap(); + conversationScope.put(FLASH_SCOPE_ATTRIBUTE, new LocalAttributeMap()); } public String getCaption() { @@ -166,7 +167,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { } public boolean hasStarted() { - return started; + return status != FlowExecutionStatus.NOT_STARTED; } public boolean isActive() { @@ -174,7 +175,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { } public boolean hasEnded() { - return hasStarted() && !isActive(); + return status == FlowExecutionStatus.ENDED; } public FlowExecutionOutcome getOutcome() { @@ -182,12 +183,11 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { } public FlowSession getActiveSession() { - if (!isActive()) { - if (started) { - throw new IllegalStateException("No active session to access; this flow execution has ended"); - } else { - throw new IllegalStateException("No active session to access; this flow execution has not been started"); - } + if (status == FlowExecutionStatus.NOT_STARTED) { + throw new IllegalStateException("No active session to access; this flow execution has not been started"); + } + if (status == FlowExecutionStatus.ENDED) { + throw new IllegalStateException("No active session to access; this flow execution has ended"); } return getActiveSessionInternal(); } @@ -208,11 +208,11 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { public void start(MutableAttributeMap input, ExternalContext externalContext) throws FlowExecutionException, IllegalStateException { - Assert.state(!started, "This flow has already been started; you cannot call 'start()' more than once"); + Assert.state(!hasStarted(), "This flow has already been started; you cannot call 'start()' more than once"); if (logger.isDebugEnabled()) { logger.debug("Starting in " + externalContext + " with input " + input); } - started = true; + status = FlowExecutionStatus.STARTING; MessageContext messageContext = createMessageContext(null); RequestControlContext requestContext = createRequestContext(externalContext, messageContext); RequestContextHolder.setRequestContext(requestContext); @@ -242,13 +242,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { } public void resume(ExternalContext externalContext) throws FlowExecutionException, IllegalStateException { - if (!isActive()) { - if (started) { - throw new IllegalStateException("This flow execution cannot be resumed; it has ended"); - } else { - throw new IllegalStateException("This flow execution cannot be resumed; it has not been started"); - } - } + Assert.state(status == FlowExecutionStatus.STARTED, "This flow execution cannot be resumed; it is not active"); if (logger.isDebugEnabled()) { logger.debug("Resuming in " + externalContext); } @@ -291,11 +285,11 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { */ public void setCurrentState(String stateId) { FlowSessionImpl session; - if (started) { - session = getActiveSessionInternal(); - } else { + if (status == FlowExecutionStatus.NOT_STARTED) { session = activateSession(flow); - started = true; + status = FlowExecutionStatus.STARTED; + } else { + session = getActiveSessionInternal(); } State state = session.getFlow().getStateInstance(stateId); session.setCurrentState(state); @@ -341,6 +335,9 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { void start(Flow flow, MutableAttributeMap input, RequestControlContext context) { listeners.fireSessionCreating(context, flow); FlowSession session = activateSession(flow); + if (session.isRoot()) { + status = FlowExecutionStatus.STARTED; + } if (input == null) { input = new LocalAttributeMap(); } @@ -379,13 +376,17 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { void endActiveFlowSession(String outcome, MutableAttributeMap output, RequestControlContext context) { FlowSessionImpl session = getActiveSessionInternal(); + if (session.isRoot()) { + status = FlowExecutionStatus.ENDING; + } listeners.fireSessionEnding(context, session, outcome, output); session.getFlow().end(context, outcome, output); flowSessions.removeLast(); - boolean executionEnded = hasEnded(); + boolean executionEnded = flowSessions.isEmpty(); if (executionEnded) { // set the root flow execution outcome for external clients to use this.outcome = new FlowExecutionOutcome(outcome, output); + status = FlowExecutionStatus.ENDED; } listeners.fireSessionEnded(context, session, outcome, output); if (!executionEnded) { @@ -418,6 +419,9 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { TransitionDefinition getMatchingTransition(String eventId) { FlowSessionImpl session = getActiveSessionInternal(); + if (session == null) { + return null; + } TransitionableState currentState = (TransitionableState) session.getState(); TransitionDefinition transition = currentState.getTransition(eventId); if (transition == null) { @@ -509,12 +513,12 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { // custom serialization (implementation of Externalizable for optimized storage) public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - started = in.readBoolean(); + status = (FlowExecutionStatus) in.readObject(); flowSessions = (LinkedList) in.readObject(); } public void writeExternal(ObjectOutput out) throws IOException { - out.writeBoolean(started); + out.writeObject(status); out.writeObject(flowSessions); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionStatus.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionStatus.java new file mode 100644 index 00000000..b274b5a3 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionStatus.java @@ -0,0 +1,41 @@ +package org.springframework.webflow.engine.impl; + +import org.springframework.core.enums.StaticLabeledEnum; + +/** + * A enum used internally by {@link FlowExecutionImpl} to track its status. Flow Executions initially start out in + * NOT_STARTED. After start is called, they are STARTING. Once the root session is activated, they have STARTED. When + * the execution begins to end, their status is ENDING. After ending, their status is updated to ENDED. + */ +public class FlowExecutionStatus extends StaticLabeledEnum { + + /** + * The flow execution has not yet started. + */ + public static final FlowExecutionStatus NOT_STARTED = new FlowExecutionStatus(0, "Not Started"); + + /** + * The flow execution is in the process of starting; the root session has not yet been activated. + */ + public static final FlowExecutionStatus STARTING = new FlowExecutionStatus(1, "Starting"); + + /** + * The flow execution has started and a session is active. + */ + public static final FlowExecutionStatus STARTED = new FlowExecutionStatus(2, "Started"); + + /** + * The flow execution is ending. + */ + public static final FlowExecutionStatus ENDING = new FlowExecutionStatus(3, "Ending"); + + /** + * The flow execution has ended. + */ + public static final FlowExecutionStatus ENDED = new FlowExecutionStatus(4, "Ended"); + + private FlowExecutionStatus(int code, String label) { + super(code, label); + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionContext.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionContext.java index 5a5f43e9..181f26e2 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionContext.java @@ -66,11 +66,9 @@ public interface FlowExecutionContext { public boolean hasStarted(); /** - * Is the flow execution active? - *

- * All methods on an active flow execution context can be called successfully. If the flow execution is not active, - * a caller cannot access some methods such as {@link #getActiveSession()}. - * @return true if active, false if the flow execution has terminated + * Is the flow execution active? A flow execution is active once it has started and remains active until it has + * ended. + * @return true if active, false if the flow execution has terminated or has not yet started */ public boolean isActive(); @@ -91,11 +89,10 @@ public interface FlowExecutionContext { /** * Returns the active flow session of this flow execution. The active flow session is the currently executing - * session -- it may be the "root flow" session, or it may be a subflow session if this flow execution has spawned a - * subflow. + * session. It may be the "root flow" session, or it may be a subflow session if this flow execution has spawned a + * subflow. Returns null of this flow execution is in the process of starting or has ended. * @return the active flow session - * @throws IllegalStateException if this flow execution has not been started at all or if this execution has ended - * and is no longer actively executing + * @throws IllegalStateException if this flow execution has not been started at all */ public FlowSession getActiveSession() throws IllegalStateException; diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java index dcac5b09..a492a7e6 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java @@ -58,19 +58,18 @@ import org.springframework.webflow.definition.TransitionDefinition; public interface RequestContext { /** - * Returns the definition of the flow that is currently executing. + * Returns the definition of the flow that is currently executing. Returns null when the flow execution + * is in the process of starting and has not yet activated its root flow session. * @return the flow definition for the active session - * @throws IllegalStateException if the flow execution has not been started at all, or if the execution has ended - * and is no longer actively executing + * @throws IllegalStateException if the flow execution has not been started at all or has ended */ public FlowDefinition getActiveFlow() throws IllegalStateException; /** - * Returns the current state of the executing flow. May return null if this flow execution is in the + * Returns the current state of the executiong flow. Returns null if this flow execution is in the * process of starting and has not yet entered its start state. * @return the current state, or null if in the process of starting - * @throws IllegalStateException if this flow execution has not been started at all, or if this execution has ended - * and is no longer actively executing + * @throws IllegalStateException if this flow execution has not been started at all or has ended */ public StateDefinition getCurrentState() throws IllegalStateException; @@ -78,8 +77,7 @@ public interface RequestContext { * Returns the transition that would execute on the occurrence of the given event. * @param eventId the id of the user event * @return the transition that would trigger, or null if no transition matches - * @throws IllegalStateException if this flow execution has not been started at all, or if this execution has ended - * and is no longer actively executing + * @throws IllegalStateException if this flow execution has not been started at all or has ended */ public TransitionDefinition getMatchingTransition(String eventId) throws IllegalStateException; diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java index 3b95467b..74f712de 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java @@ -18,6 +18,7 @@ package org.springframework.webflow.engine.impl; import junit.framework.TestCase; import org.springframework.webflow.core.collection.MutableAttributeMap; +import org.springframework.webflow.definition.FlowDefinition; import org.springframework.webflow.engine.EndState; import org.springframework.webflow.engine.Flow; import org.springframework.webflow.engine.RequestControlContext; @@ -58,6 +59,13 @@ public class FlowExecutionImplTests extends TestCase { execution.start(null, context); assertTrue(execution.hasStarted()); assertFalse(execution.isActive()); + assertTrue(execution.hasEnded()); + try { + execution.getActiveSession(); + fail("should have failed"); + } catch (IllegalStateException e) { + + } assertEquals(1, mockListener.getRequestsSubmittedCount()); assertEquals(1, mockListener.getRequestsProcessedCount()); assertEquals(1, mockListener.getSessionCreatingCount()); @@ -118,7 +126,14 @@ public class FlowExecutionImplTests extends TestCase { Flow flow = new Flow("flow"); new EndState(flow, "end"); FlowExecutionListener mockListener = new FlowExecutionListenerAdapter() { - public void sessionStarting(RequestContext context, FlowSession session, MutableAttributeMap input) { + public void sessionCreating(RequestContext context, FlowDefinition definition) { + assertNull(context.getCurrentState()); + assertNull(context.getActiveFlow()); + assertNull(context.getCurrentEvent()); + assertNull(context.getCurrentTransition()); + assertNull(context.getActiveFlow()); + assertNull(context.getFlowExecutionContext().getActiveSession()); + assertFalse(context.getFlowExecutionContext().isActive()); throw new IllegalStateException("Oops"); } };