diff --git a/build-spring-webflow/resources/changelog.txt b/build-spring-webflow/resources/changelog.txt index 417eb2d2..446d9ff6 100644 --- a/build-spring-webflow/resources/changelog.txt +++ b/build-spring-webflow/resources/changelog.txt @@ -17,6 +17,8 @@ Bug Fixes * Made FlowExecutionSnapshotGroup public for serialization reasons * Fixed bug where a flow execution snapshot id was not always incremented, which could lead to collisions between multiple windows sharing the same execution (SWF-1098). * Fixed bug where updating ViewState history could result in exceptions if no flow execution key was assigned or no snapshot had been taken (SWF-1099). +* Fixed bug where ExternalContext.getFlowExecutionUrl method was not encoding execution URL for rendering by views +* Fixed bugs where FlowHandlerAdapter was not encoding flow definition URLs in its default flow outcome and exception handler routines Improvements * Added userEventQueued and getUserEventState methods to View SPI, which simplified ViewState and View object interaction. diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/ExternalContext.java b/spring-webflow/src/main/java/org/springframework/webflow/context/ExternalContext.java index 90cdd48b..abb99af1 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/context/ExternalContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/ExternalContext.java @@ -94,7 +94,7 @@ public interface ExternalContext { /** * Get a flow execution URL for the execution with the provided key. Typically used by response writers that write - * out references to the flow execution to support postback on a subsequent request. + * out references to the flow execution to support postback on a subsequent request. The URL returned is encoded. * @param flowId the flow definition id * @param flowExecutionKey the flow execution key * @return the flow execution URL diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/ServletExternalContext.java b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/ServletExternalContext.java index a34e071c..34db2a4d 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/ServletExternalContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/ServletExternalContext.java @@ -202,7 +202,7 @@ public class ServletExternalContext implements ExternalContext { } public String getFlowExecutionUrl(String flowId, String flowExecutionKey) { - return flowUrlHandler.createFlowExecutionUrl(flowId, flowExecutionKey, request); + return response.encodeURL(flowUrlHandler.createFlowExecutionUrl(flowId, flowExecutionKey, request)); } public Writer getResponseWriter() throws IllegalStateException { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapter.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapter.java index 1863244c..8975b2a7 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapter.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapter.java @@ -273,7 +273,8 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd logger.debug("Ended flow '" + flowId + "' did not commit a response; " + "attempting to start a new flow execution as a default outcome handler"); } - response.sendRedirect(flowUrlHandler.createFlowDefinitionUrl(flowId, outcome.getOutput(), request)); + String flowUrl = flowUrlHandler.createFlowDefinitionUrl(flowId, outcome.getOutput(), request); + sendRedirect(flowUrl, request, response); } } @@ -296,7 +297,8 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd logger.debug("Restarting a new execution of previously ended flow '" + flowId + "'"); } // by default, attempt to restart the flow - response.sendRedirect(flowUrlHandler.createFlowDefinitionUrl(flowId, null, request)); + String flowUrl = flowUrlHandler.createFlowDefinitionUrl(flowId, null, request); + sendRedirect(flowUrl, request, response); } } else { throw e; diff --git a/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/ServletExternalContextTests.java b/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/ServletExternalContextTests.java index 13412a54..9a920df8 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/ServletExternalContextTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/ServletExternalContextTests.java @@ -74,6 +74,12 @@ public class ServletExternalContextTests extends TestCase { assertEquals(response, context.getNativeResponse()); } + public void testGetExecutionUrl() { + request.setRequestURI("/foo"); + String url = context.getFlowExecutionUrl("foo", "e1s1"); + assertEquals("/foo?execution=e1s1", url); + } + public void testNotAnAjaxRequest() { assertFalse(context.isAjaxRequest()); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapterTests.java b/spring-webflow/src/test/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapterTests.java index f2dd0169..e6b545cf 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapterTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/mvc/servlet/FlowHandlerAdapterTests.java @@ -77,7 +77,6 @@ public class FlowHandlerAdapterTests extends TestCase { } } }; - request = new MockHttpServletRequest(); response = new MockHttpServletResponse(); context = new ServletExternalContext(servletContext, request, response, flowHandlerAdapter.getFlowUrlHandler()); @@ -117,6 +116,28 @@ public class FlowHandlerAdapterTests extends TestCase { EasyMock.verify(new Object[] { flowExecutor }); } + public void testLaunchFlowRequestEndsAfterProcessingAjaxRequest() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/whatever"); + request.setRequestURI("/springtravel/app/whatever"); + request.setMethod("GET"); + Map parameters = new HashMap(); + request.setParameters(parameters); + context.setAjaxRequest(true); + flowExecutor.launchExecution("foo", flowInput, context); + LocalAttributeMap output = new LocalAttributeMap(); + output.put("bar", "baz"); + FlowExecutionOutcome outcome = new FlowExecutionOutcome("finish", output); + FlowExecutionResult result = FlowExecutionResult.createEndedResult("foo", outcome); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { flowExecutor }); + request.addHeader("Accept", "text/html;type=ajax"); + flowHandlerAdapter.handle(request, response, flowHandler); + assertEquals("/springtravel/app/foo?bar=baz", response.getHeader("Spring-Redirect-URL")); + EasyMock.verify(new Object[] { flowExecutor }); + } + public void testResumeFlowRequest() throws Exception { request.setContextPath("/springtravel"); request.setServletPath("/app"); @@ -412,6 +433,24 @@ public class FlowHandlerAdapterTests extends TestCase { EasyMock.verify(new Object[] { flowExecutor }); } + public void testDefaultHandleNoSuchFlowExecutionExceptionAjaxRequest() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + request.addParameter("execution", "12345"); + flowExecutor.resumeExecution("12345", context); + FlowException flowException = new NoSuchFlowExecutionException(new MockFlowExecutionKey("12345"), null); + EasyMock.expectLastCall().andThrow(flowException); + EasyMock.replay(new Object[] { flowExecutor }); + context.setAjaxRequest(true); + request.addHeader("Accept", "text/html;type=ajax"); + flowHandlerAdapter.handle(request, response, flowHandler); + assertEquals("/springtravel/app/foo", response.getHeader("Spring-Redirect-URL")); + EasyMock.verify(new Object[] { flowExecutor }); + } + public void testHandleFlowOutcomeCustomFlowHandler() throws Exception { handleExecutionOutcome = true; request.setContextPath("/springtravel");