Ensures that all exceptions (including RuntimeExceptions) are handled properly by the FlowExcution (SWF-333)

This commit is contained in:
Ben Hale
2007-06-14 02:24:36 +00:00
parent 106cf6d4d5
commit da9de998a5
4 changed files with 116 additions and 2 deletions

View File

@@ -191,6 +191,15 @@ 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();
}
FlowExecutionException flowException = new FlowExecutionException(flowId, stateId,
"Exception thrown in state '" + stateId + "' of flow '" + flowId + "'", e);
return pause(context, handleException(flowException, context));
}
}
finally {
@@ -216,6 +225,12 @@ 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();
FlowExecutionException flowException = new FlowExecutionException(flowId, stateId,
"Exception thrown in state '" + stateId + "' of flow '" + flowId + "'", e);
return pause(context, handleException(flowException, context));
}
}
finally {
@@ -243,6 +258,12 @@ 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();
FlowExecutionException flowException = new FlowExecutionException(flowId, stateId,
"Exception thrown in state '" + stateId + "' of flow '" + flowId + "'", e);
return pause(context, handleException(flowException, context));
}
}
finally {

View File

@@ -27,6 +27,8 @@ import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.engine.ActionState;
import org.springframework.webflow.engine.EndState;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.FlowExecutionExceptionHandler;
import org.springframework.webflow.engine.RequestControlContext;
import org.springframework.webflow.engine.SubflowState;
import org.springframework.webflow.engine.TargetStateResolver;
import org.springframework.webflow.engine.Transition;
@@ -47,7 +49,9 @@ import org.springframework.webflow.engine.support.TransitionExecutingStateExcept
import org.springframework.webflow.execution.Action;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.FlowExecutionListener;
import org.springframework.webflow.execution.FlowExecutionListenerAdapter;
import org.springframework.webflow.execution.MockFlowExecutionListener;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.TestAction;
@@ -248,6 +252,45 @@ public class FlowExecutionImplTests extends TestCase {
execution.refresh(context);
execution.signalEvent("view", context);
}
public void testExceptionFromInputMapper() {
FlowBuilder flowBuilder = new XmlFlowBuilder(new ClassPathResource("runtime-exception.xml",
getClass()));
Flow flow = new FlowAssembler("runtime-exception", flowBuilder).assembleFlow();
FlowExecutionImpl flowExecution = new FlowExecutionImpl(flow);
try {
flowExecution.start(new LocalAttributeMap(), new MockExternalContext());
fail("Should have thrown a FlowExecutionException, not any other type");
} catch (FlowExecutionException e) {
}
}
public void testExceptionWithListener() {
FlowBuilder flowBuilder = new XmlFlowBuilder(new ClassPathResource("runtime-exception.xml",
getClass()));
Flow flow = new FlowAssembler("runtime-exception", flowBuilder).assembleFlow();
FlowExceptionListener listener = new FlowExceptionListener();
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) {
}
assertTrue("Listener should have been called on exception", listener.getExceptionFired());
}
public void testExceptionWithHandler() {
FlowBuilder flowBuilder = new XmlFlowBuilder(new ClassPathResource("runtime-exception.xml",
getClass()));
Flow flow = new FlowAssembler("runtime-exception", flowBuilder).assembleFlow();
FlowExceptionHandler handler = new FlowExceptionHandler();
flow.getExceptionHandlerSet().add(handler);
FlowExecutionImpl flowExecution = new FlowExecutionImpl(flow);
flowExecution.start(new LocalAttributeMap(), new MockExternalContext());
assertTrue("Handler should have been called on exception", handler.getExceptionHandled());
}
public static TransitionCriteria onEvent(String event) {
return new EventIdTransitionCriteria(event);
@@ -293,4 +336,36 @@ public class FlowExecutionImplTests extends TestCase {
});
}
}
private class FlowExceptionListener extends FlowExecutionListenerAdapter {
private boolean exceptionFired = false;
public boolean getExceptionFired() {
return exceptionFired;
}
public void exceptionThrown(RequestContext context, FlowExecutionException exception) {
exceptionFired = true;
}
}
private class FlowExceptionHandler implements FlowExecutionExceptionHandler {
private boolean exceptionHandled = false;
public boolean getExceptionHandled() {
return exceptionHandled;
}
public ViewSelection handle(FlowExecutionException exception, RequestControlContext context) {
exceptionHandled = true;
return ViewSelection.NULL_VIEW;
}
public boolean handles(FlowExecutionException exception) {
return true;
}
}
}

View File

@@ -30,6 +30,7 @@ import org.springframework.webflow.engine.builder.xml.XmlFlowBuilder;
import org.springframework.webflow.engine.support.ApplicationViewSelector;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.support.ApplicationView;
import org.springframework.webflow.test.MockExternalContext;
@@ -75,8 +76,10 @@ public class MiscFlowExecutionTests extends TestCase {
FlowExecutionImpl execution = new FlowExecutionImpl(flow);
try {
execution.start(null, new MockExternalContext());
} catch (RequiredMappingException e) {
fail("Should have thrown a FlowExecutionException");
} catch (FlowExecutionException e) {
assertTrue("Root cause should have been a RequiredMappingException",
e.getRootCause() instanceof RequiredMappingException);
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd">
<input-mapper>
<input-attribute name="inputAttribute" required="true"/>
</input-mapper>
<start-state idref="start" />
<end-state id="start"/>
</flow>