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");
}
};