From cc6451c2dc111286b59be1c3866b8b3557854b15 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Fri, 7 Mar 2008 14:32:38 +0000 Subject: [PATCH] transitions with no targets --- .../springframework/webflow/engine/Flow.java | 6 +- .../webflow/engine/RequestControlContext.java | 5 +- .../webflow/engine/SubflowState.java | 4 +- .../webflow/engine/Transition.java | 68 +++++++++---------- .../webflow/engine/TransitionableState.java | 15 +--- .../webflow/engine/ViewState.java | 47 ++++++------- .../engine/impl/FlowExecutionImpl.java | 8 +-- .../impl/RequestControlContextImpl.java | 8 +-- .../test/MockRequestControlContext.java | 8 +-- .../webflow/engine/TransitionTests.java | 17 ++--- 10 files changed, 85 insertions(+), 101 deletions(-) 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()); }