SPR-6464 Drop @FlashAttributes, add ResponseContext, ViewResponse, and RedirectResponse types for annotated controllers to use to prepare a redirect response with flash attributes; Add FlashMap and FlashMapManager and update DispatcherServlet to discover and invoke the FlashMapManager.

This commit is contained in:
Rossen Stoyanchev
2011-08-08 14:00:07 +00:00
parent 11597c906d
commit 1df0cd9f20
28 changed files with 1171 additions and 724 deletions

View File

@@ -1,215 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.annotation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Test;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.FlashAttributes;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.support.FlashStatus;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.method.annotation.FlashAttributesHandler;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
/**
* Test controllers with @{@link FlashAttributes} through the DispatcherServlet.
*
* @author Rossen Stoyanchev
*/
public class FlashAttributesServletTests extends AbstractServletHandlerMethodTests {
private static final String MESSAGE_KEY = "message";
@Test
public void successMessage() throws Exception {
initServletWithModelExposingViewResolver(MessageController.class);
MockHttpServletRequest req = new MockHttpServletRequest("GET", "/message");
MockHttpServletResponse res = new MockHttpServletResponse();
getServlet().service(req, res);
assertEquals(200, res.getStatus());
assertNull(getModelAttribute(req, MESSAGE_KEY));
assertNull(getFlashAttribute(req, MESSAGE_KEY));
req.setMethod("POST");
getServlet().service(req, res);
assertEquals(200, res.getStatus());
assertEquals("Yay!", ((Message) getModelAttribute(req, MESSAGE_KEY)).getText());
assertEquals("Yay!", ((Message) getFlashAttribute(req, MESSAGE_KEY)).getText());
req.setMethod("GET");
getServlet().service(req, res);
assertEquals(200, res.getStatus());
assertEquals("Yay!", ((Message) getModelAttribute(req, MESSAGE_KEY)).getText());
assertNull(getFlashAttribute(req, MESSAGE_KEY));
}
@Test
public void successMessageAcrossControllers() throws Exception {
initServletWithModelExposingViewResolver(MessageController.class, SecondMessageController.class);
MockHttpServletRequest req = new MockHttpServletRequest("POST", "/message");
MockHttpServletResponse res = new MockHttpServletResponse();
getServlet().service(req, res);
req.setParameter("another", "true");
getServlet().service(req, res);
assertEquals(200, res.getStatus());
assertEquals("Nay!", ((Message) getModelAttribute(req, MESSAGE_KEY)).getText());
assertEquals("Nay!", ((Message) getFlashAttribute(req, MESSAGE_KEY)).getText());
req.setMethod("GET");
req.setRequestURI("/second/message");
getServlet().service(req, res);
assertEquals(200, res.getStatus());
assertEquals("Nay!", ((Message) getModelAttribute(req, MESSAGE_KEY)).getText());
assertNull(getFlashAttribute(req, MESSAGE_KEY));
}
@Controller
@FlashAttributes("message")
static class MessageController {
@RequestMapping(value="/message", method=RequestMethod.GET)
public void message(Model model) {
}
@RequestMapping(value="/message", method=RequestMethod.POST)
public String sendMessage(Model model, FlashStatus status) {
status.setActive();
model.addAttribute(Message.success("Yay!"));
return "redirect:/message";
}
@RequestMapping(value="/message", method=RequestMethod.POST, params="another")
public String sendMessageToSecondController(Model model, FlashStatus status) {
status.setActive();
model.addAttribute(Message.error("Nay!"));
return "redirect:/second/message";
}
}
@Controller
static class SecondMessageController {
@RequestMapping(value="/second/message", method=RequestMethod.GET)
public void message(Model model) {
}
}
private static class Message {
private final MessageType type;
private final String text;
private Message(MessageType type, String text) {
this.type = type;
this.text = text;
}
public static Message success(String text) {
return new Message(MessageType.success, text);
}
public static Message error(String text) {
return new Message(MessageType.error, text);
}
public MessageType getType() {
return type;
}
public String getText() {
return text;
}
public String toString() {
return type + ": " + text;
}
}
private static enum MessageType {
info, success, warning, error
}
@SuppressWarnings("unchecked")
private Object getModelAttribute(MockHttpServletRequest req, String key) {
Map<String, ?> model = (Map<String, ?>) req.getAttribute(ModelExposingViewResolver.REQUEST_ATTRIBITE_MODEL);
return model.get(key);
}
@SuppressWarnings("unchecked")
private Object getFlashAttribute(MockHttpServletRequest req, String key) {
String flashAttributesKey = FlashAttributesHandler.FLASH_ATTRIBUTES_SESSION_KEY;
Map<String, Object> attrs = (Map<String, Object>) req.getSession().getAttribute(flashAttributesKey);
return (attrs != null) ? attrs.get(key) : null;
}
private WebApplicationContext initServletWithModelExposingViewResolver(Class<?>... controllerClasses)
throws ServletException {
return initServlet(new ApplicationContextInitializer<GenericWebApplicationContext>() {
public void initialize(GenericWebApplicationContext wac) {
wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(ModelExposingViewResolver.class));
}
}, controllerClasses);
}
static class ModelExposingViewResolver implements ViewResolver {
static String REQUEST_ATTRIBITE_MODEL = "ModelExposingViewResolver.model";
public View resolveViewName(final String viewName, Locale locale) throws Exception {
return new View() {
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
request.setAttribute(REQUEST_ATTRIBITE_MODEL, model);
}
public String getContentType() {
return null;
}
};
}
}
}

View File

@@ -133,11 +133,14 @@ import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.multipart.support.StringMultipartFileEditor;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebArgumentResolverAdapter;
import org.springframework.web.servlet.mvc.method.support.ResponseContext;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
/**
@@ -1452,7 +1455,46 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
assertEquals("application/json", response.getHeader("Content-Type"));
assertEquals("homeJson", response.getContentAsString());
}
@Test
public void flashAttribute() throws Exception {
initServletWithControllers(MessageController.class);
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/messages");
HttpSession session = request.getSession();
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
// POST -> bind error
getServlet().service(request, response);
assertEquals(200, response.getStatus());
assertEquals("messages/new", response.getForwardedUrl());
assertTrue(RequestContextUtils.getFlashMap(request).isEmpty());
// POST -> success
request = new MockHttpServletRequest("POST", "/messages");
request.setSession(session);
request.addParameter("name", "Jeff");
response = new MockHttpServletResponse();
getServlet().service(request, response);
FlashMap flashMap = RequestContextUtils.getFlashMap(request);
assertNotNull(flashMap);
assertEquals(200, response.getStatus());
assertEquals("/messages/1?name=value&_flashKey=" + flashMap.getKey(), response.getRedirectedUrl());
// GET after POST
request = new MockHttpServletRequest("GET", "/messages/1");
request.setSession(session);
request.setParameter("_flashKey", String.valueOf(flashMap.getKey()));
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals(200, response.getStatus());
assertEquals("Got: yay!", response.getContentAsString());
}
/*
* Controllers
@@ -2761,6 +2803,32 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
return "homeJson";
}
}
@Controller
static class MessageController {
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.setRequiredFields("name");
}
@RequestMapping(value = "/messages/{id}", method = RequestMethod.GET)
public void message(ModelMap model, Writer writer) throws IOException {
writer.write("Got: " + model.get("successMessage"));
}
@RequestMapping(value = "/messages", method = RequestMethod.POST)
public void sendMessage(TestBean testBean, BindingResult result, ResponseContext responseContext) {
if (result.hasErrors()) {
responseContext.view("messages/new");
}
else {
responseContext.redirect("/messages/{id}").uriVariable("id", "1").queryParam("name", "value")
.flashAttribute("successMessage", "yay!");
}
}
}
// Test cases deleted from the original SevletAnnotationControllerTests:

View File

@@ -56,7 +56,7 @@ public class DefaultMethodReturnValueHandlerTests {
public void setUp() {
mavResolvers = new ArrayList<ModelAndViewResolver>();
handler = new DefaultMethodReturnValueHandler(mavResolvers);
mavContainer = new ModelAndViewContainer(new ExtendedModelMap());
mavContainer = new ModelAndViewContainer();
request = new ServletWebRequest(new MockHttpServletRequest());
}
@@ -69,7 +69,7 @@ public class DefaultMethodReturnValueHandlerTests {
handler.handleReturnValue(testBean, testBeanType, mavContainer, request);
assertEquals("viewName", mavContainer.getViewName());
assertSame(testBean, mavContainer.getAttribute("modelAttrName"));
assertSame(testBean, mavContainer.getModel().get("modelAttrName"));
assertTrue(mavContainer.isResolveView());
}

View File

@@ -26,12 +26,9 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.method.annotation.support.DefaultMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler;
import org.springframework.web.servlet.view.InternalResourceView;
/**
@@ -50,7 +47,7 @@ public class ViewMethodReturnValueHandlerTests {
@Before
public void setUp() {
handler = new ViewMethodReturnValueHandler();
mavContainer = new ModelAndViewContainer(new ExtendedModelMap());
mavContainer = new ModelAndViewContainer();
webRequest = new ServletWebRequest(new MockHttpServletRequest());
}

View File

@@ -0,0 +1,174 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.support;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.FlashMapManager;
/**
* Test fixture for {@link DefaultFlashMapManager} tests.
*
* @author Rossen Stoyanchev
*/
public class DefaultFlashMapManagerTests {
private DefaultFlashMapManager flashMapManager;
private MockHttpServletRequest request;
@Before
public void setup() {
this.flashMapManager = new DefaultFlashMapManager();
this.request = new MockHttpServletRequest();
}
@Test
public void requestAlreadyStarted() {
request.setAttribute(FlashMapManager.CURRENT_FLASH_MAP_ATTRIBUTE, new FlashMap());
boolean actual = this.flashMapManager.requestStarted(this.request);
assertFalse(actual);
}
@Test
public void createFlashMap() {
boolean actual = this.flashMapManager.requestStarted(this.request);
FlashMap flashMap = RequestContextUtils.getFlashMap(this.request);
assertTrue(actual);
assertNotNull(flashMap);
assertNotNull(flashMap.getKey());
assertEquals("_flashKey", flashMap.getKeyParameterName());
}
@Test
public void createFlashMapWithoutKey() {
this.flashMapManager.setUseUniqueFlashKey(false);
boolean actual = this.flashMapManager.requestStarted(this.request);
FlashMap flashMap = RequestContextUtils.getFlashMap(this.request);
assertTrue(actual);
assertNotNull(flashMap);
assertNull(flashMap.getKey());
assertNull(flashMap.getKeyParameterName());
}
@Test
public void lookupPreviousFlashMap() {
FlashMap flashMap = new FlashMap("key", "_flashKey");
flashMap.put("name", "value");
Map<String, FlashMap> allFlashMaps = new HashMap<String, FlashMap>();
allFlashMaps.put(flashMap.getKey(), flashMap);
this.request.getSession().setAttribute(DefaultFlashMapManager.FLASH_MAPS_SESSION_ATTRIBUTE, allFlashMaps);
this.request.addParameter("_flashKey", flashMap.getKey());
this.flashMapManager.requestStarted(this.request);
assertSame(flashMap, request.getAttribute(DefaultFlashMapManager.PREVIOUS_FLASH_MAP_ATTRIBUTE));
assertEquals("value", request.getAttribute("name"));
}
@Test
public void lookupPreviousFlashMapWithoutKey() {
Map<String, FlashMap> allFlashMaps = new HashMap<String, FlashMap>();
request.getSession().setAttribute(DefaultFlashMapManager.FLASH_MAPS_SESSION_ATTRIBUTE, allFlashMaps);
FlashMap flashMap = new FlashMap();
flashMap.put("name", "value");
allFlashMaps.put("key", flashMap);
this.flashMapManager.setUseUniqueFlashKey(false);
this.flashMapManager.requestStarted(this.request);
assertSame(flashMap, this.request.getAttribute(DefaultFlashMapManager.PREVIOUS_FLASH_MAP_ATTRIBUTE));
assertEquals("value", this.request.getAttribute("name"));
}
@SuppressWarnings("static-access")
@Test
public void removeExpired() throws InterruptedException {
FlashMap[] flashMapArray = new FlashMap[5];
flashMapArray[0] = new FlashMap("key0", "_flashKey");
flashMapArray[1] = new FlashMap("key1", "_flashKey");
flashMapArray[2] = new FlashMap("key2", "_flashKey");
flashMapArray[3] = new FlashMap("key3", "_flashKey");
flashMapArray[4] = new FlashMap("key4", "_flashKey");
Map<String, FlashMap> allFlashMaps = new HashMap<String, FlashMap>();
for (FlashMap flashMap : flashMapArray) {
allFlashMaps.put(flashMap.getKey(), flashMap);
}
flashMapArray[1].startExpirationPeriod(0);
flashMapArray[3].startExpirationPeriod(0);
Thread.currentThread().sleep(5);
MockHttpServletRequest request = new MockHttpServletRequest();
request.getSession().setAttribute(DefaultFlashMapManager.FLASH_MAPS_SESSION_ATTRIBUTE, allFlashMaps);
request.setParameter("_flashKey", "key0");
this.flashMapManager.requestStarted(request);
assertEquals(2, allFlashMaps.size());
assertNotNull(allFlashMaps.get("key2"));
assertNotNull(allFlashMaps.get("key4"));
}
@SuppressWarnings({ "unchecked", "static-access" })
@Test
public void saveFlashMap() throws InterruptedException {
FlashMap flashMap = new FlashMap("key", "_flashKey");
flashMap.put("name", "value");
request.setAttribute(DefaultFlashMapManager.CURRENT_FLASH_MAP_ATTRIBUTE, flashMap);
this.flashMapManager.setFlashMapTimeout(0);
this.flashMapManager.requestCompleted(this.request);
Thread.currentThread().sleep(1);
String sessionKey = DefaultFlashMapManager.FLASH_MAPS_SESSION_ATTRIBUTE;
Map<String, FlashMap> allFlashMaps = (Map<String, FlashMap>) this.request.getSession().getAttribute(sessionKey);
assertSame(flashMap, allFlashMaps.get("key"));
assertTrue(flashMap.isExpired());
}
@Test
public void saveEmptyFlashMap() throws InterruptedException {
FlashMap flashMap = new FlashMap("key", "_flashKey");
request.setAttribute(DefaultFlashMapManager.CURRENT_FLASH_MAP_ATTRIBUTE, flashMap);
this.flashMapManager.setFlashMapTimeout(0);
this.flashMapManager.requestCompleted(this.request);
assertNull(this.request.getSession().getAttribute(DefaultFlashMapManager.FLASH_MAPS_SESSION_ATTRIBUTE));
}
}

View File

@@ -32,6 +32,8 @@ import org.springframework.beans.TestBean;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.FlashMapManager;
import org.springframework.web.servlet.View;
import org.springframework.web.util.WebUtils;
@@ -90,6 +92,35 @@ public class RedirectViewTests {
assertEquals(201, response.getStatus());
assertEquals("http://url.somewhere.com", response.getHeader("Location"));
}
@Test
public void flashMap() throws Exception {
RedirectView rv = new RedirectView();
rv.setUrl("http://url.somewhere.com");
rv.setHttp10Compatible(false);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
FlashMap flashMap = new FlashMap("key", "_flashKey");
flashMap.put("name", "value");
request.setAttribute(FlashMapManager.CURRENT_FLASH_MAP_ATTRIBUTE, flashMap);
rv.render(new HashMap<String, Object>(), request, response);
assertEquals(303, response.getStatus());
assertEquals("http://url.somewhere.com?_flashKey=key", response.getHeader("Location"));
}
@Test
public void emptyFlashMap() throws Exception {
RedirectView rv = new RedirectView();
rv.setUrl("http://url.somewhere.com");
rv.setHttp10Compatible(false);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
FlashMap flashMap = new FlashMap("key", "_flashKey");
request.setAttribute(FlashMapManager.CURRENT_FLASH_MAP_ATTRIBUTE, flashMap);
rv.render(new HashMap<String, Object>(), request, response);
assertEquals(303, response.getStatus());
assertEquals("http://url.somewhere.com", response.getHeader("Location"));
}
@Test
public void emptyMap() throws Exception {