Skip to content

Commit a3bb376

Browse files
committed
Tighten FlashMapManager for use with alternative storage options
Ensure that both FlashMapManager methods - the one invoked at the start of a request and the one invoked before a redirect, update the underlying storage fully since it's not guaranteed that both will be invoked on any given request. Also move the logic to remove expired FlashMap instances to the metohd invoked at the start of a request to ensure the check is made frequently enough. SPR-8997
1 parent 598b125 commit a3bb376

File tree

7 files changed

+143
-178
lines changed

7 files changed

+143
-178
lines changed

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -840,13 +840,13 @@ protected void doService(HttpServletRequest request, HttpServletResponse respons
840840
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
841841
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
842842
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
843-
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
844-
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
845843

846-
Map<String, ?> flashMap = this.flashMapManager.getFlashMapForRequest(request);
847-
if (flashMap != null) {
848-
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, flashMap);
844+
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
845+
if (inputFlashMap != null) {
846+
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
849847
}
848+
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
849+
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
850850

851851
try {
852852
doDispatch(request, response);

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,12 +32,11 @@
3232
* <p>A FlashMap can be set up with a request path and request parameters to
3333
* help identify the target request. Without this information, a FlashMap is
3434
* made available to the next request, which may or may not be the intended
35-
* recipient. On a redirect, the target URL is known and for example
36-
* {@code org.springframework.web.servlet.view.RedirectView} has the
37-
* opportunity to automatically update the current FlashMap with target
38-
* URL information.
35+
* recipient. On a redirect, the target URL is known and a FlashMap can be
36+
* updated with that information. This is done automatically when the
37+
* {@code org.springframework.web.servlet.view.RedirectView} is used.
3938
*
40-
* <p>Annotated controllers will usually not use this type directly.
39+
* <p>Note: annotated controllers will usually not use FlashMap directly.
4140
* See {@code org.springframework.web.servlet.mvc.support.RedirectAttributes}
4241
* for an overview of using flash attributes in annotated controllers.
4342
*
@@ -58,25 +57,6 @@ public final class FlashMap extends HashMap<String, Object> implements Comparabl
5857

5958
private int timeToLive;
6059

61-
private final int createdBy;
62-
63-
/**
64-
* Create a new instance with an id uniquely identifying the creator of
65-
* this FlashMap.
66-
* @param createdBy identifies the FlashMapManager instance that created
67-
* and will manage this FlashMap instance (e.g. via a hashCode)
68-
*/
69-
public FlashMap(int createdBy) {
70-
this.createdBy = createdBy;
71-
}
72-
73-
/**
74-
* Create a new instance.
75-
*/
76-
public FlashMap() {
77-
this.createdBy = 0;
78-
}
79-
8060
/**
8161
* Provide a URL path to help identify the target request for this FlashMap.
8262
* The path may be absolute (e.g. /application/resource) or relative to the
@@ -96,7 +76,6 @@ public String getTargetRequestPath() {
9676

9777
/**
9878
* Provide request parameters identifying the request for this FlashMap.
99-
* Null or empty keys and values are skipped.
10079
* @param params a Map with the names and values of expected parameters.
10180
*/
10281
public FlashMap addTargetRequestParams(MultiValueMap<String, String> params) {
@@ -112,8 +91,8 @@ public FlashMap addTargetRequestParams(MultiValueMap<String, String> params) {
11291

11392
/**
11493
* Provide a request parameter identifying the request for this FlashMap.
115-
* @param name the expected parameter name, skipped if {@code null}
116-
* @param value the expected parameter value, skipped if {@code null}
94+
* @param name the expected parameter name, skipped if empty or {@code null}
95+
* @param value the expected value, skipped if empty or {@code null}
11796
*/
11897
public FlashMap addTargetRequestParam(String name, String value) {
11998
if (StringUtils.hasText(name) && StringUtils.hasText(value)) {
@@ -151,13 +130,6 @@ public boolean isExpired() {
151130
}
152131
}
153132

154-
/**
155-
* Whether the given id matches the id of the creator of this FlashMap.
156-
*/
157-
public boolean isCreatedBy(int createdBy) {
158-
return this.createdBy == createdBy;
159-
}
160-
161133
/**
162134
* Compare two FlashMaps and prefer the one that specifies a target URL
163135
* path or has more target URL parameters. Before comparing FlashMap

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMapManager.java

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,44 +16,43 @@
1616

1717
package org.springframework.web.servlet;
1818

19-
import java.util.Map;
20-
2119
import javax.servlet.http.HttpServletRequest;
2220
import javax.servlet.http.HttpServletResponse;
2321

2422
/**
2523
* A strategy interface for retrieving and saving FlashMap instances.
2624
* See {@link FlashMap} for a general overview of flash attributes.
27-
*
25+
*
2826
* @author Rossen Stoyanchev
2927
* @since 3.1
30-
*
28+
*
3129
* @see FlashMap
3230
*/
3331
public interface FlashMapManager {
3432

3533
/**
36-
* Get a Map with flash attributes saved by a previous request.
37-
* See {@link FlashMap} for details on how FlashMap instances
38-
* identifies the target requests they're saved for.
39-
* If found, the Map is removed from the underlying storage.
34+
* Find a FlashMap saved by a previous request that matches to the current
35+
* request, remove it from underlying storage, and also remove other
36+
* expired FlashMap instances.
37+
* <p>This method is invoked in the beginning of every request in contrast
38+
* to {@link #saveOutputFlashMap}, which is invoked only when there are
39+
* flash attributes to be saved - i.e. before a redirect.
4040
* @param request the current request
41-
* @return a read-only Map with flash attributes or {@code null}
41+
* @param response the current response
42+
* @return a FlashMap matching the current request or {@code null}
4243
*/
43-
Map<String, ?> getFlashMapForRequest(HttpServletRequest request);
44+
FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
4445

4546
/**
46-
* Save the given FlashMap, in some underlying storage, mark the beginning
47-
* of its expiration period, and remove other expired FlashMap instances.
48-
* The method has no impact if the FlashMap is empty and there are no
49-
* expired FlashMap instances to be removed.
47+
* Save the given FlashMap, in some underlying storage and set the start
48+
* of its expiration period.
5049
* <p><strong>Note:</strong> Invoke this method prior to a redirect in order
51-
* to allow saving the FlashMap in the HTTP session or perhaps in a response
50+
* to allow saving the FlashMap in the HTTP session or in a response
5251
* cookie before the response is committed.
5352
* @param flashMap the FlashMap to save
5453
* @param request the current request
5554
* @param response the current response
5655
*/
57-
void save(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
56+
void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
5857

5958
}

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/AbstractFlashMapManager.java

Lines changed: 66 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.util.ArrayList;
2020
import java.util.Collections;
2121
import java.util.List;
22-
import java.util.Map;
2322
import java.util.concurrent.CopyOnWriteArrayList;
2423

2524
import javax.servlet.http.HttpServletRequest;
@@ -50,6 +49,8 @@ public abstract class AbstractFlashMapManager implements FlashMapManager {
5049

5150
private UrlPathHelper urlPathHelper = new UrlPathHelper();
5251

52+
private static final Object writeLock = new Object();
53+
5354
/**
5455
* Set the amount of time in seconds after a {@link FlashMap} is saved
5556
* (at request completion) and before it expires.
@@ -81,20 +82,59 @@ public UrlPathHelper getUrlPathHelper() {
8182
return this.urlPathHelper;
8283
}
8384

84-
/**
85-
* {@inheritDoc}
86-
* <p>Does not cause an HTTP session to be created.
87-
*/
88-
public final Map<String, ?> getFlashMapForRequest(HttpServletRequest request) {
89-
List<FlashMap> flashMaps = retrieveFlashMaps(request);
90-
if (CollectionUtils.isEmpty(flashMaps)) {
85+
public final FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response) {
86+
List<FlashMap> allMaps = retrieveFlashMaps(request);
87+
if (CollectionUtils.isEmpty(allMaps)) {
9188
return null;
9289
}
9390
if (logger.isDebugEnabled()) {
94-
logger.debug("Retrieved FlashMap(s): " + flashMaps);
91+
logger.debug("Retrieved FlashMap(s): " + allMaps);
92+
}
93+
List<FlashMap> mapsToRemove = getExpiredFlashMaps(allMaps);
94+
FlashMap match = getMatchingFlashMap(allMaps, request);
95+
if (match != null) {
96+
mapsToRemove.add(match);
97+
}
98+
if (!mapsToRemove.isEmpty()) {
99+
if (logger.isDebugEnabled()) {
100+
logger.debug("Removing FlashMap(s): " + allMaps);
101+
}
102+
synchronized (writeLock) {
103+
allMaps = retrieveFlashMaps(request);
104+
allMaps.removeAll(mapsToRemove);
105+
updateFlashMaps(allMaps, request, response);
106+
}
107+
}
108+
return match;
109+
}
110+
111+
/**
112+
* Retrieve saved FlashMap instances from underlying storage.
113+
* @param request the current request
114+
* @return a List with FlashMap instances or {@code null}
115+
*/
116+
protected abstract List<FlashMap> retrieveFlashMaps(HttpServletRequest request);
117+
118+
/**
119+
* Return a list of expired FlashMap instances contained in the given list.
120+
*/
121+
private List<FlashMap> getExpiredFlashMaps(List<FlashMap> allMaps) {
122+
List<FlashMap> result = new ArrayList<FlashMap>();
123+
for (FlashMap map : allMaps) {
124+
if (map.isExpired()) {
125+
result.add(map);
126+
}
95127
}
128+
return result;
129+
}
130+
131+
/**
132+
* Return a FlashMap contained in the given list that matches the request.
133+
* @return a matching FlashMap or {@code null}
134+
*/
135+
private FlashMap getMatchingFlashMap(List<FlashMap> allMaps, HttpServletRequest request) {
96136
List<FlashMap> result = new ArrayList<FlashMap>();
97-
for (FlashMap flashMap : flashMaps) {
137+
for (FlashMap flashMap : allMaps) {
98138
if (isFlashMapForRequest(flashMap, request)) {
99139
result.add(flashMap);
100140
}
@@ -104,24 +144,15 @@ public UrlPathHelper getUrlPathHelper() {
104144
if (logger.isDebugEnabled()) {
105145
logger.debug("Found matching FlashMap(s): " + result);
106146
}
107-
FlashMap match = result.remove(0);
108-
flashMaps.remove(match);
109-
return Collections.unmodifiableMap(match);
147+
return result.get(0);
110148
}
111149
return null;
112150
}
113151

114-
/**
115-
* Retrieve saved FlashMap instances from underlying storage.
116-
* @param request the current request
117-
* @return a List with FlashMap instances or {@code null}
118-
*/
119-
protected abstract List<FlashMap> retrieveFlashMaps(HttpServletRequest request);
120-
121152
/**
122153
* Whether the given FlashMap matches the current request.
123-
* The default implementation uses the target request path and query params
124-
* saved in the FlashMap.
154+
* The default implementation uses the target request path and query
155+
* parameters saved in the FlashMap.
125156
*/
126157
protected boolean isFlashMapForRequest(FlashMap flashMap, HttpServletRequest request) {
127158
if (flashMap.getTargetRequestPath() != null) {
@@ -142,37 +173,21 @@ protected boolean isFlashMapForRequest(FlashMap flashMap, HttpServletRequest req
142173
return true;
143174
}
144175

145-
/**
146-
* {@inheritDoc}
147-
* <p>The FlashMap, if not empty, is saved to the HTTP session.
148-
*/
149-
public final void save(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response) {
150-
Assert.notNull(flashMap, "FlashMap must not be null");
151-
152-
List<FlashMap> flashMaps = retrieveFlashMaps(request);
153-
if (flashMap.isEmpty() && (flashMaps == null)) {
176+
public final void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response) {
177+
if (CollectionUtils.isEmpty(flashMap)) {
154178
return;
155179
}
156-
synchronized (this) {
157-
boolean update = false;
158-
flashMaps = retrieveFlashMaps(request);
159-
if (!CollectionUtils.isEmpty(flashMaps)) {
160-
update = removeExpired(flashMaps);
161-
}
162-
if (!flashMap.isEmpty()) {
163-
String path = decodeAndNormalizePath(flashMap.getTargetRequestPath(), request);
164-
flashMap.setTargetRequestPath(path);
165-
flashMap.startExpirationPeriod(this.flashMapTimeout);
166-
if (logger.isDebugEnabled()) {
167-
logger.debug("Saving FlashMap=" + flashMap);
168-
}
169-
flashMaps = (flashMaps == null) ? new CopyOnWriteArrayList<FlashMap>() : flashMaps;
170-
flashMaps.add(flashMap);
171-
update = true;
172-
}
173-
if (update) {
174-
updateFlashMaps(flashMaps, request, response);
175-
}
180+
String path = decodeAndNormalizePath(flashMap.getTargetRequestPath(), request);
181+
flashMap.setTargetRequestPath(path);
182+
flashMap.startExpirationPeriod(this.flashMapTimeout);
183+
if (logger.isDebugEnabled()) {
184+
logger.debug("Saving FlashMap=" + flashMap);
185+
}
186+
synchronized (writeLock) {
187+
List<FlashMap> allMaps = retrieveFlashMaps(request);
188+
allMaps = (allMaps == null) ? new CopyOnWriteArrayList<FlashMap>() : allMaps;
189+
allMaps.add(flashMap);
190+
updateFlashMaps(allMaps, request, response);
176191
}
177192
}
178193

@@ -197,25 +212,4 @@ private String decodeAndNormalizePath(String path, HttpServletRequest request) {
197212
protected abstract void updateFlashMaps(List<FlashMap> flashMaps, HttpServletRequest request,
198213
HttpServletResponse response);
199214

200-
/**
201-
* Remove expired FlashMap instances from the given List.
202-
*/
203-
protected boolean removeExpired(List<FlashMap> flashMaps) {
204-
List<FlashMap> expired = new ArrayList<FlashMap>();
205-
for (FlashMap flashMap : flashMaps) {
206-
if (flashMap.isExpired()) {
207-
if (logger.isTraceEnabled()) {
208-
logger.trace("Removing expired FlashMap: " + flashMap);
209-
}
210-
expired.add(flashMap);
211-
}
212-
}
213-
if (expired.isEmpty()) {
214-
return false;
215-
}
216-
else {
217-
return flashMaps.removeAll(expired);
218-
}
219-
}
220-
221215
}

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/SessionFlashMapManager.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import org.springframework.web.servlet.FlashMap;
2626

2727
/**
28-
* Stores {@link FlashMap} instances in the HTTP session.
28+
* Store and retrieve {@link FlashMap} instances to and from the HTTP session.
2929
*
3030
* @author Rossen Stoyanchev
3131
* @since 3.1.1
@@ -35,9 +35,10 @@ public class SessionFlashMapManager extends AbstractFlashMapManager{
3535
private static final String FLASH_MAPS_SESSION_ATTRIBUTE = SessionFlashMapManager.class.getName() + ".FLASH_MAPS";
3636

3737
/**
38-
* Retrieve saved FlashMap instances from the HTTP session.
39-
* @param request the current request
40-
* @return a List with FlashMap instances or {@code null}
38+
* Retrieve saved FlashMap instances from the HTTP Session.
39+
* <p>Does not cause an HTTP session to be created but may update it if a
40+
* FlashMap matching the current request is found or there are expired
41+
* FlashMap to be removed.
4142
*/
4243
@SuppressWarnings("unchecked")
4344
protected List<FlashMap> retrieveFlashMaps(HttpServletRequest request) {
@@ -46,7 +47,7 @@ protected List<FlashMap> retrieveFlashMaps(HttpServletRequest request) {
4647
}
4748

4849
/**
49-
* Save the given FlashMap instances in the HTTP session.
50+
* Save the given FlashMap instance, if not empty, in the HTTP session.
5051
*/
5152
protected void updateFlashMaps(List<FlashMap> flashMaps, HttpServletRequest request, HttpServletResponse response) {
5253
request.getSession().setAttribute(FLASH_MAPS_SESSION_ATTRIBUTE, flashMaps);

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/RedirectView.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ protected void renderMergedOutputModel(
276276
}
277277

278278
FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request);
279-
flashMapManager.save(flashMap, request, response);
279+
flashMapManager.saveOutputFlashMap(flashMap, request, response);
280280

281281
sendRedirect(request, response, targetUrl.toString(), this.http10Compatible);
282282
}

0 commit comments

Comments
 (0)