transitions with no targets
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user