polish
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 <code>FlowSession</code> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -58,18 +58,19 @@ import org.springframework.webflow.definition.TransitionDefinition;
|
||||
public interface RequestContext {
|
||||
|
||||
/**
|
||||
* Returns the definition of the flow that is currently executing. Returns <code>null</code> 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 <code>null</code> 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 <code>null</code> if the active flow's start state has
|
||||
* not yet been entered.
|
||||
* @return the current state, or <code>null</code> 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 <code>null</code> 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. <b>View scoped attributes exist for
|
||||
* the life of the current view state.</b>
|
||||
* @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. <b>Flow scoped attributes exist for
|
||||
* the life of the active flow session.</b>
|
||||
* @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. <b>Conversation scoped
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user