From 671744aa622853d5b7b3dd12467f3244b62c8f18 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 9 Sep 2011 16:48:00 +0000 Subject: [PATCH] SPR-6464 Polish FlashMap changes. --- .../web/servlet/DispatcherServlet.java | 3 +- .../springframework/web/servlet/FlashMap.java | 60 ++++++------- .../web/servlet/FlashMapManager.java | 53 ++++++----- .../web/servlet/SmartView.java | 3 +- ...irectAttributesMethodArgumentResolver.java | 6 +- .../mvc/support/RedirectAttributes.java | 78 ++++++++-------- .../support/RedirectAttributesModelMap.java | 68 ++++++-------- .../support/DefaultFlashMapManager.java | 90 +++++++++++-------- .../servlet/support/RequestContextUtils.java | 13 +-- 9 files changed, 185 insertions(+), 189 deletions(-) diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java index 1f7ebf7972..1875eead59 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java @@ -671,7 +671,8 @@ public class DispatcherServlet extends FrameworkServlet { /** * Initialize the {@link FlashMapManager} used by this servlet instance. - *

If no implementation is configured then we default to DefaultFlashMapManager. + *

If no implementation is configured then we default to + * {@code org.springframework.web.servlet.support.DefaultFlashMapManager}. */ private void initFlashMapManager(ApplicationContext context) { try { diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java index 5e80f0f363..72f2155b11 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java @@ -21,7 +21,6 @@ import java.util.LinkedHashMap; import java.util.Map; import org.springframework.beans.BeanUtils; -import org.springframework.util.Assert; /** * A FlashMap provides a way for one request to store attributes intended for @@ -33,11 +32,14 @@ import org.springframework.util.Assert; *

A FlashMap can be set up with a request path and request parameters to * help identify the target request. Without this information, a FlashMap is * made available to the next request, which may or may not be the intended - * result. Before a redirect, the target URL is known and when using the - * {@code org.springframework.web.servlet.view.RedirectView}, FlashMap - * instances are automatically updated with redirect URL information. + * recipient. On a redirect, the target URL is known and for example + * {@code org.springframework.web.servlet.view.RedirectView} has the + * opportunity to automatically update the current FlashMap with target + * URL information . * - *

Annotated controllers will usually not access a FlashMap directly.. TODO + *

Annotated controllers will usually not use this type directly. + * See {@code org.springframework.web.servlet.mvc.support.RedirectAttributes} + * for an overview of using flash attributes in annotated controllers. * * @author Rossen Stoyanchev * @since 3.1 @@ -58,13 +60,6 @@ public class FlashMap extends HashMap implements Comparable implements Comparable implements Comparable params) { if (params != null) { @@ -112,10 +112,8 @@ public class FlashMap extends HashMap implements Comparable implements Comparable implements Comparable implements Comparable implements ComparableA FlashMapManager is invoked at the beginning and at the end of requests. + * For each request it retrieves an "input" FlashMap with attributes passed + * from a previous request (if any) and creates an "output" FlashMap with + * attributes to pass to a subsequent request. "Input" and "output" FlashMap + * instances are exposed as request attributes and are accessible via methods + * in {@code org.springframework.web.servlet.support.RequestContextUtils}. * - *

A FlashMapManager is invoked at the beginning and at the end of a request. - * For each request, it exposes an "input" FlashMap with attributes passed from - * a previous request (if any) and an "output" FlashMap with attributes to pass - * to a subsequent request. Both FlashMap instances are exposed via request - * attributes and can be accessed through methods in - * {@code org.springframework.web.servlet.support.RequestContextUtils}. + *

Annotated controllers are most likely to store and access flash attributes + * through their model. + * See {@code org.springframework.web.servlet.mvc.support.RedirectAttributes}. * * @author Rossen Stoyanchev * @since 3.1 @@ -39,42 +41,39 @@ import javax.servlet.http.HttpServletRequest; public interface FlashMapManager { /** - * Name of request attribute that holds a read-only {@link Map} with - * "input" flash attributes from a previous request (if any). + * Name of request attribute that holds a read-only + * {@code Map} with "input" flash attributes if any. * @see org.springframework.web.servlet.support.RequestContextUtils#getInputFlashMap(HttpServletRequest) */ public static final String INPUT_FLASH_MAP_ATTRIBUTE = FlashMapManager.class.getName() + ".INPUT_FLASH_MAP"; /** * Name of request attribute that holds the "output" {@link FlashMap} with - * attributes to pass to a subsequent request. + * attributes to save for a subsequent request. * @see org.springframework.web.servlet.support.RequestContextUtils#getOutputFlashMap(HttpServletRequest) */ public static final String OUTPUT_FLASH_MAP_ATTRIBUTE = FlashMapManager.class.getName() + ".OUTPUT_FLASH_MAP"; /** - * Performs the following tasks unless the {@link #OUTPUT_FLASH_MAP_ATTRIBUTE} + * Perform the following tasks unless the {@link #OUTPUT_FLASH_MAP_ATTRIBUTE} * request attribute exists: *

    - *
  1. Find the "input" FlashMap from a previous request (if any), expose it - * under the request attribute {@link #INPUT_FLASH_MAP_ATTRIBUTE}, and - * remove it from underlying storage. - *
  2. Create the "output" FlashMap where the current request can save - * flash attributes and expose it under the request attribute - * {@link #OUTPUT_FLASH_MAP_ATTRIBUTE}. - *
  3. Remove expired FlashMap instances. + *
  4. Find the "input" FlashMap, expose it under the request attribute + * {@link #INPUT_FLASH_MAP_ATTRIBUTE}, and remove it from underlying storage. + *
  5. Create the "output" FlashMap and expose it under the request + * attribute {@link #OUTPUT_FLASH_MAP_ATTRIBUTE}. + *
  6. Clean expired FlashMap instances. *
- * * @param request the current request */ void requestStarted(HttpServletRequest request); /** - * Save the "output" FlashMap in underlying storage, start its expiration - * period, and decode/normalize its target request path. - * - *

The "output" FlashMap is not saved if it is empty or if it was not - * created by this FlashMapManager. + * Start the expiration period of the "output" FlashMap save it in the + * underlying storage. + *

The "output" FlashMap should not be saved if it is empty or if it was + * not created by the current FlashMapManager instance. + * @param request the current request */ void requestCompleted(HttpServletRequest request); diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/SmartView.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/SmartView.java index 55dcdb21d8..3ac6526f66 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/SmartView.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/SmartView.java @@ -17,7 +17,8 @@ package org.springframework.web.servlet; /** - * Interface to be implemented by Views that perform a redirect. + * Provides additional information about a View such as whether it + * performs redirects. * * @author Rossen Stoyanchev * @since 3.1 diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RedirectAttributesMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RedirectAttributesMethodArgumentResolver.java index caf8749e61..dc878feaca 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RedirectAttributesMethodArgumentResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RedirectAttributesMethodArgumentResolver.java @@ -31,10 +31,10 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; /** - * Resolves {@link RedirectAttributesModelMap} method arguments. + * Resolves method arguments of type {@link RedirectAttributes}. * - *

This resolver must be listed ahead of the {@link ModelMethodProcessor}, - * which also resolves arguments of type {@link Map} and {@link Model}. + *

This resolver must be listed before the {@link ModelMethodProcessor}, + * which resolves {@link Map} and {@link Model} arguments. * * @author Rossen Stoyanchev * @since 3.1 diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/support/RedirectAttributes.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/support/RedirectAttributes.java index 11e6f07742..682c7964d7 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/support/RedirectAttributes.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/support/RedirectAttributes.java @@ -20,70 +20,66 @@ import java.util.Collection; import java.util.Map; import org.springframework.ui.Model; +import org.springframework.web.servlet.FlashMap; /** - * A {@link Model} that can also store attributes candidate for flash storage. + * A specialization of the {@link Model} interface that controllers can use to + * select attributes for a redirect scenario. Since the intent of adding + * redirect attributes is very explicit -- i.e. to be used for a redirect URL, + * attribute values may be formatted as Strings and stored that way to make + * them eligible to be appended to the query string or expanded as URI + * variables in {@code org.springframework.web.servlet.view.RedirectView}. + * + *

This interface also provides a way to add flash attributes. For a + * general overview of flash attributes see {@link FlashMap}. You can use + * {@link RedirectAttributes} to store flash attributes and they will be + * automatically propagated to the "output" FlashMap of the current request. + * + *

Example usage in an {@code @Controller}: + *

+ * @RequestMapping(value = "/accounts", method = RequestMethod.POST)
+ * public String handle(Account account, BindingResult result, RedirectAttributes redirectAttrs) {
+ *   if (result.hasErrors()) {
+ *     return "accounts/new";
+ *   }
+ *   // Save account ...
+ *   redirectAttrs.addAttribute("id", account.getId()).addFlashAttribute("message", "Account created!");
+ *   return "redirect:/accounts/{id}";
+ * }
+ * 
+ * + *

After the redirect, flash attributes are automatically added to the model + * of the controller serving the redirect URL. * * @author Rossen Stoyanchev * @since 3.1 */ public interface RedirectAttributes extends Model { - /** - * Add the supplied attribute under the supplied name. - * @param attributeName the name of the model attribute (never null) - * @param attributeValue the model attribute value (can be null) - */ RedirectAttributes addAttribute(String attributeName, Object attributeValue); - /** - * Add the supplied attribute to this Map using a - * {@link org.springframework.core.Conventions#getVariableName generated name}. - *

Note: Empty {@link java.util.Collection Collections} are not added to - * the model when using this method because we cannot correctly determine - * the true convention name. View code should check for null rather - * than for empty collections as is already done by JSTL tags. - * @param attributeValue the model attribute value (never null) - */ RedirectAttributes addAttribute(Object attributeValue); - /** - * Copy all attributes in the supplied Collection into this - * Map, using attribute name generation for each element. - * @see #addAttribute(Object) - */ RedirectAttributes addAllAttributes(Collection attributeValues); - - /** - * Copy all attributes in the supplied Map into this Map. - * @see #addAttribute(String, Object) - */ - Model addAllAttributes(Map attributes); - - /** - * Copy all attributes in the supplied Map into this Map, - * with existing objects of the same name taking precedence (i.e. not getting - * replaced). - */ - RedirectAttributes mergeAttributes(Map attributes); + RedirectAttributes mergeAttributes(Map attributes); + /** - * Add the given attribute as a candidate for flash storage. - * @param attributeName the flash attribute name; never null - * @param attributeValue the flash attribute value; may be null + * Add the given flash attribute. + * @param attributeName the attribute name; never {@code null} + * @param attributeValue the attribute value; may be {@code null} */ RedirectAttributes addFlashAttribute(String attributeName, Object attributeValue); /** - * Add the given attribute value as a candidate for flash storage using a + * Add the given flash storage using a * {@link org.springframework.core.Conventions#getVariableName generated name}. - * @param attributeValue the flash attribute value; never null + * @param attributeValue the flash attribute value; never {@code null} */ RedirectAttributes addFlashAttribute(Object attributeValue); /** - * Return the attributes candidate for flash storage. + * Return the attributes candidate for flash storage or an empty Map. */ Map getFlashAttributes(); - -} \ No newline at end of file +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/support/RedirectAttributesModelMap.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/support/RedirectAttributesModelMap.java index 0dc565e007..d98fd4a6ee 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/support/RedirectAttributesModelMap.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/support/RedirectAttributesModelMap.java @@ -23,12 +23,10 @@ import org.springframework.ui.ModelMap; import org.springframework.validation.DataBinder; /** - * A {@link ModelMap} that implements the {@link RedirectAttributes} interface. - * - *

Attributes are formatted and stored as Strings so they can be used as URI - * variables or as query parameters in the redirect URL. Alternatively, - * attributes may also be added as flash attributes in order to request storing - * them until the next request without affecting the redirect URL. + * A {@link ModelMap} implementation of {@link RedirectAttributes} that formats + * values as Strings using a {@link DataBinder}. Also provides a place to store + * flash attributes so they can survive a redirect without the need to be + * embedded in the redirect URL. * * @author Rossen Stoyanchev * @since 3.1 @@ -41,33 +39,31 @@ public class RedirectAttributesModelMap extends ModelMap implements RedirectAttr private final ModelMap flashAttributes = new ModelMap(); /** - * Default constructor without a DataBinder. - * Redirect attribute values will be formatted via {@link #toString()}. + * Class constructor. + * @param dataBinder used to format attribute values as Strings. */ - public RedirectAttributesModelMap() { - this(null); + public RedirectAttributesModelMap(DataBinder dataBinder) { + this.dataBinder = dataBinder; } - + /** - * Constructor with a DataBinder to use to format redirect attribute values. - * @param dataBinder a DataBinder for converting attribute values to String. + * Default constructor without a DataBinder. + * Attribute values are converted to String via {@link #toString()}. */ - public RedirectAttributesModelMap(DataBinder dataBinder) { - this.dataBinder = dataBinder; + public RedirectAttributesModelMap() { + this(null); } /** - * Return the attributes candidate for flash storage. + * Return the attributes candidate for flash storage or an empty Map. */ public Map getFlashAttributes() { - return flashAttributes; + return this.flashAttributes; } /** - * Format the attribute value as a String and add it. If the value is - * {@code null} it is not be added. - * @param attributeName the attribute name; never null - * @param attributeValue the attribute value; skipped if null + * {@inheritDoc} + *

Formats the attribute value as a String before adding it. */ public RedirectAttributesModelMap addAttribute(String attributeName, Object attributeValue) { if (attributeValue != null) { @@ -81,10 +77,8 @@ public class RedirectAttributesModelMap extends ModelMap implements RedirectAttr } /** - * Add an attribute using a - * {@link org.springframework.core.Conventions#getVariableName generated name}. - * Before being added the attribute value is formatted as a String. - * @param attributeValue the attribute value; never null + * {@inheritDoc} + *

Formats the attribute value as a String before adding it. */ public RedirectAttributesModelMap addAttribute(Object attributeValue) { super.addAttribute(attributeValue); @@ -92,9 +86,8 @@ public class RedirectAttributesModelMap extends ModelMap implements RedirectAttr } /** - * Copy all attributes in the supplied Collection into this - * Model using attribute name generation for each element. - * @see #addAttribute(Object) + * {@inheritDoc} + *

Each attribute value is formatted as a String before being added. */ public RedirectAttributesModelMap addAllAttributes(Collection attributeValues) { super.addAllAttributes(attributeValues); @@ -102,8 +95,8 @@ public class RedirectAttributesModelMap extends ModelMap implements RedirectAttr } /** - * Copy all supplied attributes into this model. - * @see #addAttribute(String, Object) + * {@inheritDoc} + *

Each attribute value is formatted as a String before being added. */ public RedirectAttributesModelMap addAllAttributes(Map attributes) { if (attributes != null) { @@ -115,9 +108,8 @@ public class RedirectAttributesModelMap extends ModelMap implements RedirectAttr } /** - * Copy all supplied attributes into this model with with existing - * attributes of the same name taking precedence (i.e. not getting replaced). - * @see #addAttribute(String, Object) + * {@inheritDoc} + *

Each attribute value is formatted as a String before being merged. */ public RedirectAttributesModelMap mergeAttributes(Map attributes) { if (attributes != null) { @@ -134,21 +126,11 @@ public class RedirectAttributesModelMap extends ModelMap implements RedirectAttr return this; } - /** - * Add the given attribute as a candidate for flash storage. - * @param attributeName the flash attribute name; never null - * @param attributeValue the flash attribute value; may be null - */ public RedirectAttributes addFlashAttribute(String attributeName, Object attributeValue) { this.flashAttributes.addAttribute(attributeName, attributeValue); return this; } - /** - * Add the given attribute value as a candidate for flash storage using a - * {@link org.springframework.core.Conventions#getVariableName generated name}. - * @param attributeValue the flash attribute value; never null - */ public RedirectAttributes addFlashAttribute(Object attributeValue) { this.flashAttributes.addAttribute(attributeValue); return this; diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/DefaultFlashMapManager.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/DefaultFlashMapManager.java index f5dbdb2e4a..c8b9f57b53 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/DefaultFlashMapManager.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/DefaultFlashMapManager.java @@ -19,7 +19,6 @@ package org.springframework.web.servlet.support; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import javax.servlet.http.HttpServletRequest; @@ -34,7 +33,8 @@ import org.springframework.web.servlet.FlashMapManager; import org.springframework.web.util.UrlPathHelper; /** - * A {@link FlashMapManager} that stores FlashMap instances in the HTTP session. + * A default {@link FlashMapManager} implementation keeps {@link FlashMap} + * instances in the HTTP session. * * @author Rossen Stoyanchev * @since 3.1 @@ -50,8 +50,9 @@ public class DefaultFlashMapManager implements FlashMapManager { private final UrlPathHelper urlPathHelper = new UrlPathHelper(); /** - * The amount of time in seconds after a FlashMap is saved (after request - * completion) before it is considered expired. The default value is 180. + * Set the amount of time in seconds after a {@link FlashMap} is saved + * (at request completion) and before it expires. + *

The default value is 180 seconds. */ public void setFlashMapTimeout(int flashTimeout) { this.flashTimeout = flashTimeout; @@ -59,16 +60,16 @@ public class DefaultFlashMapManager implements FlashMapManager { /** * {@inheritDoc} - *

This method never causes the HTTP session to be created. + *

An HTTP session is never created by this method. */ public void requestStarted(HttpServletRequest request) { if (request.getAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE) != null) { return; } - Map inputFlashMap = lookupFlashMap(request); + FlashMap inputFlashMap = lookupFlashMap(request); if (inputFlashMap != null) { - request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, inputFlashMap); + request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } FlashMap outputFlashMap = new FlashMap(this.hashCode()); @@ -78,17 +79,17 @@ public class DefaultFlashMapManager implements FlashMapManager { } /** - * Look up the "input" FlashMap by matching the target request path and - * the target request parameters configured in each available FlashMap - * to the current request. - */ - private Map lookupFlashMap(HttpServletRequest request) { + * Find the "input" FlashMap for the current request target by matching it + * to the target request information of all stored FlashMap instances. + * @return a FlashMap instance or {@code null} + */ + private FlashMap lookupFlashMap(HttpServletRequest request) { List allFlashMaps = retrieveFlashMaps(request, false); if (CollectionUtils.isEmpty(allFlashMaps)) { return null; } if (logger.isDebugEnabled()) { - logger.debug("Retrieved flash maps: " + allFlashMaps); + logger.debug("Retrieved FlashMap(s): " + allFlashMaps); } List result = new ArrayList(); for (FlashMap flashMap : allFlashMaps) { @@ -99,19 +100,19 @@ public class DefaultFlashMapManager implements FlashMapManager { if (!result.isEmpty()) { Collections.sort(result); if (logger.isDebugEnabled()) { - logger.debug("Matching flash maps: " + result); + logger.debug("Found matching FlashMap(s): " + result); } FlashMap match = result.remove(0); allFlashMaps.remove(match); - return Collections.unmodifiableMap(match); + return match; } return null; } /** - * Compares the target request path and the target request parameters in the - * given FlashMap and returns "true" if they match. If the FlashMap does not - * have target request information, it matches any request. + * Whether the given FlashMap matches the current request. + * The default implementation uses the target request path and query params + * saved in the FlashMap. */ protected boolean isFlashMapForRequest(FlashMap flashMap, HttpServletRequest request) { if (flashMap.getTargetRequestPath() != null) { @@ -122,8 +123,8 @@ public class DefaultFlashMapManager implements FlashMapManager { } } if (flashMap.getTargetRequestParams() != null) { - for (Map.Entry entry : flashMap.getTargetRequestParams().entrySet()) { - if (!entry.getValue().equals(request.getParameter(entry.getKey()))) { + for (String paramName : flashMap.getTargetRequestParams().keySet()) { + if (!flashMap.getTargetRequestParams().get(paramName).equals(request.getParameter(paramName))) { return false; } } @@ -132,10 +133,13 @@ public class DefaultFlashMapManager implements FlashMapManager { } /** - * Retrieve all available FlashMap instances from the HTTP session. + * Retrieve all FlashMap instances from the current HTTP session. + * If {@code allowCreate} is "true" and no flash maps exist yet, a new list + * is created and stored as a session attribute. * @param request the current request - * @param allowCreate whether to create and save the FlashMap in the session - * @return a Map with all FlashMap instances; or {@code null} + * @param allowCreate whether to create the session if necessary + * @return a List to add FlashMap instances to or {@code null} + * assuming {@code allowCreate} is "false". */ @SuppressWarnings("unchecked") protected List retrieveFlashMaps(HttpServletRequest request, boolean allowCreate) { @@ -157,7 +161,7 @@ public class DefaultFlashMapManager implements FlashMapManager { } /** - * Iterate available FlashMap instances and remove the ones that have expired. + * Iterate all flash maps and remove expired ones. */ private void removeExpiredFlashMaps(HttpServletRequest request) { List allMaps = retrieveFlashMaps(request, false); @@ -173,40 +177,52 @@ public class DefaultFlashMapManager implements FlashMapManager { expiredMaps.add(flashMap); } } - allMaps.removeAll(expiredMaps); + if (!expiredMaps.isEmpty()) { + allMaps.removeAll(expiredMaps); + } } - + + /** + * {@inheritDoc} + *

An HTTP session is never created if the "output" FlashMap is empty. + */ public void requestCompleted(HttpServletRequest request) { FlashMap flashMap = (FlashMap) request.getAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE); if (flashMap == null) { - throw new IllegalStateException( - "Did not find a FlashMap exposed as the request attribute " + OUTPUT_FLASH_MAP_ATTRIBUTE); + throw new IllegalStateException("requestCompleted called but \"output\" FlashMap was never created"); } if (!flashMap.isEmpty() && flashMap.isCreatedBy(this.hashCode())) { if (logger.isDebugEnabled()) { logger.debug("Saving FlashMap=" + flashMap); } - decodeAndNormalizeTargetPath(flashMap, request); - flashMap.startExpirationPeriod(this.flashTimeout); + onSaveFlashMap(flashMap, request); retrieveFlashMaps(request, true).add(flashMap); } } - + /** - * Ensure the target request path in the given FlashMap is decoded and also - * normalized (if it is relative) against the current request URL. + * Update a FlashMap before it is stored in the HTTP Session. + *

The default implementation starts the expiration period and ensures the + * target request path is decoded and normalized if it is relative. + * @param flashMap the flash map to be saved + * @param request the current request */ - private void decodeAndNormalizeTargetPath(FlashMap flashMap, HttpServletRequest request) { - String path = flashMap.getTargetRequestPath(); + protected void onSaveFlashMap(FlashMap flashMap, HttpServletRequest request) { + String targetPath = flashMap.getTargetRequestPath(); + flashMap.setTargetRequestPath(decodeAndNormalizePath(targetPath, request)); + flashMap.startExpirationPeriod(this.flashTimeout); + } + + private String decodeAndNormalizePath(String path, HttpServletRequest request) { if (path != null) { - path = urlPathHelper.decodeRequestString(request, path); + path = this.urlPathHelper.decodeRequestString(request, path); if (path.charAt(0) != '/') { String requestUri = this.urlPathHelper.getRequestUri(request); path = requestUri.substring(0, requestUri.lastIndexOf('/') + 1) + path; path = StringUtils.cleanPath(path); } - flashMap.setTargetRequestPath(path); } + return path; } } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java index 62497fe263..a615777b88 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java @@ -157,9 +157,11 @@ public abstract class RequestContextUtils { } /** - * Return a read-only Map with flash attributes saved during the previous request. + * Return a read-only {@link Map} with "input" flash attributes saved on a + * previous request. * @param request the current request - * @return a read-only Map, or {@code null} + * @return a read-only Map, or {@code null} + * @see FlashMap */ @SuppressWarnings("unchecked") public static Map getInputFlashMap(HttpServletRequest request) { @@ -167,9 +169,10 @@ public abstract class RequestContextUtils { } /** - * Return a FlashMap to add attributes to during the current request. - * @param request current HTTP request - * @return the flash map for the current request; never {@code null}. + * Return the "output" FlashMap with attributes to save for a subsequent request. + * @param request current request + * @return a {@link FlashMap} instance, never {@code null} + * @see FlashMap */ public static FlashMap getOutputFlashMap(HttpServletRequest request) { return (FlashMap) request.getAttribute(FlashMapManager.OUTPUT_FLASH_MAP_ATTRIBUTE);