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