SPR-6464 Polish FlashMap changes.

This commit is contained in:
Rossen Stoyanchev 2011-09-09 16:48:00 +00:00
parent f0ed37c233
commit 671744aa62
9 changed files with 186 additions and 190 deletions

View File

@ -671,7 +671,8 @@ public class DispatcherServlet extends FrameworkServlet {
/** /**
* Initialize the {@link FlashMapManager} used by this servlet instance. * Initialize the {@link FlashMapManager} used by this servlet instance.
* <p>If no implementation is configured then we default to DefaultFlashMapManager. * <p>If no implementation is configured then we default to
* {@code org.springframework.web.servlet.support.DefaultFlashMapManager}.
*/ */
private void initFlashMapManager(ApplicationContext context) { private void initFlashMapManager(ApplicationContext context) {
try { try {

View File

@ -21,7 +21,6 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.util.Assert;
/** /**
* A FlashMap provides a way for one request to store attributes intended for * A FlashMap provides a way for one request to store attributes intended for
@ -33,11 +32,14 @@ import org.springframework.util.Assert;
* <p>A FlashMap can be set up with a request path and request parameters to * <p>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 * 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 * 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 * recipient. On a redirect, the target URL is known and for example
* {@code org.springframework.web.servlet.view.RedirectView}, FlashMap * {@code org.springframework.web.servlet.view.RedirectView} has the
* instances are automatically updated with redirect URL information. * opportunity to automatically update the current FlashMap with target
* URL information .
* *
* <p>Annotated controllers will usually not access a FlashMap directly.. TODO * <p>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 * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1
@ -58,6 +60,14 @@ public class FlashMap extends HashMap<String, Object> implements Comparable<Flas
private final int createdBy; private final int createdBy;
/**
* Create a new instance with an id uniquely identifying the creator of
* this FlashMap.
*/
public FlashMap(int createdBy) {
this.createdBy = createdBy;
}
/** /**
* Create a new instance. * Create a new instance.
*/ */
@ -65,27 +75,18 @@ public class FlashMap extends HashMap<String, Object> implements Comparable<Flas
this.createdBy = 0; this.createdBy = 0;
} }
/**
* Create a new instance with an id uniquely identifying the creator of
* this FlashMap.
*/
public FlashMap(int createdBy) {
this.createdBy = createdBy;
}
/** /**
* Provide a URL path to help identify the target request for this FlashMap. * Provide a URL path to help identify the target request for this FlashMap.
* The path may be absolute (e.g. /application/resource) or relative to the * The path may be absolute (e.g. /application/resource) or relative to the
* current request (e.g. ../resource). * current request (e.g. ../resource).
* @param path the URI path, never {@code null} * @param path the URI path
*/ */
public void setTargetRequestPath(String path) { public void setTargetRequestPath(String path) {
Assert.notNull(path, "Expected path must not be null");
this.targetRequestPath = path; this.targetRequestPath = path;
} }
/** /**
* Return the URL path of the target request, or {@code null} if none. * Return the target URL path or {@code null}.
*/ */
public String getTargetRequestPath() { public String getTargetRequestPath() {
return targetRequestPath; return targetRequestPath;
@ -93,10 +94,9 @@ public class FlashMap extends HashMap<String, Object> implements Comparable<Flas
/** /**
* Provide request parameter pairs to identify the request for this FlashMap. * Provide request parameter pairs to identify the request for this FlashMap.
* If not set, the FlashMap will match to requests with any parameters. * Only simple type, non-null parameter values are used.
* Only simple value types, as defined in {@link BeanUtils#isSimpleValueType},
* are used.
* @param params a Map with the names and values of expected parameters. * @param params a Map with the names and values of expected parameters.
* @see BeanUtils#isSimpleValueType(Class)
*/ */
public FlashMap addTargetRequestParams(Map<String, ?> params) { public FlashMap addTargetRequestParams(Map<String, ?> params) {
if (params != null) { if (params != null) {
@ -112,10 +112,8 @@ public class FlashMap extends HashMap<String, Object> implements Comparable<Flas
/** /**
* Provide a request parameter to identify the request for this FlashMap. * Provide a request parameter to identify the request for this FlashMap.
* If not set, the FlashMap will match to requests with any parameters. * @param name the name of the expected parameter, never {@code null}
* * @param value the value for the expected parameter, never {@code null}
* @param name the name of the expected parameter (never {@code null})
* @param value the value for the expected parameter (never {@code null})
*/ */
public FlashMap addTargetRequestParam(String name, String value) { public FlashMap addTargetRequestParam(String name, String value) {
this.targetRequestParams.put(name, value.toString()); this.targetRequestParams.put(name, value.toString());
@ -130,9 +128,8 @@ public class FlashMap extends HashMap<String, Object> implements Comparable<Flas
} }
/** /**
* Start the expiration period for this instance. After the given number of * Start the expiration period for this instance.
* seconds calls to {@link #isExpired()} will return "true". * @param timeToLive the number of seconds before expiration
* @param timeToLive the number of seconds before flash map expires
*/ */
public void startExpirationPeriod(int timeToLive) { public void startExpirationPeriod(int timeToLive) {
this.expirationStartTime = System.currentTimeMillis(); this.expirationStartTime = System.currentTimeMillis();
@ -140,8 +137,8 @@ public class FlashMap extends HashMap<String, Object> implements Comparable<Flas
} }
/** /**
* Whether the flash map has expired depending on the number of seconds * Whether this instance has expired depending on the amount of elapsed
* elapsed since the call to {@link #startExpirationPeriod}. * time since the call to {@link #startExpirationPeriod}.
*/ */
public boolean isExpired() { public boolean isExpired() {
if (this.expirationStartTime != 0) { if (this.expirationStartTime != 0) {
@ -160,8 +157,9 @@ public class FlashMap extends HashMap<String, Object> implements Comparable<Flas
} }
/** /**
* Compare two FlashMaps and select the one that has a target URL path or * Compare two FlashMaps and prefer the one that specifies a target URL
* has more target request parameters. * path or has more target URL parameters. Before comparing FlashMap
* instances ensure that they match a given request.
*/ */
public int compareTo(FlashMap other) { public int compareTo(FlashMap other) {
int thisUrlPath = (this.targetRequestPath != null) ? 1 : 0; int thisUrlPath = (this.targetRequestPath != null) ? 1 : 0;
@ -178,8 +176,8 @@ public class FlashMap extends HashMap<String, Object> implements Comparable<Flas
public String toString() { public String toString() {
StringBuilder result = new StringBuilder(); StringBuilder result = new StringBuilder();
result.append("[Attributes=").append(super.toString()); result.append("[Attributes=").append(super.toString());
result.append(", expecteRequestUri=").append(this.targetRequestPath); result.append(", targetRequestPath=").append(this.targetRequestPath);
result.append(", expectedRequestParameters=" + this.targetRequestParams.toString()).append("]"); result.append(", targetRequestParams=" + this.targetRequestParams.toString()).append("]");
return result.toString(); return result.toString();
} }

View File

@ -16,20 +16,22 @@
package org.springframework.web.servlet; package org.springframework.web.servlet;
import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
/** /**
* A strategy interface for storing and retrieving {@code FlashMap} instances. * A strategy interface for storing, retrieving, and managing {@code FlashMap}
* See {@link FlashMap} for a general overview of using flash attributes. * instances. See {@link FlashMap} for a general overview of flash attributes.
* *
* <p>A FlashMapManager is invoked at the beginning and at the end of a request. * <p>A FlashMapManager is invoked at the beginning and at the end of requests.
* For each request, it exposes an "input" FlashMap with attributes passed from * For each request it retrieves an "input" FlashMap with attributes passed
* a previous request (if any) and an "output" FlashMap with attributes to pass * from a previous request (if any) and creates an "output" FlashMap with
* to a subsequent request. Both FlashMap instances are exposed via request * attributes to pass to a subsequent request. "Input" and "output" FlashMap
* attributes and can be accessed through methods in * instances are exposed as request attributes and are accessible via methods
* {@code org.springframework.web.servlet.support.RequestContextUtils}. * in {@code org.springframework.web.servlet.support.RequestContextUtils}.
*
* <p>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 * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1
@ -39,42 +41,39 @@ import javax.servlet.http.HttpServletRequest;
public interface FlashMapManager { public interface FlashMapManager {
/** /**
* Name of request attribute that holds a read-only {@link Map} with * Name of request attribute that holds a read-only
* "input" flash attributes from a previous request (if any). * {@code Map<String, Object>} with "input" flash attributes if any.
* @see org.springframework.web.servlet.support.RequestContextUtils#getInputFlashMap(HttpServletRequest) * @see org.springframework.web.servlet.support.RequestContextUtils#getInputFlashMap(HttpServletRequest)
*/ */
public static final String INPUT_FLASH_MAP_ATTRIBUTE = FlashMapManager.class.getName() + ".INPUT_FLASH_MAP"; 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 * 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) * @see org.springframework.web.servlet.support.RequestContextUtils#getOutputFlashMap(HttpServletRequest)
*/ */
public static final String OUTPUT_FLASH_MAP_ATTRIBUTE = FlashMapManager.class.getName() + ".OUTPUT_FLASH_MAP"; 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: * request attribute exists:
* <ol> * <ol>
* <li>Find the "input" FlashMap from a previous request (if any), expose it * <li>Find the "input" FlashMap, expose it under the request attribute
* under the request attribute {@link #INPUT_FLASH_MAP_ATTRIBUTE}, and * {@link #INPUT_FLASH_MAP_ATTRIBUTE}, and remove it from underlying storage.
* remove it from underlying storage. * <li>Create the "output" FlashMap and expose it under the request
* <li>Create the "output" FlashMap where the current request can save * attribute {@link #OUTPUT_FLASH_MAP_ATTRIBUTE}.
* flash attributes and expose it under the request attribute * <li>Clean expired FlashMap instances.
* {@link #OUTPUT_FLASH_MAP_ATTRIBUTE}.
* <li>Remove expired FlashMap instances.
* </ol> * </ol>
*
* @param request the current request * @param request the current request
*/ */
void requestStarted(HttpServletRequest request); void requestStarted(HttpServletRequest request);
/** /**
* Save the "output" FlashMap in underlying storage, start its expiration * Start the expiration period of the "output" FlashMap save it in the
* period, and decode/normalize its target request path. * underlying storage.
* * <p>The "output" FlashMap should not be saved if it is empty or if it was
* <p>The "output" FlashMap is not saved if it is empty or if it was not * not created by the current FlashMapManager instance.
* created by this FlashMapManager. * @param request the current request
*/ */
void requestCompleted(HttpServletRequest request); void requestCompleted(HttpServletRequest request);

View File

@ -17,7 +17,8 @@
package org.springframework.web.servlet; 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 * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1

View File

@ -31,10 +31,10 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap;
/** /**
* Resolves {@link RedirectAttributesModelMap} method arguments. * Resolves method arguments of type {@link RedirectAttributes}.
* *
* <p>This resolver must be listed ahead of the {@link ModelMethodProcessor}, * <p>This resolver must be listed before the {@link ModelMethodProcessor},
* which also resolves arguments of type {@link Map} and {@link Model}. * which resolves {@link Map} and {@link Model} arguments.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1

View File

@ -20,70 +20,66 @@ import java.util.Collection;
import java.util.Map; import java.util.Map;
import org.springframework.ui.Model; 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}.
*
* <p>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.
*
* <p>Example usage in an {@code @Controller}:
* <pre>
* &#064;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}";
* }
* </pre>
*
* <p>After the redirect, flash attributes are automatically added to the model
* of the controller serving the redirect URL.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1
*/ */
public interface RedirectAttributes extends Model { public interface RedirectAttributes extends Model {
/**
* Add the supplied attribute under the supplied name.
* @param attributeName the name of the model attribute (never <code>null</code>)
* @param attributeValue the model attribute value (can be <code>null</code>)
*/
RedirectAttributes addAttribute(String attributeName, Object attributeValue); RedirectAttributes addAttribute(String attributeName, Object attributeValue);
/**
* Add the supplied attribute to this <code>Map</code> using a
* {@link org.springframework.core.Conventions#getVariableName generated name}.
* <p><emphasis>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 <code>null</code> rather
* than for empty collections as is already done by JSTL tags.</emphasis>
* @param attributeValue the model attribute value (never <code>null</code>)
*/
RedirectAttributes addAttribute(Object attributeValue); RedirectAttributes addAttribute(Object attributeValue);
/**
* Copy all attributes in the supplied <code>Collection</code> into this
* <code>Map</code>, using attribute name generation for each element.
* @see #addAttribute(Object)
*/
RedirectAttributes addAllAttributes(Collection<?> attributeValues); RedirectAttributes addAllAttributes(Collection<?> attributeValues);
/**
* Copy all attributes in the supplied <code>Map</code> into this <code>Map</code>.
* @see #addAttribute(String, Object)
*/
Model addAllAttributes(Map<String, ?> attributes);
/**
* Copy all attributes in the supplied <code>Map</code> into this <code>Map</code>,
* with existing objects of the same name taking precedence (i.e. not getting
* replaced).
*/
RedirectAttributes mergeAttributes(Map<String, ?> attributes);
RedirectAttributes mergeAttributes(Map<String, ?> attributes);
/** /**
* Add the given attribute as a candidate for flash storage. * Add the given flash attribute.
* @param attributeName the flash attribute name; never null * @param attributeName the attribute name; never {@code null}
* @param attributeValue the flash attribute value; may be null * @param attributeValue the attribute value; may be {@code null}
*/ */
RedirectAttributes addFlashAttribute(String attributeName, Object attributeValue); 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}. * {@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); RedirectAttributes addFlashAttribute(Object attributeValue);
/** /**
* Return the attributes candidate for flash storage. * Return the attributes candidate for flash storage or an empty Map.
*/ */
Map<String, ?> getFlashAttributes(); Map<String, ?> getFlashAttributes();
}
}

View File

@ -23,12 +23,10 @@ import org.springframework.ui.ModelMap;
import org.springframework.validation.DataBinder; import org.springframework.validation.DataBinder;
/** /**
* A {@link ModelMap} that implements the {@link RedirectAttributes} interface. * A {@link ModelMap} implementation of {@link RedirectAttributes} that formats
* * values as Strings using a {@link DataBinder}. Also provides a place to store
* <p>Attributes are formatted and stored as Strings so they can be used as URI * flash attributes so they can survive a redirect without the need to be
* variables or as query parameters in the redirect URL. Alternatively, * embedded in the redirect URL.
* attributes may also be added as flash attributes in order to request storing
* them until the next request without affecting the redirect URL.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1
@ -40,34 +38,32 @@ public class RedirectAttributesModelMap extends ModelMap implements RedirectAttr
private final ModelMap flashAttributes = new ModelMap(); private final ModelMap flashAttributes = new ModelMap();
/**
* Class constructor.
* @param dataBinder used to format attribute values as Strings.
*/
public RedirectAttributesModelMap(DataBinder dataBinder) {
this.dataBinder = dataBinder;
}
/** /**
* Default constructor without a DataBinder. * Default constructor without a DataBinder.
* Redirect attribute values will be formatted via {@link #toString()}. * Attribute values are converted to String via {@link #toString()}.
*/ */
public RedirectAttributesModelMap() { public RedirectAttributesModelMap() {
this(null); this(null);
} }
/** /**
* Constructor with a DataBinder to use to format redirect attribute values. * Return the attributes candidate for flash storage or an empty Map.
* @param dataBinder a DataBinder for converting attribute values to String.
*/
public RedirectAttributesModelMap(DataBinder dataBinder) {
this.dataBinder = dataBinder;
}
/**
* Return the attributes candidate for flash storage.
*/ */
public Map<String, ?> getFlashAttributes() { public Map<String, ?> getFlashAttributes() {
return flashAttributes; return this.flashAttributes;
} }
/** /**
* Format the attribute value as a String and add it. If the value is * {@inheritDoc}
* {@code null} it is not be added. * <p>Formats the attribute value as a String before adding it.
* @param attributeName the attribute name; never null
* @param attributeValue the attribute value; skipped if null
*/ */
public RedirectAttributesModelMap addAttribute(String attributeName, Object attributeValue) { public RedirectAttributesModelMap addAttribute(String attributeName, Object attributeValue) {
if (attributeValue != null) { if (attributeValue != null) {
@ -81,10 +77,8 @@ public class RedirectAttributesModelMap extends ModelMap implements RedirectAttr
} }
/** /**
* Add an attribute using a * {@inheritDoc}
* {@link org.springframework.core.Conventions#getVariableName generated name}. * <p>Formats the attribute value as a String before adding it.
* Before being added the attribute value is formatted as a String.
* @param attributeValue the attribute value; never null
*/ */
public RedirectAttributesModelMap addAttribute(Object attributeValue) { public RedirectAttributesModelMap addAttribute(Object attributeValue) {
super.addAttribute(attributeValue); super.addAttribute(attributeValue);
@ -92,9 +86,8 @@ public class RedirectAttributesModelMap extends ModelMap implements RedirectAttr
} }
/** /**
* Copy all attributes in the supplied <code>Collection</code> into this * {@inheritDoc}
* Model using attribute name generation for each element. * <p>Each attribute value is formatted as a String before being added.
* @see #addAttribute(Object)
*/ */
public RedirectAttributesModelMap addAllAttributes(Collection<?> attributeValues) { public RedirectAttributesModelMap addAllAttributes(Collection<?> attributeValues) {
super.addAllAttributes(attributeValues); super.addAllAttributes(attributeValues);
@ -102,8 +95,8 @@ public class RedirectAttributesModelMap extends ModelMap implements RedirectAttr
} }
/** /**
* Copy all supplied attributes into this model. * {@inheritDoc}
* @see #addAttribute(String, Object) * <p>Each attribute value is formatted as a String before being added.
*/ */
public RedirectAttributesModelMap addAllAttributes(Map<String, ?> attributes) { public RedirectAttributesModelMap addAllAttributes(Map<String, ?> attributes) {
if (attributes != null) { if (attributes != null) {
@ -115,9 +108,8 @@ public class RedirectAttributesModelMap extends ModelMap implements RedirectAttr
} }
/** /**
* Copy all supplied attributes into this model with with existing * {@inheritDoc}
* attributes of the same name taking precedence (i.e. not getting replaced). * <p>Each attribute value is formatted as a String before being merged.
* @see #addAttribute(String, Object)
*/ */
public RedirectAttributesModelMap mergeAttributes(Map<String, ?> attributes) { public RedirectAttributesModelMap mergeAttributes(Map<String, ?> attributes) {
if (attributes != null) { if (attributes != null) {
@ -134,21 +126,11 @@ public class RedirectAttributesModelMap extends ModelMap implements RedirectAttr
return this; 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) { public RedirectAttributes addFlashAttribute(String attributeName, Object attributeValue) {
this.flashAttributes.addAttribute(attributeName, attributeValue); this.flashAttributes.addAttribute(attributeName, attributeValue);
return this; 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) { public RedirectAttributes addFlashAttribute(Object attributeValue) {
this.flashAttributes.addAttribute(attributeValue); this.flashAttributes.addAttribute(attributeValue);
return this; return this;

View File

@ -19,7 +19,6 @@ package org.springframework.web.servlet.support;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -34,7 +33,8 @@ import org.springframework.web.servlet.FlashMapManager;
import org.springframework.web.util.UrlPathHelper; 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 * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1
@ -50,8 +50,9 @@ public class DefaultFlashMapManager implements FlashMapManager {
private final UrlPathHelper urlPathHelper = new UrlPathHelper(); private final UrlPathHelper urlPathHelper = new UrlPathHelper();
/** /**
* The amount of time in seconds after a FlashMap is saved (after request * Set the amount of time in seconds after a {@link FlashMap} is saved
* completion) before it is considered expired. The default value is 180. * (at request completion) and before it expires.
* <p>The default value is 180 seconds.
*/ */
public void setFlashMapTimeout(int flashTimeout) { public void setFlashMapTimeout(int flashTimeout) {
this.flashTimeout = flashTimeout; this.flashTimeout = flashTimeout;
@ -59,16 +60,16 @@ public class DefaultFlashMapManager implements FlashMapManager {
/** /**
* {@inheritDoc} * {@inheritDoc}
* <p>This method never causes the HTTP session to be created. * <p>An HTTP session is never created by this method.
*/ */
public void requestStarted(HttpServletRequest request) { public void requestStarted(HttpServletRequest request) {
if (request.getAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE) != null) { if (request.getAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE) != null) {
return; return;
} }
Map<String, ?> inputFlashMap = lookupFlashMap(request); FlashMap inputFlashMap = lookupFlashMap(request);
if (inputFlashMap != null) { 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()); 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 * Find the "input" FlashMap for the current request target by matching it
* the target request parameters configured in each available FlashMap * to the target request information of all stored FlashMap instances.
* to the current request. * @return a FlashMap instance or {@code null}
*/ */
private Map<String, ?> lookupFlashMap(HttpServletRequest request) { private FlashMap lookupFlashMap(HttpServletRequest request) {
List<FlashMap> allFlashMaps = retrieveFlashMaps(request, false); List<FlashMap> allFlashMaps = retrieveFlashMaps(request, false);
if (CollectionUtils.isEmpty(allFlashMaps)) { if (CollectionUtils.isEmpty(allFlashMaps)) {
return null; return null;
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Retrieved flash maps: " + allFlashMaps); logger.debug("Retrieved FlashMap(s): " + allFlashMaps);
} }
List<FlashMap> result = new ArrayList<FlashMap>(); List<FlashMap> result = new ArrayList<FlashMap>();
for (FlashMap flashMap : allFlashMaps) { for (FlashMap flashMap : allFlashMaps) {
@ -99,19 +100,19 @@ public class DefaultFlashMapManager implements FlashMapManager {
if (!result.isEmpty()) { if (!result.isEmpty()) {
Collections.sort(result); Collections.sort(result);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Matching flash maps: " + result); logger.debug("Found matching FlashMap(s): " + result);
} }
FlashMap match = result.remove(0); FlashMap match = result.remove(0);
allFlashMaps.remove(match); allFlashMaps.remove(match);
return Collections.unmodifiableMap(match); return match;
} }
return null; return null;
} }
/** /**
* Compares the target request path and the target request parameters in the * Whether the given FlashMap matches the current request.
* given FlashMap and returns "true" if they match. If the FlashMap does not * The default implementation uses the target request path and query params
* have target request information, it matches any request. * saved in the FlashMap.
*/ */
protected boolean isFlashMapForRequest(FlashMap flashMap, HttpServletRequest request) { protected boolean isFlashMapForRequest(FlashMap flashMap, HttpServletRequest request) {
if (flashMap.getTargetRequestPath() != null) { if (flashMap.getTargetRequestPath() != null) {
@ -122,8 +123,8 @@ public class DefaultFlashMapManager implements FlashMapManager {
} }
} }
if (flashMap.getTargetRequestParams() != null) { if (flashMap.getTargetRequestParams() != null) {
for (Map.Entry<String, String> entry : flashMap.getTargetRequestParams().entrySet()) { for (String paramName : flashMap.getTargetRequestParams().keySet()) {
if (!entry.getValue().equals(request.getParameter(entry.getKey()))) { if (!flashMap.getTargetRequestParams().get(paramName).equals(request.getParameter(paramName))) {
return false; 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 request the current request
* @param allowCreate whether to create and save the FlashMap in the session * @param allowCreate whether to create the session if necessary
* @return a Map with all FlashMap instances; or {@code null} * @return a List to add FlashMap instances to or {@code null}
* assuming {@code allowCreate} is "false".
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected List<FlashMap> retrieveFlashMaps(HttpServletRequest request, boolean allowCreate) { protected List<FlashMap> 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) { private void removeExpiredFlashMaps(HttpServletRequest request) {
List<FlashMap> allMaps = retrieveFlashMaps(request, false); List<FlashMap> allMaps = retrieveFlashMaps(request, false);
@ -173,40 +177,52 @@ public class DefaultFlashMapManager implements FlashMapManager {
expiredMaps.add(flashMap); expiredMaps.add(flashMap);
} }
} }
allMaps.removeAll(expiredMaps); if (!expiredMaps.isEmpty()) {
allMaps.removeAll(expiredMaps);
}
} }
/**
* {@inheritDoc}
* <p>An HTTP session is never created if the "output" FlashMap is empty.
*/
public void requestCompleted(HttpServletRequest request) { public void requestCompleted(HttpServletRequest request) {
FlashMap flashMap = (FlashMap) request.getAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE); FlashMap flashMap = (FlashMap) request.getAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE);
if (flashMap == null) { if (flashMap == null) {
throw new IllegalStateException( throw new IllegalStateException("requestCompleted called but \"output\" FlashMap was never created");
"Did not find a FlashMap exposed as the request attribute " + OUTPUT_FLASH_MAP_ATTRIBUTE);
} }
if (!flashMap.isEmpty() && flashMap.isCreatedBy(this.hashCode())) { if (!flashMap.isEmpty() && flashMap.isCreatedBy(this.hashCode())) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Saving FlashMap=" + flashMap); logger.debug("Saving FlashMap=" + flashMap);
} }
decodeAndNormalizeTargetPath(flashMap, request); onSaveFlashMap(flashMap, request);
flashMap.startExpirationPeriod(this.flashTimeout);
retrieveFlashMaps(request, true).add(flashMap); retrieveFlashMaps(request, true).add(flashMap);
} }
} }
/** /**
* Ensure the target request path in the given FlashMap is decoded and also * Update a FlashMap before it is stored in the HTTP Session.
* normalized (if it is relative) against the current request URL. * <p>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) { protected void onSaveFlashMap(FlashMap flashMap, HttpServletRequest request) {
String path = flashMap.getTargetRequestPath(); String targetPath = flashMap.getTargetRequestPath();
flashMap.setTargetRequestPath(decodeAndNormalizePath(targetPath, request));
flashMap.startExpirationPeriod(this.flashTimeout);
}
private String decodeAndNormalizePath(String path, HttpServletRequest request) {
if (path != null) { if (path != null) {
path = urlPathHelper.decodeRequestString(request, path); path = this.urlPathHelper.decodeRequestString(request, path);
if (path.charAt(0) != '/') { if (path.charAt(0) != '/') {
String requestUri = this.urlPathHelper.getRequestUri(request); String requestUri = this.urlPathHelper.getRequestUri(request);
path = requestUri.substring(0, requestUri.lastIndexOf('/') + 1) + path; path = requestUri.substring(0, requestUri.lastIndexOf('/') + 1) + path;
path = StringUtils.cleanPath(path); path = StringUtils.cleanPath(path);
} }
flashMap.setTargetRequestPath(path);
} }
return path;
} }
} }

View File

@ -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 * @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") @SuppressWarnings("unchecked")
public static Map<String, ?> getInputFlashMap(HttpServletRequest request) { public static Map<String, ?> getInputFlashMap(HttpServletRequest request) {
@ -167,9 +169,10 @@ public abstract class RequestContextUtils {
} }
/** /**
* Return a FlashMap to add attributes to during the current request. * Return the "output" FlashMap with attributes to save for a subsequent request.
* @param request current HTTP request * @param request current request
* @return the flash map for the current request; never {@code null}. * @return a {@link FlashMap} instance, never {@code null}
* @see FlashMap
*/ */
public static FlashMap getOutputFlashMap(HttpServletRequest request) { public static FlashMap getOutputFlashMap(HttpServletRequest request) {
return (FlashMap) request.getAttribute(FlashMapManager.OUTPUT_FLASH_MAP_ATTRIBUTE); return (FlashMap) request.getAttribute(FlashMapManager.OUTPUT_FLASH_MAP_ATTRIBUTE);