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 691a4b46..2802017e 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 @@ -192,11 +192,8 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { catch (FlowExecutionException e) { return pause(context, handleException(e, context)); } catch (Exception e) { - String flowId = context.getActiveFlow().getId(); - String stateId = null; - if(context.getCurrentState() != null) { - stateId = context.getCurrentState().getId(); - } + String flowId = getCurrentFlow().getId(); + String stateId = getCurrentStateId(); FlowExecutionException flowException = new FlowExecutionException(flowId, stateId, "Exception thrown in state '" + stateId + "' of flow '" + flowId + "'", e); return pause(context, handleException(flowException, context)); @@ -226,8 +223,8 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { catch (FlowExecutionException e) { return pause(context, handleException(e, context)); } catch (Exception e) { - String flowId = context.getActiveFlow().getId(); - String stateId = context.getCurrentState().getId(); + String flowId = getCurrentFlow().getId(); + String stateId = getCurrentStateId(); FlowExecutionException flowException = new FlowExecutionException(flowId, stateId, "Exception thrown in state '" + stateId + "' of flow '" + flowId + "'", e); return pause(context, handleException(flowException, context)); @@ -259,8 +256,8 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { catch (FlowExecutionException e) { return pause(context, handleException(e, context)); } catch (Exception e) { - String flowId = context.getActiveFlow().getId(); - String stateId = context.getCurrentState().getId(); + String flowId = getCurrentFlow().getId(); + String stateId = getCurrentStateId(); FlowExecutionException flowException = new FlowExecutionException(flowId, stateId, "Exception thrown in state '" + stateId + "' of flow '" + flowId + "'", e); return pause(context, handleException(flowException, context)); @@ -332,16 +329,29 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { // the state could be null if the flow was attempting a start operation ViewSelection selectedView = tryStateHandlers(exception, context); if (selectedView != null) { + if (logger.isDebugEnabled()) { + logger.debug("State '" + exception.getStateId() + "' handled exception"); + } return selectedView; } selectedView = tryFlowHandlers(exception, context); if (selectedView != null) { + if (logger.isDebugEnabled()) { + logger.debug("Flow '" + flow.getId() + "' handled exception"); + } return selectedView; } } catch (FlowExecutionException newException) { // exception handling resulted in a new FlowExecutionException, try to handle it return handleException(newException, context); + } catch (Exception e) { + // a lower-level exception occured, wrap it in a flow execution exception and try to handle it + String flowId = getCurrentFlow().getId(); + String stateId = getCurrentStateId(); + FlowExecutionException flowException = new FlowExecutionException(flowId, stateId, + "Exception thrown in state '" + stateId + "' of flow '" + flowId + "'", e); + return handleException(flowException, context); } if (logger.isDebugEnabled()) { logger.debug("Rethrowing unhandled flow execution exception"); @@ -354,16 +364,11 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { * at the state level. Returns null if no handler handled the exception. */ private ViewSelection tryStateHandlers(FlowExecutionException exception, RequestControlContext context) { - ViewSelection selectedView = null; - if (exception.getStateId() != null) { - selectedView = getActiveFlow().getStateInstance(exception.getStateId()).handleException(exception, context); - if (selectedView != null) { - if (logger.isDebugEnabled()) { - logger.debug("State '" + exception.getStateId() + "' handled exception"); - } - } + if (isActive() && exception.getStateId() != null) { + return getActiveFlow().getStateInstance(exception.getStateId()).handleException(exception, context); + } else { + return null; } - return selectedView; } /** @@ -371,13 +376,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { * at the flow level. Returns null if no handler handled the exception. */ private ViewSelection tryFlowHandlers(FlowExecutionException exception, RequestControlContext context) { - ViewSelection selectedView = getActiveFlow().handleException(exception, context); - if (selectedView != null) { - if (logger.isDebugEnabled()) { - logger.debug("Flow '" + exception.getFlowId() + "' handled exception"); - } - } - return selectedView; + return getCurrentFlow().handleException(exception, context); } // internal helpers @@ -476,17 +475,41 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { } } + /** + * Returns the "current flow": which is the active flow if this execution is active, else the top-level flow definition. + */ + private Flow getCurrentFlow() { + return isActive() ? getActiveFlow() : this.flow; + } + + /** + * Returns the id of the "current" state: a valid state identifier if the flow is active and in a state; null if the flow + * is not active or has not yet entered a state. + */ + private String getCurrentStateId() { + if (isActive()) { + State state = getCurrentState(); + if (state != null) { + return state.getId(); + } else { + return null; + } + } else { + return null; + } + } + /** * Returns the currently active flow. */ - private Flow getActiveFlow() { + private Flow getActiveFlow() throws IllegalStateException { return (Flow)getActiveSessionInternal().getDefinition(); } /** * Returns the current state of this flow execution. */ - private State getCurrentState() { + private State getCurrentState() throws IllegalStateException { return (State)getActiveSessionInternal().getState(); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java index 81d1584c..d02d8745 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java @@ -24,6 +24,8 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.webflow.action.AbstractAction; import org.springframework.webflow.core.DefaultExpressionParserFactory; import org.springframework.webflow.core.collection.LocalAttributeMap; +import org.springframework.webflow.core.collection.MutableAttributeMap; +import org.springframework.webflow.definition.FlowDefinition; import org.springframework.webflow.engine.ActionState; import org.springframework.webflow.engine.EndState; import org.springframework.webflow.engine.Flow; @@ -253,6 +255,24 @@ public class FlowExecutionImplTests extends TestCase { execution.signalEvent("view", context); } + public void testUnhandledExceptionThrownBeforeSessionStartup() { + FlowBuilder flowBuilder = new XmlFlowBuilder(new ClassPathResource("runtime-exception.xml", + getClass())); + Flow flow = new FlowAssembler("runtime-exception", flowBuilder).assembleFlow(); + FlowExecutionListener listener = new FlowExecutionListenerAdapter() { + public void sessionStarting(RequestContext context, FlowDefinition definition, MutableAttributeMap input) { + throw new IllegalStateException("Cannot proceed"); + } + }; + FlowExecutionImpl flowExecution = new FlowExecutionImpl(flow); + flowExecution.setListeners(new FlowExecutionListeners(new FlowExecutionListener[] { listener })); + try { + flowExecution.start(new LocalAttributeMap(), new MockExternalContext()); + fail("Should have thrown a FlowExecutionException, not any other type"); + } catch (FlowExecutionException e) { + } + } + public void testExceptionFromInputMapper() { FlowBuilder flowBuilder = new XmlFlowBuilder(new ClassPathResource("runtime-exception.xml", getClass())); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/exceptionHandlingFlow.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/exceptionHandlingFlow.xml index 19aa440a..d8299477 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/exceptionHandlingFlow.xml +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/exceptionHandlingFlow.xml @@ -17,9 +17,27 @@ - + - - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file