diff --git a/build-spring-webflow/resources/changelog.txt b/build-spring-webflow/resources/changelog.txt index 22085cb5..a0cec49b 100644 --- a/build-spring-webflow/resources/changelog.txt +++ b/build-spring-webflow/resources/changelog.txt @@ -15,6 +15,8 @@ Bug Fixes * Fixed bug where Errors nestedPath attribute was not respected in views rendered by Web Flow (SWF-973) * Fixed bug where Errors nestedPath attribute was not respected when using Errors API programatically within a Validator called by Web Flow (SWF-973) * Made MessageCodesResolver pluggable on MvcViewFactoryCreator; configure a DefaultMessageCodesResolver to resolve error message codes consistent with default Spring MVC behavior (SWF-977) +* Made FlowHandlerAdapter#sendRedirect method protected, allowing overriding of web flow system redirect behavior; for example, to redirect to a SSL channel. +* Made DefaultFlowUrlHandler consistent with Spring MVC RedirectView behavior for URL encoding of flow URL parameters; URL encoding scheme is also now pluggable. * Introduced createDefaultFlowHandler hook in FlowHandlerMapping, allowing for customizing the Default FlowHandler implementation application wide (SWF-994) Improvements 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 index ffb4b566..7485f2be 100644 --- 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 @@ -23,6 +23,7 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.springframework.util.StringUtils; +import org.springframework.web.util.WebUtils; import org.springframework.webflow.core.collection.AttributeMap; /** @@ -50,16 +51,23 @@ import org.springframework.webflow.core.collection.AttributeMap; * execution "e1s1" of the "hotels/booking" flow. * * @author Keith Donald - * */ public class DefaultFlowUrlHandler implements FlowUrlHandler { - private static final String DEFAULT_URL_ENCODING_SCHEME = "UTF-8"; + private static final String FLOW_EXECUTION_KEY_PARAMETER = "execution"; - private String urlEncodingScheme = DEFAULT_URL_ENCODING_SCHEME; + private String encodingScheme; + + /** + * Set the character encoding scheme for flow urls. Default is the request's encoding scheme (which is ISO-8859-1 if + * not specified otherwise). + */ + public void setEncodingScheme(String encodingScheme) { + this.encodingScheme = encodingScheme; + } public String getFlowExecutionKey(HttpServletRequest request) { - return request.getParameter("execution"); + return request.getParameter(FLOW_EXECUTION_KEY_PARAMETER); } public String getFlowId(HttpServletRequest request) { @@ -90,7 +98,7 @@ public class DefaultFlowUrlHandler implements FlowUrlHandler { StringBuffer url = new StringBuffer(); url.append(request.getRequestURI()); url.append('?'); - appendQueryParameter(url, "execution", flowExecutionKey); + appendQueryParameter(url, FLOW_EXECUTION_KEY_PARAMETER, flowExecutionKey, getEncodingScheme(request)); return url.toString(); } @@ -118,35 +126,49 @@ public class DefaultFlowUrlHandler implements FlowUrlHandler { } if (input != null && !input.isEmpty()) { url.append('?'); - appendQueryParameters(url, input.asMap()); + appendQueryParameters(url, input.asMap(), getEncodingScheme(request)); } return url.toString(); } - private void appendQueryParameters(StringBuffer url, Map parameters) { + // internal helpers + + private String getEncodingScheme(HttpServletRequest request) { + if (encodingScheme != null) { + return encodingScheme; + } else { + String encodingScheme = request.getCharacterEncoding(); + if (encodingScheme == null) { + encodingScheme = WebUtils.DEFAULT_CHARACTER_ENCODING; + } + return encodingScheme; + } + } + + private void appendQueryParameters(StringBuffer url, Map parameters, String encodingScheme) { Iterator entries = parameters.entrySet().iterator(); while (entries.hasNext()) { Map.Entry entry = (Map.Entry) entries.next(); - appendQueryParameter(url, entry.getKey(), entry.getValue()); + appendQueryParameter(url, entry.getKey(), entry.getValue(), encodingScheme); if (entries.hasNext()) { url.append('&'); } } } - private void appendQueryParameter(StringBuffer url, Object key, Object value) { - String encodedKey = encode(key); - String encodedValue = encode(value); + private void appendQueryParameter(StringBuffer url, Object key, Object value, String encodingScheme) { + String encodedKey = encode(key, encodingScheme); + String encodedValue = encode(value, encodingScheme); url.append(encodedKey).append('=').append(encodedValue); } - private String encode(Object value) { - return value != null ? urlEncode(String.valueOf(value)) : ""; + private String encode(Object value, String encodingScheme) { + return value != null ? urlEncode(value.toString(), encodingScheme) : ""; } - private String urlEncode(String value) { + private String urlEncode(String value, String encodingScheme) { try { - return URLEncoder.encode(String.valueOf(value), urlEncodingScheme); + return URLEncoder.encode(value, encodingScheme); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException("Cannot url encode " + value); } 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 9bde3e1a..1863244c 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 @@ -53,6 +53,8 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd private static final Log logger = LogFactory.getLog(FlowHandlerAdapter.class); + private static final String REFERER_FLOW_EXECUTION_ATTRIBUTE = "refererExecution"; + private static final String SERVLET_RELATIVE_LOCATION_PREFIX = "servletRelative:"; private static final String CONTEXT_RELATIVE_LOCATION_PREFIX = "contextRelative:"; @@ -133,6 +135,14 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd this.ajaxHandler = ajaxHandler; } + /** + * Whether redirect sent by this handler adapter should be compatible with HTTP 1.0 clients. + * @return true if so, false otherwise + */ + public boolean getRedirectHttp10Compatible() { + return redirectHttp10Compatible; + } + /** * Set whether redirects sent by this handler adapter should be compatible with HTTP 1.0 clients. *

@@ -189,6 +199,10 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd return null; } + public long getLastModified(HttpServletRequest request, Object handler) { + return -1; + } + // subclassing hooks /** @@ -206,7 +220,7 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd /** * The default algorithm to determine the id of the flow to launch from the current request. Only called if * {@link FlowHandler#getFlowId()} returns null. This implementation delegates to the configured - * {@link FlowUrlHandler#getFlowId(HttpServletRequest)}. + * {@link FlowUrlHandler#getFlowId(HttpServletRequest)}. Subclasses may override. * @param request the current request */ protected String defaultGetFlowId(HttpServletRequest request) { @@ -216,7 +230,7 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd /** * The default algorithm to create the flow execution input map. Only called if * {@link FlowHandler#createExecutionInputMap(HttpServletRequest)} returns null. This implementation exposes all - * current request parameters as flow execution input attributes. + * current request parameters as flow execution input attributes. Subclasses may override. * @param request the current request */ protected MutableAttributeMap defaultCreateFlowExecutionInputMap(HttpServletRequest request) { @@ -243,7 +257,7 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd * The default algorithm for handling a flow execution outcome. Only called if * {@link FlowHandler#handleExecutionOutcome(FlowExecutionOutcome, HttpServletRequest, HttpServletResponse)} returns * null. This implementation attempts to start a new execution of the ended flow. Any flow execution output is - * passed as input to the new execution. + * passed as input to the new execution. Subclasses may override. * @param flowId the id of the ended flow * @param outcome the flow execution outcome * @param context ServletExternalContext the completed ServletExternalContext @@ -268,6 +282,7 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd * {@link FlowHandler#handleException(FlowException, HttpServletRequest, HttpServletResponse)} returns null. This * implementation rethrows the exception unless it is a {@link NoSuchFlowExecutionException}. If the exception is a * NoSuchFlowExecutionException, this implementation attempts to start a new execution of the ended or expired flow. + * Subclasses may override. * @param flowId the id of the ended flow * @param e the flow exception * @param request the current request @@ -288,6 +303,31 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd } } + /** + * Sends a redirect to the requested url using {@link HttpServletResponse#sendRedirect(String)}.Called to actually + * perform flow execution redirects, flow definition redirects, and external redirects. Subclasses may override to + * customize general Web Flow system redirect behavior. + * @param url the url to redirect to + * @param request the current request + * @param response the current response + * @throws IOException an exception occurred + */ + protected void sendRedirect(String url, HttpServletRequest request, HttpServletResponse response) + throws IOException { + if (ajaxHandler.isAjaxRequest(request, response)) { + ajaxHandler.sendAjaxRedirect(url, request, response, false); + } else { + if (redirectHttp10Compatible) { + // Always send status code 302. + response.sendRedirect(response.encodeRedirectURL(url)); + } else { + // Correct HTTP status code is 303, in particular for POST requests. + response.setStatus(303); + response.setHeader("Location", response.encodeRedirectURL(url)); + } + } + } + // internal helpers private void handleFlowExecutionResult(FlowExecutionResult result, ServletExternalContext context, @@ -336,7 +376,7 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd String flowId = context.getFlowRedirectFlowId(); MutableAttributeMap input = context.getFlowRedirectFlowInput(); if (result.isPaused()) { - input.put("refererExecution", result.getPausedKey()); + input.put(REFERER_FLOW_EXECUTION_ATTRIBUTE, result.getPausedKey()); } String url = flowUrlHandler.createFlowDefinitionUrl(flowId, input, request); if (logger.isDebugEnabled()) { @@ -385,21 +425,6 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd sendRedirect(url.toString(), request, response); } - private void sendRedirect(String url, HttpServletRequest request, HttpServletResponse response) throws IOException { - if (ajaxHandler.isAjaxRequest(request, response)) { - ajaxHandler.sendAjaxRedirect(url, request, response, false); - } else { - if (redirectHttp10Compatible) { - // Always send status code 302. - response.sendRedirect(response.encodeRedirectURL(url)); - } else { - // Correct HTTP status code is 303, in particular for POST requests. - response.setStatus(303); - response.setHeader("Location", response.encodeRedirectURL(url)); - } - } - } - private void handleFlowException(FlowException e, HttpServletRequest request, HttpServletResponse response, FlowHandler handler) throws IOException { String location = handler.handleException(e, request, response); @@ -410,10 +435,6 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd } } - public long getLastModified(HttpServletRequest request, Object handler) { - return -1; - } - private String getFlowId(FlowHandler handler, HttpServletRequest request) { String flowId = handler.getFlowId(); if (flowId != null) {