Skip to content

Commit 9e3bb1e

Browse files
rstoyanchevjhoeller
authored andcommitted
Ensure RedirectModel is initialized
This commit fixes an old bug in ModelAndViewContainer where getModel returns a new ModelMap instance that isn't saved and re-used. Issue: SPR-14045 (cherry picked from commit d7062f6)
1 parent c2eb5e1 commit 9e3bb1e

File tree

3 files changed

+101
-72
lines changed

3 files changed

+101
-72
lines changed

spring-web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java

Lines changed: 48 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -44,24 +44,35 @@
4444
*/
4545
public class ModelAndViewContainer {
4646

47-
private Object view;
47+
private boolean ignoreDefaultModelOnRedirect = false;
4848

49-
private boolean requestHandled = false;
49+
private Object view;
5050

5151
private final ModelMap defaultModel = new BindingAwareModelMap();
5252

5353
private ModelMap redirectModel;
5454

5555
private boolean redirectModelScenario = false;
5656

57-
private boolean ignoreDefaultModelOnRedirect = false;
58-
5957
private final SessionStatus sessionStatus = new SimpleSessionStatus();
6058

59+
private boolean requestHandled = false;
60+
61+
6162
/**
62-
* Create a new instance.
63+
* By default the content of the "default" model is used both during
64+
* rendering and redirect scenarios. Alternatively controller methods
65+
* can declare an argument of type {@code RedirectAttributes} and use
66+
* it to provide attributes to prepare the redirect URL.
67+
* <p>Setting this flag to {@code true} guarantees the "default" model is
68+
* never used in a redirect scenario even if a RedirectAttributes argument
69+
* is not declared. Setting it to {@code false} means the "default" model
70+
* may be used in a redirect if the controller method doesn't declare a
71+
* RedirectAttributes argument.
72+
* <p>The default setting is {@code false}.
6373
*/
64-
public ModelAndViewContainer() {
74+
public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect) {
75+
this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect;
6576
}
6677

6778
/**
@@ -105,47 +116,28 @@ public boolean isViewReference() {
105116
}
106117

107118
/**
108-
* Signal a scenario where the request is handled directly.
109-
* <p>A {@link HandlerMethodReturnValueHandler} may use this flag to
110-
* indicate the response has been fully handled and view resolution
111-
* is not required (e.g. {@code @ResponseBody}).
112-
* <p>A {@link HandlerMethodArgumentResolver} may also use this flag
113-
* to indicate the presence of an argument (e.g.
114-
* {@code ServletResponse} or {@code OutputStream}) that may lead to
115-
* a complete response depending on the method return value.
116-
* <p>The default value is {@code true}.
117-
*/
118-
public void setRequestHandled(boolean requestHandled) {
119-
this.requestHandled = requestHandled;
120-
}
121-
122-
/**
123-
* Whether the request is handled directly.
124-
*/
125-
public boolean isRequestHandled() {
126-
return this.requestHandled;
127-
}
128-
129-
/**
130-
* Return the model to use: the "default" or the "redirect" model.
131-
* <p>The default model is used if {@code "redirectModelScenario=false"} or
132-
* if the redirect model is {@code null} (i.e. it wasn't declared as a
133-
* method argument) and {@code ignoreDefaultModelOnRedirect=false}.
119+
* Return the model to use: either the "default" or the "redirect" model.
120+
* The default model is used if {@code redirectModelScenario=false} or
121+
* there is no redirect model (i.e. RedirectAttributes was not declared as
122+
* a method argument) and {@code ignoreDefaultModelOnRedirect=false}.
134123
*/
135124
public ModelMap getModel() {
136125
if (useDefaultModel()) {
137126
return this.defaultModel;
138127
}
139128
else {
140-
return (this.redirectModel != null) ? this.redirectModel : new ModelMap();
129+
if (this.redirectModel == null) {
130+
this.redirectModel = new ModelMap();
131+
}
132+
return this.redirectModel;
141133
}
142134
}
143135

144136
/**
145137
* Whether to use the default model or the redirect model.
146138
*/
147139
private boolean useDefaultModel() {
148-
return !this.redirectModelScenario || ((this.redirectModel == null) && !this.ignoreDefaultModelOnRedirect);
140+
return (!this.redirectModelScenario || (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect));
149141
}
150142

151143
/**
@@ -159,31 +151,37 @@ public void setRedirectModel(ModelMap redirectModel) {
159151
}
160152

161153
/**
162-
* Signal the conditions are in place for using a redirect model.
163-
* Typically that means the controller has returned a redirect instruction.
154+
* Whether the controller has returned a redirect instruction, e.g. a
155+
* "redirect:" prefixed view name, a RedirectView instance, etc.
164156
*/
165157
public void setRedirectModelScenario(boolean redirectModelScenario) {
166158
this.redirectModelScenario = redirectModelScenario;
167159
}
168160

169161
/**
170-
* When set to {@code true} the default model is never used in a redirect
171-
* scenario. So if a redirect model is not available, an empty model is
172-
* used instead.
173-
* <p>When set to {@code false} the default model can be used in a redirect
174-
* scenario if a redirect model is not available.
175-
* <p>The default setting is {@code false}.
162+
* Return the {@link SessionStatus} instance to use that can be used to
163+
* signal that session processing is complete.
176164
*/
177-
public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect) {
178-
this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect;
165+
public SessionStatus getSessionStatus() {
166+
return this.sessionStatus;
179167
}
180168

181169
/**
182-
* Return the {@link SessionStatus} instance to use that can be used to
183-
* signal that session processing is complete.
170+
* Whether the request has been handled fully within the handler, e.g.
171+
* {@code @ResponseBody} method, and therefore view resolution is not
172+
* necessary. This flag can also be set when controller methods declare an
173+
* argument of type {@code ServletResponse} or {@code OutputStream}).
174+
* <p>The default value is {@code false}.
184175
*/
185-
public SessionStatus getSessionStatus() {
186-
return sessionStatus;
176+
public void setRequestHandled(boolean requestHandled) {
177+
this.requestHandled = requestHandled;
178+
}
179+
180+
/**
181+
* Whether the request has been handled fully within the handler.
182+
*/
183+
public boolean isRequestHandled() {
184+
return this.requestHandled;
187185
}
188186

189187
/**
@@ -243,6 +241,7 @@ public boolean containsAttribute(String name) {
243241
return getModel().containsAttribute(name);
244242
}
245243

244+
246245
/**
247246
* Return diagnostic information.
248247
*/
Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -16,12 +16,13 @@
1616

1717
package org.springframework.web.method.support;
1818

19-
import static org.junit.Assert.assertEquals;
20-
2119
import org.junit.Before;
2220
import org.junit.Test;
21+
2322
import org.springframework.ui.ModelMap;
2423

24+
import static org.junit.Assert.*;
25+
2526
/**
2627
* Test fixture for {@link ModelAndViewContainer}.
2728
*
@@ -32,44 +33,57 @@ public class ModelAndViewContainerTests {
3233

3334
private ModelAndViewContainer mavContainer;
3435

36+
3537
@Before
3638
public void setup() {
3739
this.mavContainer = new ModelAndViewContainer();
3840
}
3941

42+
4043
@Test
4144
public void getModel() {
4245
this.mavContainer.addAttribute("name", "value");
4346
assertEquals(1, this.mavContainer.getModel().size());
47+
assertEquals("value", this.mavContainer.getModel().get("name"));
4448
}
4549

4650
@Test
47-
public void getModelRedirectModel() {
48-
ModelMap redirectModel = new ModelMap("name", "redirectValue");
49-
this.mavContainer.setRedirectModel(redirectModel);
50-
this.mavContainer.addAttribute("name", "value");
51+
public void redirectScenarioWithRedirectModel() {
52+
this.mavContainer.addAttribute("name1", "value1");
53+
this.mavContainer.setRedirectModel(new ModelMap("name2", "value2"));
54+
this.mavContainer.setRedirectModelScenario(true);
5155

52-
assertEquals("Default model should be used if not in redirect scenario",
53-
"value", this.mavContainer.getModel().get("name"));
56+
assertEquals(1, this.mavContainer.getModel().size());
57+
assertEquals("value2", this.mavContainer.getModel().get("name2"));
58+
}
5459

60+
@Test
61+
public void redirectScenarioWithoutRedirectModel() {
62+
this.mavContainer.addAttribute("name", "value");
5563
this.mavContainer.setRedirectModelScenario(true);
5664

57-
assertEquals("Redirect model should be used in redirect scenario",
58-
"redirectValue", this.mavContainer.getModel().get("name"));
65+
assertEquals(1, this.mavContainer.getModel().size());
66+
assertEquals("value", this.mavContainer.getModel().get("name"));
5967
}
6068

6169
@Test
62-
public void getModelIgnoreDefaultModelOnRedirect() {
70+
public void ignoreDefaultModel() {
71+
this.mavContainer.setIgnoreDefaultModelOnRedirect(true);
6372
this.mavContainer.addAttribute("name", "value");
6473
this.mavContainer.setRedirectModelScenario(true);
6574

66-
assertEquals("Default model should be used since no redirect model was provided",
67-
1, this.mavContainer.getModel().size());
75+
assertTrue(this.mavContainer.getModel().isEmpty());
76+
}
6877

78+
@Test // SPR-14045
79+
public void ignoreDefaultModelAndWithoutRedirectModel() {
6980
this.mavContainer.setIgnoreDefaultModelOnRedirect(true);
81+
this.mavContainer.setRedirectModelScenario(true);
82+
this.mavContainer.addAttribute("name", "value");
7083

71-
assertEquals("Empty model should be returned if no redirect model is available",
72-
0, this.mavContainer.getModel().size());
84+
assertEquals(1, this.mavContainer.getModel().size());
85+
assertEquals("value", this.mavContainer.getModel().get("name"));
7386
}
7487

88+
7589
}

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

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -16,16 +16,11 @@
1616

1717
package org.springframework.web.servlet.mvc.method.annotation;
1818

19-
import static org.junit.Assert.assertEquals;
20-
import static org.junit.Assert.assertFalse;
21-
import static org.junit.Assert.assertNotSame;
22-
import static org.junit.Assert.assertSame;
23-
import static org.junit.Assert.assertTrue;
24-
2519
import java.lang.reflect.Method;
2620

2721
import org.junit.Before;
2822
import org.junit.Test;
23+
2924
import org.springframework.core.MethodParameter;
3025
import org.springframework.mock.web.test.MockHttpServletRequest;
3126
import org.springframework.ui.ModelMap;
@@ -35,6 +30,8 @@
3530
import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap;
3631
import org.springframework.web.servlet.view.RedirectView;
3732

33+
import static org.junit.Assert.*;
34+
3835
/**
3936
* Test fixture with {@link ModelAndViewMethodReturnValueHandler}.
4037
*
@@ -50,6 +47,7 @@ public class ModelAndViewMethodReturnValueHandlerTests {
5047

5148
private MethodParameter returnParamModelAndView;
5249

50+
5351
@Before
5452
public void setUp() throws Exception {
5553
this.handler = new ModelAndViewMethodReturnValueHandler();
@@ -58,6 +56,7 @@ public void setUp() throws Exception {
5856
this.returnParamModelAndView = getReturnValueParam("modelAndView");
5957
}
6058

59+
6160
@Test
6261
public void supportsReturnType() throws Exception {
6362
assertTrue(handler.supportsReturnType(returnParamModelAndView));
@@ -131,16 +130,33 @@ public void handleRedirectAttributesWithoutRedirect() throws Exception {
131130
assertNotSame("RedirectAttributes should not be used if controller doesn't redirect", redirectAttributes, model);
132131
}
133132

133+
@Test // SPR-14045
134+
public void handleRedirectWithIgnoreDefaultModel() throws Exception {
135+
mavContainer.setIgnoreDefaultModelOnRedirect(true);
136+
137+
RedirectView redirectView = new RedirectView();
138+
ModelAndView mav = new ModelAndView(redirectView, "name", "value");
139+
handler.handleReturnValue(mav, returnParamModelAndView, mavContainer, webRequest);
140+
141+
ModelMap model = mavContainer.getModel();
142+
assertSame(redirectView, mavContainer.getView());
143+
assertEquals(1, model.size());
144+
assertEquals("value", model.get("name"));
145+
}
146+
134147

135148
private MethodParameter getReturnValueParam(String methodName) throws Exception {
136149
Method method = getClass().getDeclaredMethod(methodName);
137150
return new MethodParameter(method, -1);
138151
}
139152

153+
154+
@SuppressWarnings("unused")
140155
ModelAndView modelAndView() {
141156
return null;
142157
}
143158

159+
@SuppressWarnings("unused")
144160
String viewName() {
145161
return null;
146162
}

0 commit comments

Comments
 (0)