diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java
index 2e6738b3..c02df2b9 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java
@@ -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
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java
index 0a8391de..e1a30757 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java
@@ -85,11 +85,12 @@ public interface RequestControlContext extends RequestContext {
* should be called by clients that report internal event occurrences, such as action states. The
* onEvent() 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.
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowState.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowState.java
index efea65ff..da2aff0c 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowState.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowState.java
@@ -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) {
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/Transition.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/Transition.java
index 16f40554..c5788a33 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/engine/Transition.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/Transition.java
@@ -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() {
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/TransitionableState.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/TransitionableState.java
index 5f37cefb..6e5553c1 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/engine/TransitionableState.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/TransitionableState.java
@@ -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.
- *
- * By default, this just calls enter().
- * @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));
}
/**
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java
index f49f7600..fd614d37 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java
@@ -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()) {
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 7109b7a3..bb74be85 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
@@ -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) {
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 8afbbff7..b120920f 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
@@ -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 {
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java
index b8a408a8..c45bd958 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java
@@ -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() {
diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/TransitionTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/TransitionTests.java
index eba48bb0..6b1a7a57 100644
--- a/spring-webflow/src/test/java/org/springframework/webflow/engine/TransitionTests.java
+++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/TransitionTests.java
@@ -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());
}