Skip to content

Commit 5ea5f81

Browse files
committed
Support flash attributes on ResponseEntity redirect
Issue: SPR-15176
1 parent ba97432 commit 5ea5f81

File tree

4 files changed

+115
-35
lines changed

4 files changed

+115
-35
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 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.
@@ -23,6 +23,8 @@
2323
import java.util.Collections;
2424
import java.util.List;
2525
import java.util.Map;
26+
import javax.servlet.http.HttpServletRequest;
27+
import javax.servlet.http.HttpServletResponse;
2628

2729
import org.springframework.core.MethodParameter;
2830
import org.springframework.core.ResolvableType;
@@ -34,14 +36,18 @@
3436
import org.springframework.http.converter.HttpMessageConverter;
3537
import org.springframework.http.server.ServletServerHttpRequest;
3638
import org.springframework.http.server.ServletServerHttpResponse;
39+
import org.springframework.ui.ModelMap;
3740
import org.springframework.util.Assert;
41+
import org.springframework.util.CollectionUtils;
3842
import org.springframework.util.StringUtils;
3943
import org.springframework.web.HttpMediaTypeNotSupportedException;
4044
import org.springframework.web.accept.ContentNegotiationManager;
4145
import org.springframework.web.bind.support.WebDataBinderFactory;
4246
import org.springframework.web.context.request.NativeWebRequest;
4347
import org.springframework.web.context.request.ServletWebRequest;
4448
import org.springframework.web.method.support.ModelAndViewContainer;
49+
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
50+
import org.springframework.web.servlet.support.RequestContextUtils;
4551

4652
/**
4753
* Resolves {@link HttpEntity} and {@link RequestEntity} method argument values
@@ -197,6 +203,12 @@ public void handleReturnValue(Object returnValue, MethodParameter returnType,
197203
return;
198204
}
199205
}
206+
else if (returnStatus / 100 == 3) {
207+
String location = outputHeaders.getFirst("location");
208+
if (location != null) {
209+
saveFlashAttributes(mavContainer, webRequest, location);
210+
}
211+
}
200212
}
201213

202214
// Try even with null body. ResponseBodyAdvice could get involved.
@@ -241,6 +253,20 @@ private boolean isResourceNotModified(ServletServerHttpRequest inputMessage, Ser
241253
return servletWebRequest.checkNotModified(etag, lastModifiedTimestamp);
242254
}
243255

256+
private void saveFlashAttributes(ModelAndViewContainer mav, NativeWebRequest request, String location) {
257+
mav.setRedirectModelScenario(true);
258+
ModelMap model = mav.getModel();
259+
if (model instanceof RedirectAttributes) {
260+
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
261+
if (!CollectionUtils.isEmpty(flashAttributes)) {
262+
HttpServletRequest req = request.getNativeRequest(HttpServletRequest.class);
263+
HttpServletResponse res = request.getNativeRequest(HttpServletResponse.class);
264+
RequestContextUtils.getOutputFlashMap(req).putAll(flashAttributes);
265+
RequestContextUtils.saveOutputFlashMap(location, req, res);
266+
}
267+
}
268+
}
269+
244270
@Override
245271
protected Class<?> getReturnValueType(Object returnValue, MethodParameter returnType) {
246272
if (returnValue != null) {

spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 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.
@@ -22,11 +22,14 @@
2222
import javax.servlet.ServletContext;
2323
import javax.servlet.ServletRequest;
2424
import javax.servlet.http.HttpServletRequest;
25+
import javax.servlet.http.HttpServletResponse;
2526

2627
import org.springframework.context.i18n.LocaleContext;
2728
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
2829
import org.springframework.ui.context.Theme;
2930
import org.springframework.ui.context.ThemeSource;
31+
import org.springframework.util.Assert;
32+
import org.springframework.util.CollectionUtils;
3033
import org.springframework.web.context.ContextLoader;
3134
import org.springframework.web.context.WebApplicationContext;
3235
import org.springframework.web.context.support.WebApplicationContextUtils;
@@ -36,6 +39,8 @@
3639
import org.springframework.web.servlet.LocaleContextResolver;
3740
import org.springframework.web.servlet.LocaleResolver;
3841
import org.springframework.web.servlet.ThemeResolver;
42+
import org.springframework.web.util.UriComponents;
43+
import org.springframework.web.util.UriComponentsBuilder;
3944

4045
/**
4146
* Utility class for easy access to request-specific state which has been
@@ -209,9 +214,8 @@ public static Theme getTheme(HttpServletRequest request) {
209214
}
210215

211216
/**
212-
* Return a read-only {@link Map} with "input" flash attributes saved on a
213-
* previous request.
214-
* @param request the current request
217+
* Return read-only "input" flash attributes from request before redirect.
218+
* @param request current request
215219
* @return a read-only Map, or {@code null} if not found
216220
* @see FlashMap
217221
*/
@@ -221,23 +225,52 @@ public static Theme getTheme(HttpServletRequest request) {
221225
}
222226

223227
/**
224-
* Return the "output" FlashMap with attributes to save for a subsequent request.
225-
* @param request the current request
226-
* @return a {@link FlashMap} instance (never {@code null} within a DispatcherServlet request)
227-
* @see FlashMap
228+
* Return "output" FlashMap to save attributes for request after redirect.
229+
* @param request current request
230+
* @return a {@link FlashMap} instance, never {@code null} within a
231+
* {@code DispatcherServlet}-handled request
228232
*/
229233
public static FlashMap getOutputFlashMap(HttpServletRequest request) {
230234
return (FlashMap) request.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE);
231235
}
232236

233237
/**
234-
* Return the FlashMapManager instance to save flash attributes with
235-
* before a redirect.
238+
* Return the {@code FlashMapManager} instance to save flash attributes.
239+
* <p>As of 5.0 the convenience method {@link #saveOutputFlashMap} may be
240+
* used to save the "output" FlashMap.
236241
* @param request the current request
237-
* @return a {@link FlashMapManager} instance (never {@code null} within a DispatcherServlet request)
242+
* @return a {@link FlashMapManager} instance, never {@code null} within a
243+
* {@code DispatcherServlet}-handled request
238244
*/
239245
public static FlashMapManager getFlashMapManager(HttpServletRequest request) {
240246
return (FlashMapManager) request.getAttribute(DispatcherServlet.FLASH_MAP_MANAGER_ATTRIBUTE);
241247
}
242248

249+
/**
250+
* Convenience method that retrieves the {@link #getOutputFlashMap "output"
251+
* FlashMap}, updates it with the path and query params of the target URL,
252+
* and then saves it using the {@link #getFlashMapManager FlashMapManager}.
253+
*
254+
* @param location the target URL for the redirect
255+
* @param request the current request
256+
* @param response the current response
257+
* @since 5.0
258+
*/
259+
public static void saveOutputFlashMap(String location, HttpServletRequest request,
260+
HttpServletResponse response) {
261+
262+
FlashMap flashMap = getOutputFlashMap(request);
263+
if (CollectionUtils.isEmpty(flashMap)) {
264+
return;
265+
}
266+
267+
UriComponents uriComponents = UriComponentsBuilder.fromUriString(location).build();
268+
flashMap.setTargetRequestPath(uriComponents.getPath());
269+
flashMap.addTargetRequestParams(uriComponents.getQueryParams());
270+
271+
FlashMapManager manager = getFlashMapManager(request);
272+
Assert.state(manager != null, "No FlashMapManager. Is this a DispatcherServlet handled request?");
273+
manager.saveOutputFlashMap(flashMap, request, response);
274+
}
275+
243276
}

spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,14 @@
3333

3434
import org.springframework.beans.BeanUtils;
3535
import org.springframework.http.HttpStatus;
36-
import org.springframework.util.CollectionUtils;
3736
import org.springframework.util.ObjectUtils;
3837
import org.springframework.util.StringUtils;
3938
import org.springframework.web.context.WebApplicationContext;
40-
import org.springframework.web.servlet.FlashMap;
41-
import org.springframework.web.servlet.FlashMapManager;
4239
import org.springframework.web.servlet.HandlerMapping;
4340
import org.springframework.web.servlet.SmartView;
4441
import org.springframework.web.servlet.View;
4542
import org.springframework.web.servlet.support.RequestContextUtils;
4643
import org.springframework.web.servlet.support.RequestDataValueProcessor;
47-
import org.springframework.web.util.UriComponents;
4844
import org.springframework.web.util.UriComponentsBuilder;
4945
import org.springframework.web.util.UriUtils;
5046
import org.springframework.web.util.WebUtils;
@@ -305,18 +301,10 @@ protected void renderMergedOutputModel(Map<String, Object> model, HttpServletReq
305301
String targetUrl = createTargetUrl(model, request);
306302
targetUrl = updateTargetUrl(targetUrl, model, request, response);
307303

308-
FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request);
309-
if (!CollectionUtils.isEmpty(flashMap)) {
310-
UriComponents uriComponents = UriComponentsBuilder.fromUriString(targetUrl).build();
311-
flashMap.setTargetRequestPath(uriComponents.getPath());
312-
flashMap.addTargetRequestParams(uriComponents.getQueryParams());
313-
FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request);
314-
if (flashMapManager == null) {
315-
throw new IllegalStateException("FlashMapManager not found despite output FlashMap having been set");
316-
}
317-
flashMapManager.saveOutputFlashMap(flashMap, request, response);
318-
}
304+
// Save flash attributes
305+
RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);
319306

307+
// Redirect
320308
sendRedirect(request, response, targetUrl, this.http10Compatible);
321309
}
322310

spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,11 @@
109109
import org.springframework.web.bind.WebDataBinder;
110110
import org.springframework.web.bind.annotation.CookieValue;
111111
import org.springframework.web.bind.annotation.ExceptionHandler;
112+
import org.springframework.web.bind.annotation.GetMapping;
112113
import org.springframework.web.bind.annotation.InitBinder;
113114
import org.springframework.web.bind.annotation.ModelAttribute;
114115
import org.springframework.web.bind.annotation.PathVariable;
116+
import org.springframework.web.bind.annotation.PostMapping;
115117
import org.springframework.web.bind.annotation.RequestBody;
116118
import org.springframework.web.bind.annotation.RequestHeader;
117119
import org.springframework.web.bind.annotation.RequestMapping;
@@ -1569,6 +1571,32 @@ public void redirectAttribute() throws Exception {
15691571
assertTrue(RequestContextUtils.getOutputFlashMap(request).isEmpty());
15701572
}
15711573

1574+
@Test // SPR-15176
1575+
public void flashAttributesWithResponseEntity() throws Exception {
1576+
initServletWithControllers(RedirectAttributesController.class);
1577+
1578+
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/messages-response-entity");
1579+
MockHttpServletResponse response = new MockHttpServletResponse();
1580+
HttpSession session = request.getSession();
1581+
1582+
getServlet().service(request, response);
1583+
1584+
assertEquals(302, response.getStatus());
1585+
assertEquals("/messages/1?name=value", response.getRedirectedUrl());
1586+
assertEquals("yay!", RequestContextUtils.getOutputFlashMap(request).get("successMessage"));
1587+
1588+
// GET after POST
1589+
request = new MockHttpServletRequest("GET", "/messages/1");
1590+
request.setQueryString("name=value");
1591+
request.setSession(session);
1592+
response = new MockHttpServletResponse();
1593+
getServlet().service(request, response);
1594+
1595+
assertEquals(200, response.getStatus());
1596+
assertEquals("Got: yay!", response.getContentAsString());
1597+
assertTrue(RequestContextUtils.getOutputFlashMap(request).isEmpty());
1598+
}
1599+
15721600
@Test
15731601
public void prototypeController() throws Exception {
15741602
initServlet(new ApplicationContextInitializer<GenericWebApplicationContext>() {
@@ -3215,21 +3243,26 @@ public void initBinder(WebDataBinder dataBinder) {
32153243
dataBinder.setRequiredFields("name");
32163244
}
32173245

3218-
@RequestMapping(value = "/messages/{id}", method = RequestMethod.GET)
3246+
@GetMapping("/messages/{id}")
32193247
public void message(ModelMap model, Writer writer) throws IOException {
32203248
writer.write("Got: " + model.get("successMessage"));
32213249
}
32223250

3223-
@RequestMapping(value = "/messages", method = RequestMethod.POST)
3224-
public String sendMessage(TestBean testBean, BindingResult result, RedirectAttributes redirectAttrs) {
3251+
@PostMapping("/messages")
3252+
public String sendMessage(TestBean testBean, BindingResult result, RedirectAttributes attributes) {
32253253
if (result.hasErrors()) {
32263254
return "messages/new";
32273255
}
3228-
else {
3229-
redirectAttrs.addAttribute("id", "1").addAttribute("name", "value")
3230-
.addFlashAttribute("successMessage", "yay!");
3231-
return "redirect:/messages/{id}";
3232-
}
3256+
attributes.addAttribute("id", "1").addAttribute("name", "value");
3257+
attributes.addFlashAttribute("successMessage", "yay!");
3258+
return "redirect:/messages/{id}";
3259+
}
3260+
3261+
@PostMapping("/messages-response-entity")
3262+
public ResponseEntity<Void> sendMessage(RedirectAttributes attributes) {
3263+
attributes.addFlashAttribute("successMessage", "yay!");
3264+
URI location = URI.create("/messages/1?name=value");
3265+
return ResponseEntity.status(HttpStatus.FOUND).location(location).build();
32333266
}
32343267
}
32353268

0 commit comments

Comments
 (0)