diff --git a/spring-web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java b/spring-web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java index 2e5f8147ed..481fcbe4ff 100644 --- a/spring-web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java +++ b/spring-web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java @@ -44,9 +44,9 @@ import org.springframework.web.bind.support.SimpleSessionStatus; */ public class ModelAndViewContainer { - private Object view; + private boolean ignoreDefaultModelOnRedirect = false; - private boolean requestHandled = false; + private Object view; private final ModelMap defaultModel = new BindingAwareModelMap(); @@ -54,10 +54,26 @@ public class ModelAndViewContainer { private boolean redirectModelScenario = false; - private boolean ignoreDefaultModelOnRedirect = false; - private final SessionStatus sessionStatus = new SimpleSessionStatus(); + private boolean requestHandled = false; + + + /** + * By default the content of the "default" model is used both during + * rendering and redirect scenarios. Alternatively controller methods + * can declare an argument of type {@code RedirectAttributes} and use + * it to provide attributes to prepare the redirect URL. + *
Setting this flag to {@code true} guarantees the "default" model is + * never used in a redirect scenario even if a RedirectAttributes argument + * is not declared. Setting it to {@code false} means the "default" model + * may be used in a redirect if the controller method doesn't declare a + * RedirectAttributes argument. + *
The default setting is {@code false}. + */ + public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect) { + this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect; + } /** * Set a view name to be resolved by the DispatcherServlet via a ViewResolver. @@ -100,32 +116,10 @@ public class ModelAndViewContainer { } /** - * Signal a scenario where the request is handled directly. - *
A {@link HandlerMethodReturnValueHandler} may use this flag to - * indicate the response has been fully handled and view resolution - * is not required (e.g. {@code @ResponseBody}). - *
A {@link HandlerMethodArgumentResolver} may also use this flag - * to indicate the presence of an argument (e.g. - * {@code ServletResponse} or {@code OutputStream}) that may lead to - * a complete response depending on the method return value. - *
The default value is {@code true}. - */ - public void setRequestHandled(boolean requestHandled) { - this.requestHandled = requestHandled; - } - - /** - * Whether the request is handled directly. - */ - public boolean isRequestHandled() { - return this.requestHandled; - } - - /** - * Return the model to use: the "default" or the "redirect" model. - *
The default model is used if {@code "redirectModelScenario=false"} or - * if the redirect model is {@code null} (i.e. it wasn't declared as a - * method argument) and {@code ignoreDefaultModelOnRedirect=false}. + * Return the model to use -- either the "default" or the "redirect" model. + * The default model is used if {@code redirectModelScenario=false} or + * there is no redirect model (i.e. RedirectAttributes was not declared as + * a method argument) and {@code ignoreDefaultModelOnRedirect=false}. */ public ModelMap getModel() { if (useDefaultModel()) { @@ -154,25 +148,13 @@ public class ModelAndViewContainer { } /** - * Signal the conditions are in place for using a redirect model. - * Typically that means the controller has returned a redirect instruction. + * Whether the controller has returned a redirect instruction, e.g. a + * "redirect:" prefixed view name, a RedirectView instance, etc. */ public void setRedirectModelScenario(boolean redirectModelScenario) { this.redirectModelScenario = redirectModelScenario; } - /** - * When set to {@code true} the default model is never used in a redirect - * scenario. So if a redirect model is not available, an empty model is - * used instead. - *
When set to {@code false} the default model can be used in a redirect - * scenario if a redirect model is not available. - *
The default setting is {@code false}. - */ - public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect) { - this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect; - } - /** * Return the {@link SessionStatus} instance to use that can be used to * signal that session processing is complete. @@ -181,6 +163,24 @@ public class ModelAndViewContainer { return this.sessionStatus; } + /** + * Whether the request has been handled fully within the handler, e.g. + * {@code @ResponseBody} method, and therefore view resolution is not + * necessary. This flag can also be set when controller methods declare an + * argument of type {@code ServletResponse} or {@code OutputStream}). + *
The default value is {@code false}. + */ + public void setRequestHandled(boolean requestHandled) { + this.requestHandled = requestHandled; + } + + /** + * Whether the request has been handled fully within the handler. + */ + public boolean isRequestHandled() { + return this.requestHandled; + } + /** * Add the supplied attribute to the underlying model. * A shortcut for {@code getModel().addAttribute(String, Object)}. diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java index 5555feaec5..45f8cbbf2b 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -16,15 +16,23 @@ package org.springframework.web.method.annotation; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.mock; + import java.lang.reflect.Method; import java.util.Arrays; import org.junit.Before; import org.junit.Test; - import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.ui.Model; +import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; import org.springframework.web.HttpSessionRequiredException; import org.springframework.web.bind.WebDataBinder; @@ -39,8 +47,6 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolverCompo import org.springframework.web.method.support.InvocableHandlerMethod; import org.springframework.web.method.support.ModelAndViewContainer; -import static org.junit.Assert.*; -import static org.mockito.BDDMockito.*; /** * Text fixture for {@link ModelFactory} tests. @@ -49,7 +55,7 @@ import static org.mockito.BDDMockito.*; */ public class ModelFactoryTests { - private Object handler = new ModelHandler(); + private TestController controller = new TestController(); private InvocableHandlerMethod handleMethod; @@ -61,31 +67,37 @@ public class ModelFactoryTests { private NativeWebRequest webRequest; + @Before public void setUp() throws Exception { - Class> handlerType = handler.getClass(); - handleMethod = new InvocableHandlerMethod(handler, handlerType.getDeclaredMethod("handle")); - Method method = handlerType.getDeclaredMethod("handleSessionAttr", String.class); - handleSessionAttrMethod = new InvocableHandlerMethod(handler, method); - sessionAttributeStore = new DefaultSessionAttributeStore(); - sessionAttrsHandler = new SessionAttributesHandler(handlerType, sessionAttributeStore); - webRequest = new ServletWebRequest(new MockHttpServletRequest()); + this.controller = new TestController(); + + Method method = TestController.class.getDeclaredMethod("handle"); + this.handleMethod = new InvocableHandlerMethod(this.controller, method); + + method = TestController.class.getDeclaredMethod("handleSessionAttr", String.class); + this.handleSessionAttrMethod = new InvocableHandlerMethod(this.controller, method); + + this.sessionAttributeStore = new DefaultSessionAttributeStore(); + this.sessionAttrsHandler = new SessionAttributesHandler(TestController.class, this.sessionAttributeStore); + this.webRequest = new ServletWebRequest(new MockHttpServletRequest()); } + @Test public void modelAttributeMethod() throws Exception { ModelFactory modelFactory = createModelFactory("modelAttr", Model.class); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); - modelFactory.initModel(webRequest, mavContainer, handleMethod); + modelFactory.initModel(this.webRequest, mavContainer, this.handleMethod); assertEquals(Boolean.TRUE, mavContainer.getModel().get("modelAttr")); } @Test - public void modelAttributeMethodWithSpecifiedName() throws Exception { + public void modelAttributeMethodWithExplicitName() throws Exception { ModelFactory modelFactory = createModelFactory("modelAttrWithName"); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); - modelFactory.initModel(webRequest, mavContainer, handleMethod); + modelFactory.initModel(this.webRequest, mavContainer, this.handleMethod); assertEquals(Boolean.TRUE, mavContainer.getModel().get("name")); } @@ -94,7 +106,7 @@ public class ModelFactoryTests { public void modelAttributeMethodWithNameByConvention() throws Exception { ModelFactory modelFactory = createModelFactory("modelAttrConvention"); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); - modelFactory.initModel(webRequest, mavContainer, handleMethod); + modelFactory.initModel(this.webRequest, mavContainer, this.handleMethod); assertEquals(Boolean.TRUE, mavContainer.getModel().get("boolean")); } @@ -103,7 +115,7 @@ public class ModelFactoryTests { public void modelAttributeMethodWithNullReturnValue() throws Exception { ModelFactory modelFactory = createModelFactory("nullModelAttr"); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); - modelFactory.initModel(webRequest, mavContainer, handleMethod); + modelFactory.initModel(this.webRequest, mavContainer, this.handleMethod); assertTrue(mavContainer.containsAttribute("name")); assertNull(mavContainer.getModel().get("name")); @@ -111,97 +123,119 @@ public class ModelFactoryTests { @Test public void sessionAttribute() throws Exception { - sessionAttributeStore.storeAttribute(webRequest, "sessionAttr", "sessionAttrValue"); + this.sessionAttributeStore.storeAttribute(this.webRequest, "sessionAttr", "sessionAttrValue"); // Resolve successfully handler session attribute once assertTrue(sessionAttrsHandler.isHandlerSessionAttribute("sessionAttr", null)); ModelFactory modelFactory = createModelFactory("modelAttr", Model.class); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); - modelFactory.initModel(webRequest, mavContainer, handleMethod); + modelFactory.initModel(this.webRequest, mavContainer, this.handleMethod); assertEquals("sessionAttrValue", mavContainer.getModel().get("sessionAttr")); } @Test - public void requiredSessionAttribute() throws Exception { - ModelFactory modelFactory = new ModelFactory(null, null, sessionAttrsHandler); + public void sessionAttributeNotPresent() throws Exception { + ModelFactory modelFactory = new ModelFactory(null, null, this.sessionAttrsHandler); try { - modelFactory.initModel(webRequest, new ModelAndViewContainer(), handleSessionAttrMethod); + modelFactory.initModel(this.webRequest, new ModelAndViewContainer(), this.handleSessionAttrMethod); fail("Expected HttpSessionRequiredException"); - } catch (HttpSessionRequiredException e) { } + } + catch (HttpSessionRequiredException e) { + // expected + } - sessionAttributeStore.storeAttribute(webRequest, "sessionAttr", "sessionAttrValue"); + this.sessionAttributeStore.storeAttribute(this.webRequest, "sessionAttr", "sessionAttrValue"); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); - modelFactory.initModel(webRequest, mavContainer, handleSessionAttrMethod); + modelFactory.initModel(this.webRequest, mavContainer, this.handleSessionAttrMethod); assertEquals("sessionAttrValue", mavContainer.getModel().get("sessionAttr")); } @Test - public void updateModelBindingResultKeys() throws Exception { - String attrName = "attr1"; - Object attrValue = new Object(); - ModelAndViewContainer mavContainer = new ModelAndViewContainer(); - mavContainer.addAttribute(attrName, attrValue); + public void updateModelBindingResult() throws Exception { + String commandName = "attr1"; + Object command = new Object(); + ModelAndViewContainer container = new ModelAndViewContainer(); + container.addAttribute(commandName, command); - WebDataBinder dataBinder = new WebDataBinder(attrValue, attrName); + WebDataBinder dataBinder = new WebDataBinder(command, commandName); WebDataBinderFactory binderFactory = mock(WebDataBinderFactory.class); - given(binderFactory.createBinder(webRequest, attrValue, attrName)).willReturn(dataBinder); + given(binderFactory.createBinder(this.webRequest, command, commandName)).willReturn(dataBinder); - ModelFactory modelFactory = new ModelFactory(null, binderFactory, sessionAttrsHandler); - modelFactory.updateModel(webRequest, mavContainer); + ModelFactory modelFactory = new ModelFactory(null, binderFactory, this.sessionAttrsHandler); + modelFactory.updateModel(this.webRequest, container); - assertEquals(attrValue, mavContainer.getModel().remove(attrName)); - assertSame(dataBinder.getBindingResult(), mavContainer.getModel().remove(bindingResultKey(attrName))); - assertEquals(0, mavContainer.getModel().size()); + assertEquals(command, container.getModel().get(commandName)); + assertSame(dataBinder.getBindingResult(), container.getModel().get(bindingResultKey(commandName))); + assertEquals(2, container.getModel().size()); } @Test - public void updateModelSessionStatusComplete() throws Exception { - String attrName = "sessionAttr"; - String attrValue = "sessionAttrValue"; + public void updateModelSessionAttributesSaved() throws Exception { + String attributeName = "sessionAttr"; + String attribute = "value"; + ModelAndViewContainer container = new ModelAndViewContainer(); + container.addAttribute(attributeName, attribute); - ModelAndViewContainer mavContainer = new ModelAndViewContainer(); - mavContainer.addAttribute(attrName, attrValue); - mavContainer.getSessionStatus().setComplete(); - sessionAttributeStore.storeAttribute(webRequest, attrName, attrValue); - - // Resolve successfully handler session attribute once - assertTrue(sessionAttrsHandler.isHandlerSessionAttribute(attrName, null)); - - WebDataBinder dataBinder = new WebDataBinder(attrValue, attrName); + WebDataBinder dataBinder = new WebDataBinder(attribute, attributeName); WebDataBinderFactory binderFactory = mock(WebDataBinderFactory.class); - given(binderFactory.createBinder(webRequest, attrValue, attrName)).willReturn(dataBinder); + given(binderFactory.createBinder(this.webRequest, attribute, attributeName)).willReturn(dataBinder); - ModelFactory modelFactory = new ModelFactory(null, binderFactory, sessionAttrsHandler); - modelFactory.updateModel(webRequest, mavContainer); + ModelFactory modelFactory = new ModelFactory(null, binderFactory, this.sessionAttrsHandler); + modelFactory.updateModel(this.webRequest, container); - assertEquals(attrValue, mavContainer.getModel().get(attrName)); - assertNull(sessionAttributeStore.retrieveAttribute(webRequest, attrName)); + assertEquals(attribute, container.getModel().get(attributeName)); + assertEquals(attribute, this.sessionAttributeStore.retrieveAttribute(this.webRequest, attributeName)); } + @Test + public void updateModelSessionAttributesRemoved() throws Exception { + String attributeName = "sessionAttr"; + String attribute = "value"; + ModelAndViewContainer container = new ModelAndViewContainer(); + container.addAttribute(attributeName, attribute); + + // Store and resolve once (to be "remembered") + this.sessionAttributeStore.storeAttribute(this.webRequest, attributeName, attribute); + this.sessionAttrsHandler.isHandlerSessionAttribute(attributeName, null); + + WebDataBinder dataBinder = new WebDataBinder(attribute, attributeName); + WebDataBinderFactory binderFactory = mock(WebDataBinderFactory.class); + given(binderFactory.createBinder(this.webRequest, attribute, attributeName)).willReturn(dataBinder); + + container.getSessionStatus().setComplete(); + + ModelFactory modelFactory = new ModelFactory(null, binderFactory, this.sessionAttrsHandler); + modelFactory.updateModel(this.webRequest, container); + + assertEquals(attribute, container.getModel().get(attributeName)); + assertNull(this.sessionAttributeStore.retrieveAttribute(this.webRequest, attributeName)); + } + + private String bindingResultKey(String key) { return BindingResult.MODEL_KEY_PREFIX + key; } private ModelFactory createModelFactory(String methodName, Class>... parameterTypes) throws Exception{ - Method method = ModelHandler.class.getMethod(methodName, parameterTypes); + Method method = TestController.class.getMethod(methodName, parameterTypes); HandlerMethodArgumentResolverComposite argResolvers = new HandlerMethodArgumentResolverComposite(); argResolvers.addResolver(new ModelMethodProcessor()); - InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(handler, method); + InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(this.controller, method); handlerMethod.setHandlerMethodArgumentResolvers(argResolvers); handlerMethod.setDataBinderFactory(null); handlerMethod.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer()); - return new ModelFactory(Arrays.asList(handlerMethod), null, sessionAttrsHandler); + return new ModelFactory(Arrays.asList(handlerMethod), null, this.sessionAttrsHandler); } @SessionAttributes("sessionAttr") @SuppressWarnings("unused") - private static class ModelHandler { + private static class TestController { @ModelAttribute public void modelAttr(Model model) { @@ -229,4 +263,5 @@ public class ModelFactoryTests { public void handleSessionAttr(@ModelAttribute("sessionAttr") String sessionAttr) { } } + } diff --git a/spring-web/src/test/java/org/springframework/web/method/support/ModelAndViewContainerTests.java b/spring-web/src/test/java/org/springframework/web/method/support/ModelAndViewContainerTests.java index 556fe1c227..114c6712d8 100644 --- a/spring-web/src/test/java/org/springframework/web/method/support/ModelAndViewContainerTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/support/ModelAndViewContainerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 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. @@ -16,12 +16,13 @@ package org.springframework.web.method.support; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import org.junit.Before; import org.junit.Test; - import org.springframework.ui.ModelMap; -import static org.junit.Assert.*; /** * Test fixture for {@link ModelAndViewContainer}. @@ -33,44 +34,46 @@ public class ModelAndViewContainerTests { private ModelAndViewContainer mavContainer; + @Before public void setup() { this.mavContainer = new ModelAndViewContainer(); } + @Test public void getModel() { this.mavContainer.addAttribute("name", "value"); assertEquals(1, this.mavContainer.getModel().size()); + assertEquals("value", this.mavContainer.getModel().get("name")); } @Test - public void getModelRedirectModel() { - ModelMap redirectModel = new ModelMap("name", "redirectValue"); - this.mavContainer.setRedirectModel(redirectModel); - this.mavContainer.addAttribute("name", "value"); - - assertEquals("Default model should be used if not in redirect scenario", - "value", this.mavContainer.getModel().get("name")); - + public void redirectScenarioWithRedirectModel() { + this.mavContainer.addAttribute("name1", "value1"); + this.mavContainer.setRedirectModel(new ModelMap("name2", "value2")); this.mavContainer.setRedirectModelScenario(true); - assertEquals("Redirect model should be used in redirect scenario", - "redirectValue", this.mavContainer.getModel().get("name")); + assertEquals(1, this.mavContainer.getModel().size()); + assertEquals("value2", this.mavContainer.getModel().get("name2")); } @Test - public void getModelIgnoreDefaultModelOnRedirect() { + public void redirectScenarioWithoutRedirectModel() { this.mavContainer.addAttribute("name", "value"); this.mavContainer.setRedirectModelScenario(true); - assertEquals("Default model should be used since no redirect model was provided", - 1, this.mavContainer.getModel().size()); + assertEquals(1, this.mavContainer.getModel().size()); + assertEquals("value", this.mavContainer.getModel().get("name")); + } + @Test + public void ignoreDefaultModel() { this.mavContainer.setIgnoreDefaultModelOnRedirect(true); + this.mavContainer.addAttribute("name", "value"); + this.mavContainer.setRedirectModelScenario(true); - assertEquals("Empty model should be returned if no redirect model is available", - 0, this.mavContainer.getModel().size()); + assertTrue(this.mavContainer.getModel().isEmpty()); } }