flow execution status

This commit is contained in:
Keith Donald
2009-03-13 14:54:45 +00:00
parent 993bc67ac8
commit 79dfdf9bc6
5 changed files with 104 additions and 49 deletions

View File

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

View File

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

View File

@@ -66,11 +66,9 @@ public interface FlowExecutionContext {
public boolean hasStarted();
/**
* Is the flow execution active?
* <p>
* 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;

View File

@@ -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 <code>null</code> 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 <code>null</code> if this flow execution is in the
* 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.
* @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 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 <code>null</code> 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;

View File

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