diff --git a/spring-faces/src/main/java/org/springframework/faces/ui/resource/RenderResourceAction.java b/spring-faces/src/main/java/org/springframework/faces/ui/resource/RenderResourceAction.java deleted file mode 100644 index beb24cbb..00000000 --- a/spring-faces/src/main/java/org/springframework/faces/ui/resource/RenderResourceAction.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.springframework.faces.ui.resource; - -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URL; -import java.net.URLConnection; -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.util.ClassUtils; -import org.springframework.webflow.execution.Action; -import org.springframework.webflow.execution.Event; -import org.springframework.webflow.execution.RequestContext; - -/** - * Special action for resolving and rendering static resources from within a JAR file. - * - * @author Jeremy Grelle - */ -public class RenderResourceAction implements Action { - - private static final String IF_MODIFIED_SINCE_HEADER = "If-Modified-Since"; - - private static final String HTTP_CONTENT_LENGTH_HEADER = "Content-Length"; - - private static final String HTTP_LAST_MODIFIED_HEADER = "Last-Modified"; - - private static final String HTTP_EXPIRES_HEADER = "Expires"; - - private static final String HTTP_CACHE_CONTROL_HEADER = "Cache-Control"; - - private Map defaultMimeTypes = new HashMap(); - { - defaultMimeTypes.put(".css", "text/css"); - defaultMimeTypes.put(".gif", "image/gif"); - defaultMimeTypes.put(".ico", "image/vnd.microsoft.icon"); - defaultMimeTypes.put(".jpeg", "image/jpeg"); - defaultMimeTypes.put(".jpg", "image/jpeg"); - defaultMimeTypes.put(".js", "text/javascript"); - defaultMimeTypes.put(".png", "image/png"); - } - - public Event execute(RequestContext context) throws Exception { - - HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest(); - HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse(); - ServletContext servletContext = (ServletContext) context.getExternalContext().getContext(); - - String localResourcePath = context.getExternalContext().getRequestPath().toString(); - String jarResourcePath = "META-INF" + context.getExternalContext().getRequestPath().toString(); - - URL resource; - - resource = servletContext.getResource(localResourcePath); - if (resource == null) { - resource = ClassUtils.getDefaultClassLoader().getResource(jarResourcePath); - if (resource == null) { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - return new Event(this, "success"); - } - } - - URLConnection resourceConn = resource.openConnection(); - long lastModified = resourceConn.getLastModified(); - - long ifModifiedSince = request.getDateHeader(IF_MODIFIED_SINCE_HEADER); - if (ifModifiedSince > 0 && lastModified / 1000 <= ifModifiedSince / 1000) { - response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - return new Event(this, "success"); - } - - String mimeType = servletContext.getMimeType(jarResourcePath); - if (mimeType == null) { - String extension = jarResourcePath.substring(jarResourcePath.lastIndexOf('.')); - mimeType = (String) defaultMimeTypes.get(extension); - } - response.setContentType(mimeType); - - response.setHeader(HTTP_CONTENT_LENGTH_HEADER, Long.toString(resourceConn.getContentLength())); - response.setDateHeader(HTTP_LAST_MODIFIED_HEADER, lastModified); - configureCaching(response, 31556926); - - InputStream in = resourceConn.getInputStream(); - OutputStream out = response.getOutputStream(); - try { - byte[] buffer = new byte[1024]; - while (in.available() > 0) { - int len = in.read(buffer); - out.write(buffer, 0, len); - } - } finally { - in.close(); - out.close(); - } - return new Event(this, "success"); - } - - /** - * Set HTTP headers to allow caching for the given number of seconds. - * @param seconds number of seconds into the future that the response should be cacheable for - */ - private void configureCaching(HttpServletResponse response, int seconds) { - // HTTP 1.0 header - response.setDateHeader(HTTP_EXPIRES_HEADER, System.currentTimeMillis() + seconds * 1000L); - // HTTP 1.1 header - response.setHeader(HTTP_CACHE_CONTROL_HEADER, "max-age=" + seconds); - } -} diff --git a/spring-faces/src/main/java/org/springframework/faces/ui/resource/ResourcesFlowBuilder.java b/spring-faces/src/main/java/org/springframework/faces/ui/resource/ResourcesFlowBuilder.java deleted file mode 100644 index 25c97b3a..00000000 --- a/spring-faces/src/main/java/org/springframework/faces/ui/resource/ResourcesFlowBuilder.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.springframework.faces.ui.resource; - -import org.springframework.webflow.engine.EndState; -import org.springframework.webflow.engine.builder.FlowBuilderException; -import org.springframework.webflow.engine.builder.support.AbstractFlowBuilder; - -/** - * Builder for generating the "resources" flow which is responsible for serving static resources from the classpath. - * @author Jeremy Grelle - */ -public class ResourcesFlowBuilder extends AbstractFlowBuilder { - public void buildStates() throws FlowBuilderException { - EndState renderResource = new EndState(getFlow(), "renderResource"); - renderResource.setFinalResponseAction(new RenderResourceAction()); - } -} diff --git a/spring-faces/src/test/java/org/springframework/faces/ui/resource/RenderResourceActionTests.java b/spring-faces/src/test/java/org/springframework/faces/ui/resource/RenderResourceActionTests.java deleted file mode 100644 index e234ecbb..00000000 --- a/spring-faces/src/test/java/org/springframework/faces/ui/resource/RenderResourceActionTests.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.springframework.faces.ui.resource; - -import org.easymock.EasyMock; - -import java.io.PrintWriter; -import java.io.StringWriter; - -import javax.servlet.ServletContext; - -import junit.framework.TestCase; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockServletContext; -import org.springframework.webflow.context.ExternalContext; -import org.springframework.webflow.context.RequestPath; -import org.springframework.webflow.execution.RequestContext; - -public class RenderResourceActionTests extends TestCase { - - ExternalContext externalContext = (ExternalContext) EasyMock.createMock(ExternalContext.class); - RequestContext requestContext = (RequestContext) EasyMock.createMock(RequestContext.class); - ServletContext servletContext = new ResourceTestMockServletContext(); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockHttpServletRequest request = new MockHttpServletRequest(); - - RequestPath requestPath; - String[] requestElements; - - RenderResourceAction action; - - protected void setUp() throws Exception { - action = new RenderResourceAction(); - - EasyMock.expect(requestContext.getExternalContext()).andStubReturn(externalContext); - EasyMock.expect(externalContext.getContext()).andStubReturn(servletContext); - EasyMock.expect(externalContext.getResponse()).andStubReturn(response); - EasyMock.expect(externalContext.getRequest()).andStubReturn(request); - EasyMock.expect(externalContext.getResponseWriter()).andStubReturn(new PrintWriter(new StringWriter())); - } - - public final void testExecute() throws Exception { - - requestPath = new RequestPath("/ext/ext.js"); - - EasyMock.expect(externalContext.getRequestPath()).andStubReturn(requestPath); - - EasyMock.replay(new Object[] { requestContext, externalContext }); - - action.execute(requestContext); - } - - public final void testExecute_ResourceNotFound() throws Exception { - - requestPath = new RequestPath("/xxx/xxx.js"); - - EasyMock.expect(externalContext.getRequestPath()).andStubReturn(requestPath); - - EasyMock.replay(new Object[] { requestContext, externalContext }); - - action.execute(requestContext); - - assertEquals(404, response.getStatus()); - } - - private class ResourceTestMockServletContext extends MockServletContext { - - public String getMimeType(String filePath) { - return null; - } - - } -} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/DefaultFlowUrlHandler.java b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/DefaultFlowUrlHandler.java new file mode 100644 index 00000000..d9aaa5e4 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/DefaultFlowUrlHandler.java @@ -0,0 +1,87 @@ +package org.springframework.webflow.context.servlet; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Iterator; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.util.UrlPathHelper; +import org.springframework.web.util.WebUtils; +import org.springframework.webflow.core.collection.AttributeMap; + +public class DefaultFlowUrlHandler implements FlowUrlHandler { + + private static final String DEFAULT_URL_ENCODING_SCHEME = "UTF-8"; + + private String urlEncodingScheme = DEFAULT_URL_ENCODING_SCHEME; + + private UrlPathHelper urlPathHelper; + + public DefaultFlowUrlHandler() { + urlPathHelper = new UrlPathHelper(); + } + + public String getFlowExecutionKey(HttpServletRequest request) { + return request.getParameter("execution"); + } + + public String getFlowId(HttpServletRequest request) { + return WebUtils.extractFilenameFromUrlPath(urlPathHelper.getLookupPathForRequest(request)); + } + + public String createFlowExecutionUrl(String flowId, String flowExecutionKey, HttpServletRequest request) { + StringBuffer url = new StringBuffer(); + url.append(request.getContextPath()); + url.append(request.getServletPath()); + url.append('/'); + url.append(encode(flowId)); + url.append('?'); + appendQueryParameter(url, "execution", flowExecutionKey); + return url.toString(); + } + + public String createFlowDefinitionUrl(String flowId, AttributeMap input, HttpServletRequest request) { + StringBuffer url = new StringBuffer(); + url.append(request.getContextPath()); + url.append(request.getServletPath()); + url.append('/'); + url.append(encode(flowId)); + if (input != null && !input.isEmpty()) { + url.append('?'); + appendQueryParameters(url, input.asMap()); + } + return url.toString(); + } + + private void appendQueryParameters(StringBuffer url, Map parameters) { + Iterator entries = parameters.entrySet().iterator(); + while (entries.hasNext()) { + Map.Entry entry = (Map.Entry) entries.next(); + appendQueryParameter(url, entry.getKey(), entry.getValue()); + if (entries.hasNext()) { + url.append('&'); + } + } + } + + private void appendQueryParameter(StringBuffer url, Object key, Object value) { + String encodedKey = encode(key); + String encodedValue = encode(value); + url.append(encodedKey).append('=').append(encodedValue); + } + + private String encode(Object value) { + return value != null ? urlEncode(String.valueOf(value)) : ""; + } + + private String urlEncode(String value) { + try { + return URLEncoder.encode(String.valueOf(value), urlEncodingScheme); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Cannot url encode " + value); + } + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/FlowUrlHandler.java b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/FlowUrlHandler.java new file mode 100644 index 00000000..46c5f051 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/FlowUrlHandler.java @@ -0,0 +1,39 @@ +package org.springframework.webflow.context.servlet; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.webflow.core.collection.AttributeMap; + +public interface FlowUrlHandler { + + /** + * Extract the flow execution from the request. + * @param request the request + * @return the flow execution key, or null if no flow execution key is present + */ + public String getFlowExecutionKey(HttpServletRequest request); + + /** + * Extract the flow id from the request. + * @param request the request + * @return the flow id, or null if no flow id is present + */ + public String getFlowId(HttpServletRequest request); + + /** + * Create a URL that when addressed will launch a new execution of a flow. + * @param flowId the id of the flow definition + * @param input the input to pass the new flow execution + * @param request the current request + * @return the flow definition url + */ + public String createFlowDefinitionUrl(String flowId, AttributeMap input, HttpServletRequest request); + + /** + * Create a URL that when addressed will resume an existing execution of a flow. + * @param flowExecutionKey the flow execution key that is paused and waiting to be resumed + * @param request the current request + * @return the flow execution url + */ + public String createFlowExecutionUrl(String flowId, String flowExecutionKey, HttpServletRequest request); +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/ParameterBasedFlowUrlHandler.java b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/ParameterBasedFlowUrlHandler.java new file mode 100644 index 00000000..5b38f130 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/ParameterBasedFlowUrlHandler.java @@ -0,0 +1,85 @@ +package org.springframework.webflow.context.servlet; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Iterator; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.webflow.core.collection.AttributeMap; + +public class ParameterBasedFlowUrlHandler implements FlowUrlHandler { + + private static final String DEFAULT_URL_ENCODING_SCHEME = "UTF-8"; + + private String urlEncodingScheme = DEFAULT_URL_ENCODING_SCHEME; + + public String getFlowExecutionKey(HttpServletRequest request) { + return request.getParameter("_flowExecutionKey"); + } + + public String getFlowId(HttpServletRequest request) { + return request.getParameter("_flowId"); + } + + public String createFlowExecutionUrl(String flowId, String flowExecutionKey, HttpServletRequest request) { + StringBuffer url = new StringBuffer(); + url.append(request.getContextPath()); + url.append(request.getServletPath()); + if (request.getPathInfo() != null) { + url.append(request.getPathInfo()); + } + url.append('?'); + appendQueryParameter(url, "_flowId", flowId); + url.append('&'); + appendQueryParameter(url, "_flowExecutionKey", flowExecutionKey); + return url.toString(); + } + + public String createFlowDefinitionUrl(String flowId, AttributeMap input, HttpServletRequest request) { + StringBuffer url = new StringBuffer(); + url.append(request.getContextPath()); + url.append(request.getServletPath()); + if (request.getPathInfo() != null) { + url.append(request.getPathInfo()); + } + url.append('?'); + appendQueryParameter(url, "_flowId", flowId); + if (input != null && !input.isEmpty()) { + url.append('&'); + appendQueryParameters(url, input.asMap()); + } + return url.toString(); + } + + private void appendQueryParameters(StringBuffer url, Map parameters) { + Iterator entries = parameters.entrySet().iterator(); + while (entries.hasNext()) { + Map.Entry entry = (Map.Entry) entries.next(); + appendQueryParameter(url, entry.getKey(), entry.getValue()); + if (entries.hasNext()) { + url.append('&'); + } + } + } + + private void appendQueryParameter(StringBuffer url, Object key, Object value) { + String encodedKey = encode(key); + String encodedValue = encode(value); + url.append(encodedKey).append('=').append(encodedValue); + } + + private String encode(Object value) { + return value != null ? urlEncode(String.valueOf(value)) : ""; + } + + private String urlEncode(String value) { + try { + return URLEncoder.encode(String.valueOf(value), urlEncodingScheme); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Cannot url encode " + value); + } + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/executor/FlowExecutionResult.java b/spring-webflow/src/main/java/org/springframework/webflow/executor/FlowExecutionResult.java new file mode 100644 index 00000000..d0afa471 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/executor/FlowExecutionResult.java @@ -0,0 +1,51 @@ +package org.springframework.webflow.executor; + +import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.execution.Event; + +public class FlowExecutionResult { + private String flowId; + private String flowExecutionKey; + private String outcome; + private AttributeMap output; + + private FlowExecutionResult(String flowId, String flowExecutionKey, String outcome, AttributeMap output) { + this.flowId = flowId; + this.flowExecutionKey = flowExecutionKey; + this.outcome = outcome; + this.output = output; + } + + public static FlowExecutionResult createPausedResult(String flowId, String flowExecutionKey) { + return new FlowExecutionResult(flowId, flowExecutionKey, null, null); + } + + public static FlowExecutionResult createEndedResult(String flowId, Event outcome) { + return new FlowExecutionResult(flowId, null, outcome.getId(), outcome.getAttributes()); + } + + public String getFlowId() { + return flowId; + } + + public boolean paused() { + return flowExecutionKey != null; + } + + public String getPausedKey() { + return flowExecutionKey; + } + + public boolean ended() { + return flowExecutionKey == null; + } + + public String getEndedOutcome() { + return outcome; + } + + public AttributeMap getEndedOutput() { + return output; + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/FlowHandler.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/FlowHandler.java new file mode 100644 index 00000000..aec5f351 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/FlowHandler.java @@ -0,0 +1,20 @@ +package org.springframework.webflow.mvc; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.servlet.ModelAndView; +import org.springframework.webflow.core.FlowException; +import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.core.collection.MutableAttributeMap; + +public interface FlowHandler { + public String getFlowId(); + + public MutableAttributeMap createExecutionInputMap(HttpServletRequest request); + + public ModelAndView handleExecutionOutcome(String outcome, AttributeMap output, HttpServletRequest request, + HttpServletResponse response); + + public ModelAndView handleException(FlowException e, HttpServletRequest request, HttpServletResponse response); +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/FlowHandlerAdapter.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/FlowHandlerAdapter.java new file mode 100644 index 00000000..e8b43372 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/FlowHandlerAdapter.java @@ -0,0 +1,142 @@ +package org.springframework.webflow.mvc; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.context.support.WebApplicationObjectSupport; +import org.springframework.web.servlet.HandlerAdapter; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.webflow.context.servlet.DefaultFlowUrlHandler; +import org.springframework.webflow.context.servlet.FlowUrlHandler; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.core.FlowException; +import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.core.collection.LocalAttributeMap; +import org.springframework.webflow.core.collection.MutableAttributeMap; +import org.springframework.webflow.execution.repository.NoSuchFlowExecutionException; +import org.springframework.webflow.executor.FlowExecutionResult; +import org.springframework.webflow.executor.FlowExecutor; + +public class FlowHandlerAdapter extends WebApplicationObjectSupport implements HandlerAdapter { + + private FlowExecutor flowExecutor; + private FlowUrlHandler urlHandler; + + public FlowHandlerAdapter(FlowExecutor flowExecutor) { + this.flowExecutor = flowExecutor; + this.urlHandler = new DefaultFlowUrlHandler(); + } + + public FlowUrlHandler getFlowUrlHandler() { + return urlHandler; + } + + public void setFlowUrlHandler(FlowUrlHandler urlHandler) { + this.urlHandler = urlHandler; + } + + public boolean supports(Object handler) { + return handler instanceof FlowHandler; + } + + public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + FlowHandler flowHandler = (FlowHandler) handler; + String flowExecutionKey = urlHandler.getFlowExecutionKey(request); + if (flowExecutionKey != null) { + try { + ServletExternalContext context = createServletExternalContext(request, response); + FlowExecutionResult result = flowExecutor.resumeExecution(flowExecutionKey, context); + return handleFlowExecutionResult(result, context, request, response, flowHandler); + } catch (FlowException e) { + return handleFlowException(e, request, response, flowHandler); + } + } else { + try { + MutableAttributeMap input = flowHandler.createExecutionInputMap(request); + ServletExternalContext context = createServletExternalContext(request, response); + FlowExecutionResult result = flowExecutor.launchExecution(flowHandler.getFlowId(), input, context); + return handleFlowExecutionResult(result, context, request, response, flowHandler); + } catch (FlowException e) { + return handleFlowException(e, request, response, flowHandler); + } + } + } + + // subclassing hooks + + protected ServletExternalContext createServletExternalContext(HttpServletRequest request, + HttpServletResponse response) { + return new ServletExternalContext(getServletContext(), request, response, urlHandler); + } + + protected MutableAttributeMap defaultFlowExecutionInputMap(HttpServletRequest request) { + return new LocalAttributeMap(request.getParameterMap()); + } + + protected ModelAndView defaultHandleFlowOutcome(String flowId, String outcome, AttributeMap endedOutput, + HttpServletRequest request, HttpServletResponse response) throws IOException { + // by default, just start the flow over passing the output as input + response.sendRedirect(urlHandler.createFlowDefinitionUrl(flowId, endedOutput, request)); + return null; + } + + protected ModelAndView defaultHandleFlowException(String flowId, FlowException e, HttpServletRequest request, + HttpServletResponse response) throws IOException { + if (e instanceof NoSuchFlowExecutionException && flowId != null) { + // by default, attempt to restart the flow + response.sendRedirect(urlHandler.createFlowDefinitionUrl(flowId, null, request)); + return null; + } else { + throw e; + } + } + + // internal helpers + + private ModelAndView handleFlowExecutionResult(FlowExecutionResult result, ServletExternalContext context, + HttpServletRequest request, HttpServletResponse response, FlowHandler handler) throws IOException { + if (result.paused()) { + if (context.flowExecutionRedirectRequested()) { + String url = urlHandler.createFlowExecutionUrl(result.getFlowId(), result.getPausedKey(), request); + response.sendRedirect(url); + return null; + } else if (context.externalRedirectRequested()) { + response.sendRedirect(context.getExternalRedirectUrl()); + return null; + } else { + return null; + } + } else if (result.ended()) { + if (context.flowDefinitionRedirectRequested()) { + String flowId = context.getFlowRedirectFlowId(); + AttributeMap input = context.getFlowRedirectFlowInput(); + response.sendRedirect(urlHandler.createFlowDefinitionUrl(flowId, input, request)); + return null; + } else if (context.externalRedirectRequested()) { + response.sendRedirect(context.getExternalRedirectUrl()); + return null; + } else { + ModelAndView mv = handler.handleExecutionOutcome(result.getEndedOutcome(), result.getEndedOutput(), + request, response); + return mv != null ? mv : defaultHandleFlowOutcome(handler.getFlowId(), result.getEndedOutcome(), result + .getEndedOutput(), request, response); + } + } else { + throw new IllegalStateException("Execution result should have been one of [paused] or [ended]"); + } + } + + private ModelAndView handleFlowException(FlowException e, HttpServletRequest request, HttpServletResponse response, + FlowHandler handler) throws IOException { + ModelAndView result = handler.handleException(e, request, response); + return result != null ? result : defaultHandleFlowException(handler.getFlowId(), e, request, response); + } + + public long getLastModified(HttpServletRequest request, Object handler) { + return -1; + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/GeneratedFlowExecutionKey.java b/spring-webflow/src/main/java/org/springframework/webflow/test/GeneratedFlowExecutionKey.java new file mode 100644 index 00000000..f4723324 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/GeneratedFlowExecutionKey.java @@ -0,0 +1,60 @@ +/* + * Copyright 2004-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.webflow.test; + +import org.springframework.webflow.execution.FlowExecutionKey; + +/** + * A simple flow execution key implementation. New instances of this class get their values from a sequence encapsulated + * as a static private variable of this class. + * + * @author Keith Donald + */ +public class GeneratedFlowExecutionKey extends FlowExecutionKey { + + private static int nextKey = 1; + + private int value; + + /** + * Creates a new mock flow execution key. + */ + public GeneratedFlowExecutionKey() { + this.value = nextKey(); + } + + public boolean equals(Object o) { + if (!(o instanceof GeneratedFlowExecutionKey)) { + return false; + } + GeneratedFlowExecutionKey key = (GeneratedFlowExecutionKey) o; + return value == key.value; + } + + public int hashCode() { + return value * 29; + } + + public String toString() { + return String.valueOf(value); + } + + private static int nextKey() { + int key = nextKey; + nextKey++; + return key; + } +} \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/DefaultFlowUrlHandlerTests.java b/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/DefaultFlowUrlHandlerTests.java new file mode 100644 index 00000000..c3a81c35 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/DefaultFlowUrlHandlerTests.java @@ -0,0 +1,69 @@ +package org.springframework.webflow.context.servlet; + +import java.util.LinkedHashMap; + +import junit.framework.TestCase; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.webflow.context.servlet.DefaultFlowUrlHandler; +import org.springframework.webflow.core.collection.CollectionUtils; +import org.springframework.webflow.core.collection.LocalAttributeMap; + +public class DefaultFlowUrlHandlerTests extends TestCase { + private DefaultFlowUrlHandler urlHandler = new DefaultFlowUrlHandler(); + private MockHttpServletRequest request = new MockHttpServletRequest(); + + public void testGetFlowId() { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + assertEquals("foo", urlHandler.getFlowId(request)); + } + + public void testGetFlowExecutionKey() { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.addParameter("execution", "12345"); + assertEquals("12345", urlHandler.getFlowExecutionKey(request)); + } + + public void testCreateFlowDefinitionUrl() { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + String url = urlHandler.createFlowDefinitionUrl("bookHotel", null, request); + assertEquals("/springtravel/app/bookHotel", url); + } + + public void testCreateFlowDefinitionUrlEmptyInput() { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + String url = urlHandler.createFlowDefinitionUrl("bookHotel", CollectionUtils.EMPTY_ATTRIBUTE_MAP, request); + assertEquals("/springtravel/app/bookHotel", url); + } + + public void testCreateFlowDefinitionUrlWithFlowInput() { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + LocalAttributeMap input = new LocalAttributeMap(new LinkedHashMap()); + input.put("foo", "bar"); + input.put("bar", "needs encoding"); + input.put("baz", new Integer(1)); + input.put("boop", null); + String url = urlHandler.createFlowDefinitionUrl("bookHotel", input, request); + assertEquals("/springtravel/app/bookHotel?foo=bar&bar=needs+encoding&baz=1&boop=", url); + } + + public void testCreateFlowExecutionUrl() { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + String url = urlHandler.createFlowExecutionUrl("foo", "12345", request); + assertEquals("/springtravel/app/foo?execution=12345", url); + } +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/ParameterBasedFlowUrlHandlerTests.java b/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/ParameterBasedFlowUrlHandlerTests.java new file mode 100644 index 00000000..3d49c2b0 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/ParameterBasedFlowUrlHandlerTests.java @@ -0,0 +1,62 @@ +package org.springframework.webflow.context.servlet; + +import java.util.LinkedHashMap; + +import junit.framework.TestCase; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.webflow.context.servlet.ParameterBasedFlowUrlHandler; +import org.springframework.webflow.core.collection.CollectionUtils; +import org.springframework.webflow.core.collection.LocalAttributeMap; + +public class ParameterBasedFlowUrlHandlerTests extends TestCase { + private ParameterBasedFlowUrlHandler urlHandler = new ParameterBasedFlowUrlHandler(); + private MockHttpServletRequest request = new MockHttpServletRequest(); + + public void testGetFlowId() { + request.addParameter("_flowId", "foo"); + assertEquals("foo", urlHandler.getFlowId(request)); + } + + public void testGetFlowExecutionKey() { + request.addParameter("_flowExecutionKey", "12345"); + assertEquals("12345", urlHandler.getFlowExecutionKey(request)); + } + + public void testCreateFlowDefinitionUrl() { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/flows"); + String url = urlHandler.createFlowDefinitionUrl("bookHotel", null, request); + assertEquals("/springtravel/app/flows?_flowId=bookHotel", url); + } + + public void testCreateFlowDefinitionUrlEmptyInput() { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/flows"); + String url = urlHandler.createFlowDefinitionUrl("bookHotel", CollectionUtils.EMPTY_ATTRIBUTE_MAP, request); + assertEquals("/springtravel/app/flows?_flowId=bookHotel", url); + } + + public void testCreateFlowDefinitionUrlWithFlowInput() { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/flows"); + LocalAttributeMap input = new LocalAttributeMap(new LinkedHashMap()); + input.put("foo", "bar"); + input.put("bar", "needs encoding"); + input.put("baz", new Integer(1)); + input.put("boop", null); + String url = urlHandler.createFlowDefinitionUrl("bookHotel", input, request); + assertEquals("/springtravel/app/flows?_flowId=bookHotel&foo=bar&bar=needs+encoding&baz=1&boop=", url); + } + + public void testCreateFlowExecutionUrl() { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/flows"); + String url = urlHandler.createFlowExecutionUrl("bookHotel", "12345", request); + assertEquals("/springtravel/app/flows?_flowId=bookHotel&_flowExecutionKey=12345", url); + } +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/mvc/FlowControllerTests.java b/spring-webflow/src/test/java/org/springframework/webflow/mvc/FlowControllerTests.java new file mode 100644 index 00000000..2c01da55 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/mvc/FlowControllerTests.java @@ -0,0 +1,354 @@ +package org.springframework.webflow.mvc; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.easymock.EasyMock; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.support.StaticWebApplicationContext; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.core.FlowException; +import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.core.collection.LocalAttributeMap; +import org.springframework.webflow.core.collection.MutableAttributeMap; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.repository.NoSuchFlowExecutionException; +import org.springframework.webflow.executor.FlowExecutionResult; +import org.springframework.webflow.executor.FlowExecutor; +import org.springframework.webflow.test.MockFlowExecutionKey; + +public class FlowControllerTests extends TestCase { + private FlowController controller; + private FlowExecutor executor; + private MockServletContext servletContext; + private MockHttpServletRequest request; + private MockHttpServletResponse response; + private ServletExternalContext context; + + protected void setUp() { + executor = (FlowExecutor) EasyMock.createMock(FlowExecutor.class); + controller = new FlowController(executor) { + protected ServletExternalContext createServletExternalContext(HttpServletRequest request, + HttpServletResponse response) { + return context; + } + }; + servletContext = new MockServletContext(); + request = new MockHttpServletRequest(); + response = new MockHttpServletResponse(); + context = new ServletExternalContext(servletContext, request, response, controller.getFlowUrlHandler()); + controller.setApplicationContext(new StaticWebApplicationContext()); + controller.setServletContext(servletContext); + } + + public void testLaunchFlowRequest() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + Map parameters = new HashMap(); + request.setParameters(parameters); + executor.launchExecution("foo", new LocalAttributeMap(parameters), context); + FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "12345"); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handleRequest(request, response); + assertNull(mv); + EasyMock.verify(new Object[] { executor }); + } + + public void testLaunchFlowRequestEndsAfterProcessing() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + Map parameters = new HashMap(); + request.setParameters(parameters); + executor.launchExecution("foo", new LocalAttributeMap(parameters), context); + LocalAttributeMap output = new LocalAttributeMap(); + output.put("bar", "baz"); + Event outcome = new Event(this, "finish", output); + FlowExecutionResult result = FlowExecutionResult.createEndedResult("foo", outcome); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handleRequest(request, response); + assertNull(mv); + assertEquals("/springtravel/app/foo?bar=baz", response.getRedirectedUrl()); + EasyMock.verify(new Object[] { executor }); + } + + public void testResumeFlowRequest() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("POST"); + request.addParameter("execution", "12345"); + Map parameters = new HashMap(); + request.setParameters(parameters); + executor.resumeExecution("12345", context); + FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "123456"); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handleRequest(request, response); + assertNull(mv); + EasyMock.verify(new Object[] { executor }); + } + + public void testResumeFlowRequestEndsAfterProcessing() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("POST"); + request.addParameter("execution", "12345"); + Map parameters = new HashMap(); + request.setParameters(parameters); + executor.resumeExecution("12345", context); + LocalAttributeMap output = new LocalAttributeMap(); + output.put("bar", "baz"); + Event outcome = new Event(this, "finish", output); + FlowExecutionResult result = FlowExecutionResult.createEndedResult("foo", outcome); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handleRequest(request, response); + assertNull(mv); + assertEquals("/springtravel/app/foo?bar=baz", response.getRedirectedUrl()); + EasyMock.verify(new Object[] { executor }); + } + + public void testLaunchFlowWithExecutionRedirect() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + Map parameters = new HashMap(); + request.setParameters(parameters); + context.requestFlowExecutionRedirect(); + executor.launchExecution("foo", new LocalAttributeMap(parameters), context); + FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "12345"); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handleRequest(request, response); + assertNull(mv); + EasyMock.verify(new Object[] { executor }); + assertEquals("/springtravel/app/foo?execution=12345", response.getRedirectedUrl()); + EasyMock.verify(new Object[] { executor }); + } + + public void testLaunchFlowWithDefinitionRedirect() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + Map parameters = new HashMap(); + request.setParameters(parameters); + LocalAttributeMap input = new LocalAttributeMap(); + input.put("baz", "boop"); + context.requestFlowDefinitionRedirect("bar", input); + executor.launchExecution("foo", new LocalAttributeMap(parameters), context); + LocalAttributeMap output = new LocalAttributeMap(); + output.put("bar", "baz"); + Event outcome = new Event(this, "finish", output); + FlowExecutionResult result = FlowExecutionResult.createEndedResult("foo", outcome); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handleRequest(request, response); + assertNull(mv); + EasyMock.verify(new Object[] { executor }); + assertEquals("/springtravel/app/bar?baz=boop", response.getRedirectedUrl()); + EasyMock.verify(new Object[] { executor }); + } + + public void testLaunchFlowWithExternalRedirect() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + Map parameters = new HashMap(); + request.setParameters(parameters); + context.requestExternalRedirect("http://www.paypal.com"); + executor.launchExecution("foo", new LocalAttributeMap(parameters), context); + FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "12345"); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handleRequest(request, response); + assertNull(mv); + EasyMock.verify(new Object[] { executor }); + assertEquals("http://www.paypal.com", response.getRedirectedUrl()); + EasyMock.verify(new Object[] { executor }); + } + + public void testDefaultHandleFlowException() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + Map parameters = new HashMap(); + request.setParameters(parameters); + executor.launchExecution("foo", new LocalAttributeMap(parameters), context); + FlowException flowException = new FlowException("Error") { + }; + EasyMock.expectLastCall().andThrow(flowException); + EasyMock.replay(new Object[] { executor }); + try { + controller.handleRequest(request, response); + fail("Should have thrown exception"); + } catch (FlowException e) { + assertEquals(flowException, e); + } + EasyMock.verify(new Object[] { executor }); + } + + public void testDefaultHandleNoSuchFlowExecutionException() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + request.addParameter("execution", "12345"); + executor.resumeExecution("12345", context); + FlowException flowException = new NoSuchFlowExecutionException(new MockFlowExecutionKey("12345"), null); + EasyMock.expectLastCall().andThrow(flowException); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handleRequest(request, response); + assertNull(mv); + assertEquals("/springtravel/app/foo", response.getRedirectedUrl()); + EasyMock.verify(new Object[] { executor }); + } + + public void testLaunchFlowWithCustomFlowHandler() throws Exception { + final LocalAttributeMap input = new LocalAttributeMap(); + input.put("bar", "boop"); + controller.registerFlowHandler(new FlowHandler() { + public String getFlowId() { + return "foo"; + } + + public MutableAttributeMap createExecutionInputMap(HttpServletRequest request) { + return input; + } + + public ModelAndView handleExecutionOutcome(String outcome, AttributeMap output, HttpServletRequest request, + HttpServletResponse response) { + return null; + } + + public ModelAndView handleException(FlowException e, HttpServletRequest request, + HttpServletResponse response) { + return null; + } + }); + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + executor.launchExecution("foo", input, context); + FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "12345"); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handleRequest(request, response); + assertNull(mv); + EasyMock.verify(new Object[] { executor }); + } + + public void testHandleFlowOutcomeCustomFlowHandler() throws Exception { + final LocalAttributeMap input = new LocalAttributeMap(); + input.put("bar", "boop"); + controller.registerFlowHandler(new FlowHandler() { + public String getFlowId() { + return "foo"; + } + + public MutableAttributeMap createExecutionInputMap(HttpServletRequest request) { + return input; + } + + public ModelAndView handleExecutionOutcome(String outcome, AttributeMap output, HttpServletRequest request, + HttpServletResponse response) { + assertEquals("finish", outcome); + assertEquals("baz", output.get("bar")); + assertEquals(FlowControllerTests.this.request, request); + assertEquals(FlowControllerTests.this.response, response); + return null; + } + + public ModelAndView handleException(FlowException e, HttpServletRequest request, + HttpServletResponse response) { + return null; + } + }); + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + executor.launchExecution("foo", input, context); + LocalAttributeMap output = new LocalAttributeMap(); + output.put("bar", "baz"); + Event outcome = new Event(this, "finish", output); + FlowExecutionResult result = FlowExecutionResult.createEndedResult("foo", outcome); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handleRequest(request, response); + assertNull(mv); + assertEquals("/springtravel/app/foo?bar=baz", response.getRedirectedUrl()); + EasyMock.verify(new Object[] { executor }); + } + + public void testHandleFlowExceptionCustomFlowHandler() throws Exception { + final FlowException flowException = new FlowException("Error") { + }; + + controller.registerFlowHandler(new FlowHandler() { + public String getFlowId() { + return "foo"; + } + + public MutableAttributeMap createExecutionInputMap(HttpServletRequest request) { + return null; + } + + public ModelAndView handleExecutionOutcome(String outcome, AttributeMap output, HttpServletRequest request, + HttpServletResponse response) { + return null; + } + + public ModelAndView handleException(FlowException e, HttpServletRequest request, + HttpServletResponse response) { + assertEquals(flowException, e); + return null; + } + }); + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + executor.launchExecution("foo", null, context); + EasyMock.expectLastCall().andThrow(flowException); + EasyMock.replay(new Object[] { executor }); + try { + controller.handleRequest(request, response); + fail("Should have thrown exception"); + } catch (FlowException e) { + assertEquals(flowException, e); + } + EasyMock.verify(new Object[] { executor }); + } +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/mvc/FlowHandlerAdapterTests.java b/spring-webflow/src/test/java/org/springframework/webflow/mvc/FlowHandlerAdapterTests.java new file mode 100644 index 00000000..6dc5272e --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/mvc/FlowHandlerAdapterTests.java @@ -0,0 +1,300 @@ +package org.springframework.webflow.mvc; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.easymock.EasyMock; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.support.StaticWebApplicationContext; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.core.FlowException; +import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.core.collection.LocalAttributeMap; +import org.springframework.webflow.core.collection.MutableAttributeMap; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.repository.NoSuchFlowExecutionException; +import org.springframework.webflow.executor.FlowExecutionResult; +import org.springframework.webflow.executor.FlowExecutor; +import org.springframework.webflow.test.MockFlowExecutionKey; + +public class FlowHandlerAdapterTests extends TestCase { + private FlowHandlerAdapter controller; + private FlowExecutor executor; + private MockServletContext servletContext; + private MockHttpServletRequest request; + private MockHttpServletResponse response; + private ServletExternalContext context; + private FlowHandler flowHandler; + private LocalAttributeMap flowInput; + private boolean handleException; + private boolean handleExecutionOutcome; + + protected void setUp() { + executor = (FlowExecutor) EasyMock.createMock(FlowExecutor.class); + controller = new FlowHandlerAdapter(executor) { + protected ServletExternalContext createServletExternalContext(HttpServletRequest request, + HttpServletResponse response) { + return context; + } + }; + servletContext = new MockServletContext(); + request = new MockHttpServletRequest(); + response = new MockHttpServletResponse(); + context = new ServletExternalContext(servletContext, request, response, controller.getFlowUrlHandler()); + controller.setApplicationContext(new StaticWebApplicationContext()); + controller.setServletContext(servletContext); + flowHandler = new FlowHandler() { + public MutableAttributeMap createExecutionInputMap(HttpServletRequest request) { + assertEquals(FlowHandlerAdapterTests.this.request, request); + return flowInput; + } + + public String getFlowId() { + return "foo"; + } + + public ModelAndView handleException(FlowException e, HttpServletRequest request, + HttpServletResponse response) { + if (handleException) { + return new ModelAndView("error"); + } else { + return null; + } + } + + public ModelAndView handleExecutionOutcome(String outcome, AttributeMap output, HttpServletRequest request, + HttpServletResponse response) { + if (handleExecutionOutcome) { + return new ModelAndView("redirect:/home"); + } else { + return null; + } + } + + }; + } + + public void testLaunchFlowRequest() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/whatever"); + request.setRequestURI("/springtravel/app/whatever"); + request.setMethod("GET"); + executor.launchExecution("foo", flowInput, context); + FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "12345"); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handle(request, response, flowHandler); + assertNull(mv); + EasyMock.verify(new Object[] { executor }); + } + + public void testLaunchFlowRequestEndsAfterProcessing() 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); + executor.launchExecution("foo", null, context); + LocalAttributeMap output = new LocalAttributeMap(); + output.put("bar", "baz"); + Event outcome = new Event(this, "finish", output); + FlowExecutionResult result = FlowExecutionResult.createEndedResult("foo", outcome); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handle(request, response, flowHandler); + assertNull(mv); + assertEquals("/springtravel/app/foo?bar=baz", response.getRedirectedUrl()); + EasyMock.verify(new Object[] { executor }); + } + + public void testResumeFlowRequest() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("POST"); + request.addParameter("execution", "12345"); + Map parameters = new HashMap(); + request.setParameters(parameters); + executor.resumeExecution("12345", context); + FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "123456"); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handle(request, response, flowHandler); + assertNull(mv); + EasyMock.verify(new Object[] { executor }); + } + + public void testResumeFlowRequestEndsAfterProcessing() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("POST"); + request.addParameter("execution", "12345"); + Map parameters = new HashMap(); + request.setParameters(parameters); + executor.resumeExecution("12345", context); + LocalAttributeMap output = new LocalAttributeMap(); + output.put("bar", "baz"); + Event outcome = new Event(this, "finish", output); + FlowExecutionResult result = FlowExecutionResult.createEndedResult("foo", outcome); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handle(request, response, flowHandler); + assertNull(mv); + assertEquals("/springtravel/app/foo?bar=baz", response.getRedirectedUrl()); + EasyMock.verify(new Object[] { executor }); + } + + public void testLaunchFlowWithExecutionRedirect() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + Map parameters = new HashMap(); + request.setParameters(parameters); + context.requestFlowExecutionRedirect(); + executor.launchExecution("foo", null, context); + FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "12345"); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handle(request, response, flowHandler); + assertNull(mv); + EasyMock.verify(new Object[] { executor }); + assertEquals("/springtravel/app/foo?execution=12345", response.getRedirectedUrl()); + EasyMock.verify(new Object[] { executor }); + } + + public void testLaunchFlowWithDefinitionRedirect() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + Map parameters = new HashMap(); + request.setParameters(parameters); + LocalAttributeMap input = new LocalAttributeMap(); + input.put("baz", "boop"); + context.requestFlowDefinitionRedirect("bar", input); + executor.launchExecution("foo", null, context); + LocalAttributeMap output = new LocalAttributeMap(); + output.put("bar", "baz"); + Event outcome = new Event(this, "finish", output); + FlowExecutionResult result = FlowExecutionResult.createEndedResult("foo", outcome); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handle(request, response, flowHandler); + assertNull(mv); + EasyMock.verify(new Object[] { executor }); + assertEquals("/springtravel/app/bar?baz=boop", response.getRedirectedUrl()); + EasyMock.verify(new Object[] { executor }); + } + + public void testLaunchFlowWithExternalRedirect() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + Map parameters = new HashMap(); + request.setParameters(parameters); + context.requestExternalRedirect("http://www.paypal.com"); + executor.launchExecution("foo", null, context); + FlowExecutionResult result = FlowExecutionResult.createPausedResult("foo", "12345"); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handle(request, response, flowHandler); + assertNull(mv); + EasyMock.verify(new Object[] { executor }); + assertEquals("http://www.paypal.com", response.getRedirectedUrl()); + EasyMock.verify(new Object[] { executor }); + } + + public void testDefaultHandleFlowException() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + Map parameters = new HashMap(); + request.setParameters(parameters); + executor.launchExecution("foo", null, context); + FlowException flowException = new FlowException("Error") { + }; + EasyMock.expectLastCall().andThrow(flowException); + EasyMock.replay(new Object[] { executor }); + try { + controller.handle(request, response, flowHandler); + fail("Should have thrown exception"); + } catch (FlowException e) { + assertEquals(flowException, e); + } + EasyMock.verify(new Object[] { executor }); + } + + public void testDefaultHandleNoSuchFlowExecutionException() throws Exception { + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + request.addParameter("execution", "12345"); + executor.resumeExecution("12345", context); + FlowException flowException = new NoSuchFlowExecutionException(new MockFlowExecutionKey("12345"), null); + EasyMock.expectLastCall().andThrow(flowException); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handle(request, response, flowHandler); + assertNull(mv); + assertEquals("/springtravel/app/foo", response.getRedirectedUrl()); + EasyMock.verify(new Object[] { executor }); + } + + public void testHandleFlowOutcomeCustomFlowHandler() throws Exception { + handleExecutionOutcome = true; + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + executor.launchExecution("foo", flowInput, context); + LocalAttributeMap output = new LocalAttributeMap(); + output.put("bar", "baz"); + Event outcome = new Event(this, "finish", output); + FlowExecutionResult result = FlowExecutionResult.createEndedResult("foo", outcome); + EasyMock.expectLastCall().andReturn(result); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handle(request, response, flowHandler); + assertNotNull(mv); + EasyMock.verify(new Object[] { executor }); + } + + public void testHandleFlowExceptionCustomFlowHandler() throws Exception { + handleException = true; + final FlowException flowException = new FlowException("Error") { + }; + request.setContextPath("/springtravel"); + request.setServletPath("/app"); + request.setPathInfo("/foo"); + request.setRequestURI("/springtravel/app/foo"); + request.setMethod("GET"); + executor.launchExecution("foo", null, context); + EasyMock.expectLastCall().andThrow(flowException); + EasyMock.replay(new Object[] { executor }); + ModelAndView mv = controller.handle(request, response, flowHandler); + assertNotNull(mv); + EasyMock.verify(new Object[] { executor }); + } +}