diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowExecutionExceptionHandler.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowExecutionExceptionHandler.java
index e4121233..36e2493d 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowExecutionExceptionHandler.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowExecutionExceptionHandler.java
@@ -19,16 +19,16 @@ import org.springframework.webflow.engine.support.TransitionExecutingFlowExecuti
import org.springframework.webflow.execution.FlowExecutionException;
/**
- * A strategy for handling an exception that occurs at runtime during the execution of a flow definition.
+ * A strategy for handling an exception that occurs at runtime during an active flow execution.
*
* Note: special care should be taken when implementing custom flow execution exception handlers. Exception handlers are
* like Transitions in that they direct flow control when exceptions occur. They are more complex than Actions, which
* can only execute behaviors and return results that drive flow control. For this reason, if implemented incorrectly, a
* FlowExecutionHandler can leave a flow execution in an invalid state, which can render the flow execution unusable or
- * its future use undefined. For example, if an exception thrown at flow startup gets routed to an exception handler,
- * the handler must take responsibility for ensuring the flow execution returns control to the caller in a consistent
- * state. Concretely, this means the exception handler must transition the flow to its start state. The handler should
- * not simply return leaving the flow with no current state set.
+ * its future use undefined. For example, if an exception thrown at flow session startup gets routed to an exception
+ * handler, the handler must take responsibility for ensuring the flow execution returns control to the caller in a
+ * consistent state. Concretely, this means the exception handler must transition the flow to its start state. The
+ * handler should not simply return leaving the flow with no current state set.
*
* Note: Because flow execution handlers are more difficult to implement correctly, consider catching exceptions in your
* web flow action code and returning result events that drive standard transitions. Alternatively, consider use of the
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 499717fb..c20a2192 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
@@ -167,11 +167,11 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
}
public boolean hasStarted() {
- return status != FlowExecutionStatus.NOT_STARTED;
+ return status == FlowExecutionStatus.ACTIVE || status == FlowExecutionStatus.ENDED;
}
public boolean isActive() {
- return !flowSessions.isEmpty();
+ return status == FlowExecutionStatus.ACTIVE;
}
public boolean hasEnded() {
@@ -183,11 +183,13 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
}
public FlowSession getActiveSession() {
- 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");
+ if (!isActive()) {
+ if (status == FlowExecutionStatus.NOT_STARTED) {
+ throw new IllegalStateException(
+ "No active FlowSession to access; this flow execution has not been started");
+ } else {
+ throw new IllegalStateException("No active FlowSession to access; this flow execution has ended");
+ }
}
return getActiveSessionInternal();
}
@@ -212,7 +214,6 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
if (logger.isDebugEnabled()) {
logger.debug("Starting in " + externalContext + " with input " + input);
}
- status = FlowExecutionStatus.STARTING;
MessageContext messageContext = createMessageContext(null);
RequestControlContext requestContext = createRequestContext(externalContext, messageContext);
RequestContextHolder.setRequestContext(requestContext);
@@ -224,7 +225,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
} catch (Exception e) {
handleException(wrap(e), requestContext);
} finally {
- if (!hasEnded()) {
+ if (isActive()) {
saveMessages(requestContext);
try {
listeners.firePaused(requestContext);
@@ -242,8 +243,9 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
}
public void resume(ExternalContext externalContext) throws FlowExecutionException, IllegalStateException {
- Assert.state(status == FlowExecutionStatus.STARTED,
- "This flow execution cannot be resumed; it is not started or has ended");
+ Assert
+ .state(status == FlowExecutionStatus.ACTIVE,
+ "This FlowExecution cannot be resumed because it is not active; it has either not been started or has ended");
if (logger.isDebugEnabled()) {
logger.debug("Resuming in " + externalContext);
}
@@ -260,7 +262,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
} catch (Exception e) {
handleException(wrap(e), requestContext);
} finally {
- if (!hasEnded()) {
+ if (isActive()) {
saveMessages(requestContext);
try {
listeners.firePaused(requestContext);
@@ -288,7 +290,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
FlowSessionImpl session;
if (status == FlowExecutionStatus.NOT_STARTED) {
session = activateSession(flow);
- status = FlowExecutionStatus.STARTED;
+ status = FlowExecutionStatus.ACTIVE;
} else {
session = getActiveSessionInternal();
}
@@ -296,19 +298,33 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
session.setCurrentState(state);
}
- private MessageContext createMessageContext(MessageSource messageSource) {
- StateManageableMessageContext messageContext = new DefaultMessageContext(messageSource);
- Serializable messagesMemento = (Serializable) getFlashScope().extract("messagesMemento");
- if (messagesMemento != null) {
- messageContext.restoreMessages(messagesMemento);
- }
- return messageContext;
+ // custom serialization (implementation of Externalizable for optimized storage)
+
+ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ status = (FlowExecutionStatus) in.readObject();
+ flowSessions = (LinkedList) in.readObject();
}
- private void saveMessages(RequestContext context) {
- StateManageableMessageContext messageContext = (StateManageableMessageContext) context.getMessageContext();
- Serializable messagesMemento = messageContext.createMessagesMemento();
- getFlashScope().put("messagesMemento", messagesMemento);
+ public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeObject(status);
+ out.writeObject(flowSessions);
+ }
+
+ public String toString() {
+ if (!isActive()) {
+ if (!hasStarted()) {
+ return "[Not yet started " + getCaption() + "]";
+ } else {
+ return "[Ended " + getCaption() + "]";
+ }
+ } else {
+ if (flow != null) {
+ return new ToStringCreator(this).append("flow", flow.getId()).append("flowSessions", flowSessions)
+ .toString();
+ } else {
+ return "[Unhydrated execution of '" + getRootSession().getFlowId() + "']";
+ }
+ }
}
// subclassing hooks
@@ -337,7 +353,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
listeners.fireSessionCreating(context, flow);
FlowSession session = activateSession(flow);
if (session.isRoot()) {
- status = FlowExecutionStatus.STARTED;
+ status = FlowExecutionStatus.ACTIVE;
}
if (input == null) {
input = new LocalAttributeMap();
@@ -372,17 +388,11 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
boolean execute(Transition transition, RequestControlContext context) {
listeners.fireTransitionExecuting(context, transition);
- return transition.execute(getCurrentState(), context);
+ return transition.execute((State) getActiveSession().getState(), context);
}
void endActiveFlowSession(String outcome, MutableAttributeMap output, RequestControlContext context) {
FlowSessionImpl session = getActiveSessionInternal();
- if (session == null) {
- throw new IllegalArgumentException("Cannot end the active FlowSession when one has not been activated");
- }
- if (session.isRoot()) {
- status = FlowExecutionStatus.ENDING;
- }
listeners.fireSessionEnding(context, session, outcome, output);
session.getFlow().end(context, outcome, output);
flowSessions.removeLast();
@@ -514,37 +524,17 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
this.key = key;
}
- // custom serialization (implementation of Externalizable for optimized storage)
-
- public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
- status = (FlowExecutionStatus) in.readObject();
- flowSessions = (LinkedList) in.readObject();
- }
-
- public void writeExternal(ObjectOutput out) throws IOException {
- out.writeObject(status);
- out.writeObject(flowSessions);
- }
-
- public String toString() {
- if (!isActive()) {
- if (!hasStarted()) {
- return "[Not yet started " + getCaption() + "]";
- } else {
- return "[Ended " + getCaption() + "]";
- }
- } else {
- if (flow != null) {
- return new ToStringCreator(this).append("flow", flow.getId()).append("flowSessions", flowSessions)
- .toString();
- } else {
- return "[Unhydrated execution of '" + getRootSession().getFlowId() + "']";
- }
- }
- }
-
// internal helpers
+ private MessageContext createMessageContext(MessageSource messageSource) {
+ StateManageableMessageContext messageContext = new DefaultMessageContext(messageSource);
+ Serializable messagesMemento = (Serializable) getFlashScope().extract("messagesMemento");
+ if (messagesMemento != null) {
+ messageContext.restoreMessages(messagesMemento);
+ }
+ return messageContext;
+ }
+
/**
* Activate a new FlowSession for the flow definition. Creates the new flow session and pushes it onto
* the stack.
@@ -565,6 +555,12 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
return (FlowSessionImpl) flowSessions.getLast();
}
+ private void saveMessages(RequestContext context) {
+ StateManageableMessageContext messageContext = (StateManageableMessageContext) context.getMessageContext();
+ Serializable messagesMemento = messageContext.createMessagesMemento();
+ getFlashScope().put("messagesMemento", messagesMemento);
+ }
+
private FlowExecutionException wrap(Exception e) {
if (isActive()) {
FlowSession session = getActiveSession();
@@ -596,6 +592,9 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
logger.debug("Attempting to handle [" + exception + "]");
}
}
+ if (!isActive()) {
+ throw exception;
+ }
boolean handled = false;
try {
if (tryStateHandlers(exception, context) || tryFlowHandlers(exception, context)) {
@@ -628,7 +627,8 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
*/
private boolean tryStateHandlers(FlowExecutionException exception, RequestControlContext context) {
if (exception.getStateId() != null) {
- return getCurrentFlow().getStateInstance(exception.getStateId()).handleException(exception, context);
+ State state = getActiveSessionInternal().getFlow().getStateInstance(exception.getStateId());
+ return state.handleException(exception, context);
} else {
return false;
}
@@ -640,28 +640,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
* @return true if the exception was handled
*/
private boolean tryFlowHandlers(FlowExecutionException exception, RequestControlContext context) {
- return getCurrentFlow().handleException(exception, context);
- }
-
- /**
- * Returns the current flow which may or may not yet be active.
- */
- private Flow getCurrentFlow() {
- FlowSessionImpl session = getActiveSessionInternal();
- if (session != null) {
- return session.getFlow();
- } else {
- return flow;
- }
- }
-
- private State getCurrentState() {
- FlowSessionImpl session = getActiveSessionInternal();
- if (session != null) {
- return (State) session.getState();
- } else {
- return null;
- }
+ return getActiveSessionInternal().getFlow().handleException(exception, context);
}
}
\ No newline at end of file
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
index b274b5a3..aca68255 100644
--- 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
@@ -4,8 +4,7 @@ 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.
+ * NOT_STARTED. After start is called, they are ACTIVE. After ending, their status is updated to ENDED.
*/
public class FlowExecutionStatus extends StaticLabeledEnum {
@@ -14,25 +13,15 @@ public class FlowExecutionStatus extends StaticLabeledEnum {
*/
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");
+ public static final FlowExecutionStatus ACTIVE = new FlowExecutionStatus(1, "Active");
/**
* The flow execution has ended.
*/
- public static final FlowExecutionStatus ENDED = new FlowExecutionStatus(4, "Ended");
+ public static final FlowExecutionStatus ENDED = new FlowExecutionStatus(2, "Ended");
private FlowExecutionStatus(int code, String label) {
super(code, label);
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java
index 80be2f31..a29726cf 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java
@@ -32,7 +32,6 @@ import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.FlowExecutionContext;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.FlowExecutionKey;
-import org.springframework.webflow.execution.FlowSession;
import org.springframework.webflow.execution.View;
/**
@@ -99,13 +98,11 @@ class RequestControlContextImpl implements RequestControlContext {
// implementing RequestContext
public FlowDefinition getActiveFlow() {
- FlowSession session = flowExecution.getActiveSession();
- return session != null ? session.getDefinition() : null;
+ return flowExecution.getActiveSession().getDefinition();
}
public StateDefinition getCurrentState() {
- FlowSession session = flowExecution.getActiveSession();
- return session != null ? session.getState() : null;
+ return flowExecution.getActiveSession().getState();
}
public TransitionDefinition getMatchingTransition(String eventId) throws IllegalStateException {
@@ -125,13 +122,11 @@ class RequestControlContextImpl implements RequestControlContext {
}
public MutableAttributeMap getViewScope() throws IllegalStateException {
- FlowSession session = flowExecution.getActiveSession();
- return session != null ? session.getViewScope() : null;
+ return flowExecution.getActiveSession().getViewScope();
}
public MutableAttributeMap getFlowScope() {
- FlowSession session = flowExecution.getActiveSession();
- return session != null ? session.getScope() : null;
+ return flowExecution.getActiveSession().getScope();
}
public MutableAttributeMap getConversationScope() {
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 87915df4..0a02db66 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,9 +66,9 @@ public interface FlowExecutionContext {
public boolean hasStarted();
/**
- * 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
+ * Is the flow execution active? A flow execution is active once it has an {@link #getActiveSession() active
+ * session} and remains active until it has ended.
+ * @return true if active, false if the flow execution has terminated or has not yet been started
*/
public boolean isActive();
@@ -90,9 +90,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. Returns null of this flow execution is in the process of starting.
+ * subflow.
* @return the active flow session
- * @throws IllegalStateException if this flow execution has not been started at all or has ended
+ * @throws IllegalStateException if this flow execution is not active
+ * @see #isActive()
*/
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 09a0c627..269c646d 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,18 +58,19 @@ import org.springframework.webflow.definition.TransitionDefinition;
public interface RequestContext {
/**
- * 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.
+ * Returns the definition of the flow that is currently executing.
* @return the flow definition for the active session
- * @throws IllegalStateException if the flow execution has not been started at all or has ended
+ * @throws IllegalStateException if the flow execution is not active
+ * @see FlowExecutionContext#isActive()
*/
public FlowDefinition getActiveFlow() throws IllegalStateException;
/**
- * 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.
+ * Returns the current state of the executing flow. Returns null if the active flow's start state has
+ * not yet been entered.
* @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 has ended
+ * @throws IllegalStateException if this flow execution is not active
+ * @see FlowExecutionContext#isActive()
*/
public StateDefinition getCurrentState() throws IllegalStateException;
@@ -77,7 +78,8 @@ 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 has ended
+ * @throws IllegalStateException if this flow execution is not active
+ * @see FlowExecutionContext#isActive()
*/
public TransitionDefinition getMatchingTransition(String eventId) throws IllegalStateException;
@@ -106,23 +108,22 @@ public interface RequestContext {
/**
* Returns a mutable map for accessing and/or setting attributes in view scope. View scoped attributes exist for
* the life of the current view state.
- * @return the view scope, or null if the flow execution is in the process of starting but has not yet completed
- * startup
+ * @return the view scope
* @see #inViewState()
- * @throws IllegalStateException if this flow is not in a view-state, or the flow execution has not been started at
- * all or has ended
+ * @throws IllegalStateException if this flow is not in a view-state or the flow execution is not active
+ * @see FlowExecutionContext#isActive()
*/
public MutableAttributeMap getViewScope() throws IllegalStateException;
/**
* Returns a mutable map for accessing and/or setting attributes in flow scope. Flow scoped attributes exist for
* the life of the active flow session.
- * @return the flow scope, or null if the the flow execution is in the process of starting but has not yet completed
- * startup
+ * @return the flow scope
* @see FlowSession
- * @throws IllegalStateException if the flow execution has not been started at all or has ended
+ * @throws IllegalStateException if the flow execution is not active
+ * @see FlowExecutionContext#isActive()
*/
- public MutableAttributeMap getFlowScope();
+ public MutableAttributeMap getFlowScope() throws IllegalStateException;
/**
* Returns a mutable accessor for accessing and/or setting attributes in conversation scope. Conversation scoped
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 eceae582..95cc6f20 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
@@ -127,12 +127,6 @@ public class FlowExecutionImplTests extends TestCase {
new EndState(flow, "end");
FlowExecutionListener mockListener = new FlowExecutionListenerAdapter() {
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");
}
@@ -149,6 +143,7 @@ public class FlowExecutionImplTests extends TestCase {
assertEquals(flow.getId(), e.getFlowId());
assertNull(e.getStateId());
assertTrue(e.getCause() instanceof IllegalStateException);
+ e.printStackTrace();
assertTrue(e.getCause().getMessage().equals("Oops"));
}
}