transitions with no targets

This commit is contained in:
Keith Donald
2008-03-07 14:32:38 +00:00
parent af3a908b45
commit cc6451c2dc
10 changed files with 85 additions and 101 deletions

View File

@@ -531,15 +531,15 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
* Handle the last event that occurred against an active session of this flow.
* @param context the flow execution control context
*/
public void handleEvent(RequestControlContext context) {
public boolean handleEvent(RequestControlContext context) {
TransitionableState currentState = getCurrentTransitionableState(context);
try {
currentState.handleEvent(context);
return currentState.handleEvent(context);
} catch (NoMatchingTransitionException e) {
// try the flow level transition set for a match
Transition transition = globalTransitionSet.getTransition(context);
if (transition != null) {
transition.execute(currentState, context);
return transition.execute(currentState, context);
} else {
// no matching global transition => let the original exception
// propagate

View File

@@ -85,11 +85,12 @@ public interface RequestControlContext extends RequestContext {
* should be called by clients that report internal event occurrences, such as action states. The
* <code>onEvent()</code> method of the flow involved in the flow execution will be called.
* @param event the event that occurred
* @return a boolean indicating if handling this event caused the current state to exit and a new state to enter
* @throws FlowExecutionException if an exception was thrown within a state of the flow during execution of this
* signalEvent operation
* @see Flow#handleEvent(RequestControlContext)
*/
public void handleEvent(Event event) throws FlowExecutionException;
public boolean handleEvent(Event event) throws FlowExecutionException;
/**
* End the active flow session of the current flow execution. This method should be called by clients that terminate
@@ -108,7 +109,7 @@ public interface RequestControlContext extends RequestContext {
* @param transition the transition
* @see Transition#execute(State, RequestControlContext)
*/
public void execute(Transition transition);
public boolean execute(Transition transition);
/**
* Returns true if the 'always redirect pause' flow execution attribute is set to true, false otherwise.

View File

@@ -105,7 +105,7 @@ public class SubflowState extends TransitionableState {
* Called on completion of the subflow to handle the subflow result event as determined by the end state reached by
* the subflow.
*/
public void handleEvent(RequestControlContext context) {
public boolean handleEvent(RequestControlContext context) {
if (subflowAttributeMapper != null) {
AttributeMap subflowOutput = context.getLastEvent().getAttributes();
if (logger.isDebugEnabled()) {
@@ -113,7 +113,7 @@ public class SubflowState extends TransitionableState {
}
subflowAttributeMapper.mapFlowOutput(subflowOutput, context);
}
super.handleEvent(context);
return super.handleEvent(context);
}
protected void appendToString(ToStringCreator creator) {

View File

@@ -121,7 +121,7 @@ public class Transition extends AnnotatedObject implements TransitionDefinition
* @param matchingCriteria the transition matching criteria
*/
public void setMatchingCriteria(TransitionCriteria matchingCriteria) {
Assert.notNull(matchingCriteria, "The matching criteria is required");
Assert.notNull(matchingCriteria, "The criteria for matching this transition is required");
this.matchingCriteria = matchingCriteria;
}
@@ -140,7 +140,6 @@ public class Transition extends AnnotatedObject implements TransitionDefinition
* @param executionCriteria the transition execution criteria
*/
public void setExecutionCriteria(TransitionCriteria executionCriteria) {
Assert.notNull(executionCriteria, "The execution criteria is required");
this.executionCriteria = executionCriteria;
}
@@ -157,7 +156,6 @@ public class Transition extends AnnotatedObject implements TransitionDefinition
* @param targetStateResolver the target state resolver
*/
public void setTargetStateResolver(TargetStateResolver targetStateResolver) {
Assert.notNull(targetStateResolver, "The target state resolver is required");
this.targetStateResolver = targetStateResolver;
}
@@ -178,7 +176,11 @@ public class Transition extends AnnotatedObject implements TransitionDefinition
* @return true if this transition can complete execution, false if it should roll back
*/
public boolean canExecute(RequestContext context) {
return executionCriteria.test(context);
if (executionCriteria != null) {
return executionCriteria.test(context);
} else {
return false;
}
}
/**
@@ -186,44 +188,40 @@ public class Transition extends AnnotatedObject implements TransitionDefinition
* for the given context.
* @param sourceState the source state to transition from, may be null if the current state is null
* @param context the flow execution control context
* @return a boolean indicating if executing this transition caused the current state to exit and a new state to
* enter
* @throws FlowExecutionException when transition execution fails
*/
public void execute(State sourceState, RequestControlContext context) throws FlowExecutionException {
public boolean execute(State sourceState, RequestControlContext context) throws FlowExecutionException {
if (canExecute(context)) {
if (sourceState != null) {
if (logger.isDebugEnabled()) {
logger.debug("Executing " + this + " out of state '" + sourceState.getId() + "'");
}
if (sourceState instanceof TransitionableState) {
// make exit call back on transitionable state
((TransitionableState) sourceState).exit(context);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Executing " + this);
}
if (logger.isDebugEnabled()) {
logger.debug("Executing " + this);
}
State targetState = targetStateResolver.resolveTargetState(this, sourceState, context);
context.setLastTransition(this);
// enter the target state (note: any exceptions are propagated)
targetState.enter(context);
} else {
if (sourceState != null && sourceState instanceof TransitionableState) {
((TransitionableState) sourceState).reenter(context);
} else {
throw new IllegalStateException("Execution of '" + this + "' was blocked by '" + getExecutionCriteria()
+ "', " + "; however, no source state is set at runtime. "
+ "This is an illegal situation: check your flow definition.");
}
}
if (logger.isDebugEnabled()) {
if (context.getFlowExecutionContext().isActive()) {
logger.debug("Completed execution of " + this + "; as a result, the new state is '"
+ context.getCurrentState().getId() + "' in flow '" + context.getActiveFlow().getId() + "'");
} else {
logger.debug("Completed execution of " + this + "; as a result, the flow execution has ended");
if (targetStateResolver != null) {
State targetState = targetStateResolver.resolveTargetState(this, sourceState, context);
if (sourceState != null) {
if (logger.isDebugEnabled()) {
logger.debug("Exiting state '" + sourceState.getId() + "'");
}
if (sourceState instanceof TransitionableState) {
((TransitionableState) sourceState).exit(context);
}
}
targetState.enter(context);
if (logger.isDebugEnabled()) {
if (context.getFlowExecutionContext().isActive()) {
logger.debug("Completed transition execution. As a result, the new state is '"
+ context.getCurrentState().getId() + "' in flow '" + context.getActiveFlow().getId()
+ "'");
} else {
logger.debug("Completed transition execution. As a result, the flow execution has ended");
}
}
return true;
}
}
return false;
}
public String toString() {

View File

@@ -101,19 +101,8 @@ public abstract class TransitionableState extends State implements Transitionabl
* @param context the flow execution control context
* @throws NoMatchingTransitionException when a matching transition cannot be found
*/
public void handleEvent(RequestControlContext context) throws NoMatchingTransitionException {
context.execute(getRequiredTransition(context));
}
/**
* Re-enter this state. This is typically called when a transition out of this state is selected, but transition
* execution rolls back and as a result the flow reenters the source state.
* <p>
* By default, this just calls <code>enter()</code>.
* @param context the flow control context in an executing flow (a client instance of a flow)
*/
public void reenter(RequestControlContext context) {
enter(context);
public boolean handleEvent(RequestControlContext context) throws NoMatchingTransitionException {
return context.execute(getRequiredTransition(context));
}
/**

View File

@@ -169,13 +169,7 @@ public class ViewState extends TransitionableState {
context.sendFlowExecutionRedirect();
} else {
View view = viewFactory.getView(context);
renderActionList.execute(context);
if (logger.isDebugEnabled()) {
logger.debug("Rendering view " + view);
}
view.render();
context.getMessageContext().clearMessages();
context.getFlashScope().clear();
render(context, view);
}
}
@@ -187,15 +181,12 @@ public class ViewState extends TransitionableState {
if (logger.isDebugEnabled()) {
logger.debug("Event '" + event.getId() + "' signaled on view " + view);
}
context.handleEvent(event);
} else {
renderActionList.execute(context);
if (logger.isDebugEnabled()) {
logger.debug("Rendering refreshed view " + view);
boolean stateExited = context.handleEvent(event);
if (!stateExited) {
render(context, view);
}
view.render();
context.getMessageContext().clearMessages();
context.getFlashScope().clear();
} else {
render(context, view);
}
}
@@ -206,14 +197,6 @@ public class ViewState extends TransitionableState {
// internal helpers
private boolean shouldRedirect(RequestControlContext context) {
if (redirect != null) {
return redirect.booleanValue();
} else {
return context.getAlwaysRedirectOnPause();
}
}
private void createVariables(RequestContext context) {
Iterator it = variables.values().iterator();
while (it.hasNext()) {
@@ -225,6 +208,24 @@ public class ViewState extends TransitionableState {
}
}
private boolean shouldRedirect(RequestControlContext context) {
if (redirect != null) {
return redirect.booleanValue();
} else {
return context.getAlwaysRedirectOnPause();
}
}
private void render(RequestControlContext context, View view) {
if (logger.isDebugEnabled()) {
logger.debug("Rendering + " + view);
}
renderActionList.execute(context);
view.render();
context.getMessageContext().clearMessages();
context.getFlashScope().clear();
}
private void restoreVariables(RequestContext context) {
Iterator it = variables.values().iterator();
while (it.hasNext()) {

View File

@@ -344,14 +344,14 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
listeners.fireStateEntered(context, previousState);
}
void handleEvent(Event event, RequestControlContext context) {
boolean handleEvent(Event event, RequestControlContext context) {
listeners.fireEventSignaled(context, event);
getActiveSessionInternal().getFlow().handleEvent(context);
return getActiveSessionInternal().getFlow().handleEvent(context);
}
void execute(Transition transition, RequestControlContext context) {
boolean execute(Transition transition, RequestControlContext context) {
listeners.fireTransitionExecuting(context, transition);
transition.execute(getCurrentState(), context);
return transition.execute(getCurrentState(), context);
}
FlowSession endActiveFlowSession(MutableAttributeMap output, RequestControlContext context) {

View File

@@ -196,13 +196,13 @@ class RequestControlContextImpl implements RequestControlContext {
flowExecution.start(flow, input, this);
}
public void handleEvent(Event event) throws FlowExecutionException {
public boolean handleEvent(Event event) throws FlowExecutionException {
this.lastEvent = event;
flowExecution.handleEvent(event, this);
return flowExecution.handleEvent(event, this);
}
public void execute(Transition transition) {
flowExecution.execute(transition, this);
public boolean execute(Transition transition) {
return flowExecution.execute(transition, this);
}
public FlowSession endActiveFlowSession(MutableAttributeMap output) throws IllegalStateException {

View File

@@ -73,9 +73,9 @@ public class MockRequestControlContext extends MockRequestContext implements Req
flow.start(this, input);
}
public void handleEvent(Event event) {
public boolean handleEvent(Event event) {
setLastEvent(event);
((Flow) getActiveFlow()).handleEvent(this);
return ((Flow) getActiveFlow()).handleEvent(this);
}
public FlowSession endActiveFlowSession(MutableAttributeMap output) throws IllegalStateException {
@@ -85,8 +85,8 @@ public class MockRequestControlContext extends MockRequestContext implements Req
return endingSession;
}
public void execute(Transition transition) {
transition.execute((TransitionableState) getCurrentState(), this);
public boolean execute(Transition transition) {
return transition.execute((TransitionableState) getCurrentState(), this);
}
public FlowExecutionKey assignFlowExecutionKey() {

View File

@@ -24,7 +24,6 @@ import org.springframework.webflow.test.MockRequestControlContext;
public class TransitionTests extends TestCase {
private boolean reenterCalled;
private boolean exitCalled;
public void testExecuteTransitionFromState() {
@@ -50,7 +49,8 @@ public class TransitionTests extends TestCase {
MockRequestControlContext context = new MockRequestControlContext(flow);
context.setCurrentState(source);
Transition t = new Transition(targetResolver);
t.execute(source, context);
boolean stateExited = t.execute(source, context);
assertTrue(stateExited);
assertTrue(exitCalled);
assertSame(target, context.getCurrentState());
}
@@ -69,19 +69,14 @@ public class TransitionTests extends TestCase {
};
MockRequestControlContext context = new MockRequestControlContext(flow);
Transition t = new Transition(targetResolver);
t.execute(null, context);
boolean stateChanged = t.execute(null, context);
assertTrue(stateChanged);
assertSame(target, context.getCurrentState());
}
public void testTransitionExecutionRefused() {
Flow flow = new Flow("flow");
final TransitionableState source = new TransitionableState(flow, "state 1") {
public void reenter(RequestControlContext context) {
reenterCalled = true;
super.reenter(context);
}
public void exit(RequestControlContext context) {
exitCalled = true;
}
@@ -107,9 +102,9 @@ public class TransitionTests extends TestCase {
return false;
}
});
t.execute(source, context);
boolean stateExited = t.execute(source, context);
assertFalse(stateExited);
assertFalse(exitCalled);
assertTrue(reenterCalled);
assertSame(source, context.getCurrentState());
}