Skip to content

Commit d2a99de

Browse files
committed
Add ModelFactory test for HttpSessionRequiredException
1 parent c7a350c commit d2a99de

File tree

3 files changed

+78
-59
lines changed

3 files changed

+78
-59
lines changed

org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.web.bind.WebDataBinder;
3434
import org.springframework.web.bind.annotation.ModelAttribute;
3535
import org.springframework.web.bind.annotation.RequestMapping;
36+
import org.springframework.web.bind.annotation.SessionAttributes;
3637
import org.springframework.web.bind.support.SessionStatus;
3738
import org.springframework.web.bind.support.WebDataBinderFactory;
3839
import org.springframework.web.context.request.NativeWebRequest;
@@ -41,10 +42,11 @@
4142
import org.springframework.web.method.support.ModelAndViewContainer;
4243

4344
/**
44-
* Provides methods to create and update a model in the context of a given request.
45-
*
45+
* Contains methods for creating and updating a model. A {@link ModelFactory} is associated with a specific controller
46+
* through knowledge of its @{@link ModelAttribute} methods and @{@link SessionAttributes}.
47+
*
4648
* <p>{@link #initModel(NativeWebRequest, ModelAndViewContainer, HandlerMethod)} populates the model
47-
* with handler session attributes and attributes from model attribute methods.
49+
* with handler session attributes and by invoking model attribute methods.
4850
*
4951
* <p>{@link #updateModel(NativeWebRequest, ModelAndViewContainer, SessionStatus)} updates
5052
* the model (usually after the {@link RequestMapping} method has been called) promoting attributes
@@ -70,19 +72,19 @@ public final class ModelFactory {
7072
public ModelFactory(List<InvocableHandlerMethod> attributeMethods,
7173
WebDataBinderFactory binderFactory,
7274
SessionAttributesHandler sessionHandler) {
73-
this.attributeMethods = attributeMethods;
75+
this.attributeMethods = (attributeMethods != null) ? attributeMethods : new ArrayList<InvocableHandlerMethod>();
7476
this.binderFactory = binderFactory;
7577
this.sessionHandler = sessionHandler;
7678
}
7779

7880
/**
7981
* Populate the model for a request with attributes obtained in the following order:
8082
* <ol>
81-
* <li>Add known (i.e. previously accessed) handler session attributes
82-
* <li>Invoke model attribute methods
83-
* <li>Check if any {@link ModelAttribute}-annotated arguments need to be added from the session
83+
* <li>Retrieve "known" (i.e. have been in the model in prior requests) handler session attributes from the session
84+
* <li>Create attributes by invoking model attribute methods
85+
* <li>Check for not yet known handler session attributes in the session
8486
* </ol>
85-
* <p>As a general rule model attributes are added only once.
87+
* <p>As a general rule model attributes are added only once following the above order.
8688
*
8789
* @param request the current request
8890
* @param mavContainer the {@link ModelAndViewContainer} to add model attributes to
@@ -97,7 +99,7 @@ public void initModel(NativeWebRequest request, ModelAndViewContainer mavContain
9799

98100
invokeAttributeMethods(request, mavContainer);
99101

100-
addSessionAttributesByName(request, mavContainer, requestMethod);
102+
checkMissingSessionAttributes(request, mavContainer, requestMethod);
101103
}
102104

103105
/**
@@ -123,21 +125,26 @@ private void invokeAttributeMethods(NativeWebRequest request, ModelAndViewContai
123125
}
124126

125127
/**
126-
* Check if {@link ModelAttribute}-annotated arguments are handler session attributes and add them from the session.
128+
* Checks if any {@link ModelAttribute}-annotated handler method arguments are eligible as handler session
129+
* attributes, as defined by @{@link SessionAttributes}, and are not yet present in the model.
130+
* If so, attempts to retrieve them from the session and add them to the model.
131+
*
132+
* @throws HttpSessionRequiredException raised if a handler session attribute could is missing
127133
*/
128-
private void addSessionAttributesByName(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod requestMethod) {
134+
private void checkMissingSessionAttributes(NativeWebRequest request,
135+
ModelAndViewContainer mavContainer,
136+
HandlerMethod requestMethod) throws HttpSessionRequiredException {
129137
for (MethodParameter parameter : requestMethod.getMethodParameters()) {
130138
if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
131-
continue;
132-
}
133-
String attrName = getNameForParameter(parameter);
134-
if (!mavContainer.containsAttribute(attrName)) {
135-
if (sessionHandler.isHandlerSessionAttribute(attrName, parameter.getParameterType())) {
136-
Object attrValue = sessionHandler.retrieveAttribute(request, attrName);
137-
if (attrValue == null){
138-
new HttpSessionRequiredException("Session attribute '" + attrName + "' not found in session");
139+
String name = getNameForParameter(parameter);
140+
if (!mavContainer.containsAttribute(name)) {
141+
if (sessionHandler.isHandlerSessionAttribute(name, parameter.getParameterType())) {
142+
Object attrValue = sessionHandler.retrieveAttribute(request, name);
143+
if (attrValue == null){
144+
throw new HttpSessionRequiredException("Session attribute '" + name + "' not found in session");
145+
}
146+
mavContainer.addAttribute(name, attrValue);
139147
}
140-
mavContainer.addAttribute(attrName, attrValue);
141148
}
142149
}
143150
}

org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@
3838
* Resolves method arguments annotated with @{@link ModelAttribute}. Or if created in default resolution mode,
3939
* resolves any non-simple type argument even without an @{@link ModelAttribute}. See the constructor for details.
4040
*
41-
* <p>A model attribute argument value is obtained from the model or is created using its default constructor.
42-
* Data binding and optionally validation is then applied through a {@link WebDataBinder} instance. Validation is
43-
* invoked optionally when the argument is annotated with an {@code @Valid}.
41+
* <p>A model attribute argument is obtained from the model or otherwise is created with a default constructor.
42+
* Data binding and validation are applied through a {@link WebDataBinder} instance. Validation is applied
43+
* only when the argument is also annotated with {@code @Valid}.
4444
*
4545
* <p>Also handles return values from methods annotated with an @{@link ModelAttribute}. The return value is
4646
* added to the {@link ModelAndViewContainer}.

org.springframework.web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import static org.junit.Assert.assertNull;
2525
import static org.junit.Assert.assertSame;
2626
import static org.junit.Assert.assertTrue;
27+
import static org.junit.Assert.fail;
2728

2829
import java.lang.reflect.Method;
2930
import java.util.Arrays;
@@ -34,6 +35,7 @@
3435
import org.springframework.mock.web.MockHttpServletRequest;
3536
import org.springframework.ui.Model;
3637
import org.springframework.validation.BindingResult;
38+
import org.springframework.web.HttpSessionRequiredException;
3739
import org.springframework.web.bind.WebDataBinder;
3840
import org.springframework.web.bind.annotation.ModelAttribute;
3941
import org.springframework.web.bind.annotation.SessionAttributes;
@@ -59,6 +61,8 @@ public class ModelFactoryTests {
5961

6062
private InvocableHandlerMethod handleMethod;
6163

64+
private InvocableHandlerMethod handleSessionAttrMethod;
65+
6266
private SessionAttributesHandler handlerSessionAttributeStore;
6367

6468
private SessionAttributeStore sessionAttributeStore;
@@ -69,67 +73,77 @@ public class ModelFactoryTests {
6973

7074
@Before
7175
public void setUp() throws Exception {
72-
handleMethod = new InvocableHandlerMethod(handler, handler.getClass().getDeclaredMethod("handle"));
76+
Class<?> handlerType = handler.getClass();
77+
handleMethod = new InvocableHandlerMethod(handler, handlerType.getDeclaredMethod("handle"));
78+
Method method = handlerType.getDeclaredMethod("handleSessionAttr", String.class);
79+
handleSessionAttrMethod = new InvocableHandlerMethod(handler, method);
7380
sessionAttributeStore = new DefaultSessionAttributeStore();
74-
handlerSessionAttributeStore = new SessionAttributesHandler(handler.getClass(), sessionAttributeStore);
81+
handlerSessionAttributeStore = new SessionAttributesHandler(handlerType, sessionAttributeStore);
7582
mavContainer = new ModelAndViewContainer();
7683
webRequest = new ServletWebRequest(new MockHttpServletRequest());
7784
}
7885

7986
@Test
80-
public void createModel() throws Exception {
81-
ModelFactory modelFactory = createModelFactory("model", Model.class);
87+
public void addAttributeToModel() throws Exception {
88+
ModelFactory modelFactory = createModelFactory("modelAttr", Model.class);
8289
modelFactory.initModel(webRequest, mavContainer, handleMethod);
8390

84-
assertEquals(Boolean.TRUE, mavContainer.getAttribute("model"));
91+
assertEquals(Boolean.TRUE, mavContainer.getAttribute("modelAttr"));
8592
}
8693

8794
@Test
88-
public void createModelWithName() throws Exception {
89-
ModelFactory modelFactory = createModelFactory("modelWithName");
95+
public void returnAttributeWithName() throws Exception {
96+
ModelFactory modelFactory = createModelFactory("modelAttrWithName");
9097
modelFactory.initModel(webRequest, mavContainer, handleMethod);
9198

9299
assertEquals(Boolean.TRUE, mavContainer.getAttribute("name"));
93100
}
94101

95102
@Test
96-
public void createModelWithDefaultName() throws Exception {
97-
ModelFactory modelFactory = createModelFactory("modelWithDefaultName");
103+
public void returnAttributeWithNameByConvention() throws Exception {
104+
ModelFactory modelFactory = createModelFactory("modelAttrConvention");
98105
modelFactory.initModel(webRequest, mavContainer, handleMethod);
99106

100107
assertEquals(Boolean.TRUE, mavContainer.getAttribute("boolean"));
101108
}
102109

103110
@Test
104-
public void createModelWithExistingName() throws Exception {
105-
ModelFactory modelFactory = createModelFactory("modelWithName");
106-
modelFactory.initModel(webRequest, mavContainer, handleMethod);
107-
108-
assertEquals(Boolean.TRUE, mavContainer.getAttribute("name"));
109-
}
110-
111-
@Test
112-
public void createModelWithNullAttribute() throws Exception {
113-
ModelFactory modelFactory = createModelFactory("modelWithNullAttribute");
111+
public void returnNullAttributeValue() throws Exception {
112+
ModelFactory modelFactory = createModelFactory("nullModelAttr");
114113
modelFactory.initModel(webRequest, mavContainer, handleMethod);
115114

116115
assertTrue(mavContainer.containsAttribute("name"));
117116
assertNull(mavContainer.getAttribute("name"));
118117
}
119118

120119
@Test
121-
public void createModelExistingSessionAttributes() throws Exception {
122-
sessionAttributeStore.storeAttribute(webRequest, "sessAttr", "sessAttrValue");
120+
public void retrieveAttributeFromSession() throws Exception {
121+
sessionAttributeStore.storeAttribute(webRequest, "sessionAttr", "sessionAttrValue");
123122

124123
// Resolve successfully handler session attribute once
125-
assertTrue(handlerSessionAttributeStore.isHandlerSessionAttribute("sessAttr", null));
124+
assertTrue(handlerSessionAttributeStore.isHandlerSessionAttribute("sessionAttr", null));
126125

127-
ModelFactory modelFactory = createModelFactory("model", Model.class);
126+
ModelFactory modelFactory = createModelFactory("modelAttr", Model.class);
128127
modelFactory.initModel(webRequest, mavContainer, handleMethod);
129128

130-
assertEquals("sessAttrValue", mavContainer.getAttribute("sessAttr"));
129+
assertEquals("sessionAttrValue", mavContainer.getAttribute("sessionAttr"));
131130
}
132-
131+
132+
@Test
133+
public void requiredSessionAttribute() throws Exception {
134+
ModelFactory modelFactory = new ModelFactory(null, null, handlerSessionAttributeStore);
135+
136+
try {
137+
modelFactory.initModel(webRequest, mavContainer, handleSessionAttrMethod);
138+
fail("Expected HttpSessionRequiredException");
139+
} catch (HttpSessionRequiredException e) { }
140+
141+
sessionAttributeStore.storeAttribute(webRequest, "sessionAttr", "sessionAttrValue");
142+
modelFactory.initModel(webRequest, mavContainer, handleSessionAttrMethod);
143+
144+
assertEquals("sessionAttrValue", mavContainer.getAttribute("sessionAttr"));
145+
}
146+
133147
@Test
134148
public void updateBindingResult() throws Exception {
135149
String attrName = "attr1";
@@ -170,35 +184,33 @@ private ModelFactory createModelFactory(String methodName, Class<?>... parameter
170184
return new ModelFactory(Arrays.asList(handlerMethod), null, handlerSessionAttributeStore);
171185
}
172186

173-
@SessionAttributes("sessAttr")
187+
@SessionAttributes("sessionAttr") @SuppressWarnings("unused")
174188
private static class ModelHandler {
175189

176-
@SuppressWarnings("unused")
177190
@ModelAttribute
178-
public void model(Model model) {
179-
model.addAttribute("model", Boolean.TRUE);
191+
public void modelAttr(Model model) {
192+
model.addAttribute("modelAttr", Boolean.TRUE);
180193
}
181194

182-
@SuppressWarnings("unused")
183195
@ModelAttribute("name")
184-
public Boolean modelWithName() {
196+
public Boolean modelAttrWithName() {
185197
return Boolean.TRUE;
186198
}
187199

188-
@SuppressWarnings("unused")
189200
@ModelAttribute
190-
public Boolean modelWithDefaultName() {
201+
public Boolean modelAttrConvention() {
191202
return Boolean.TRUE;
192203
}
193204

194-
@SuppressWarnings("unused")
195205
@ModelAttribute("name")
196-
public Boolean modelWithNullAttribute() {
206+
public Boolean nullModelAttr() {
197207
return null;
198208
}
199209

200-
@SuppressWarnings("unused")
201210
public void handle() {
202211
}
212+
213+
public void handleSessionAttr(@ModelAttribute("sessionAttr") String sessionAttr) {
214+
}
203215
}
204216
}

0 commit comments

Comments
 (0)