Add ModelFactory test for HttpSessionRequiredException

This commit is contained in:
Rossen Stoyanchev 2011-06-03 09:38:16 +00:00
parent c7a350cde7
commit d2a99de9fc
3 changed files with 78 additions and 59 deletions

View File

@ -33,6 +33,7 @@ import org.springframework.web.HttpSessionRequiredException;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus; import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
@ -41,10 +42,11 @@ import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.method.support.ModelAndViewContainer;
/** /**
* Provides methods to create and update a model in the context of a given request. * Contains methods for creating and updating a model. A {@link ModelFactory} is associated with a specific controller
* through knowledge of its @{@link ModelAttribute} methods and @{@link SessionAttributes}.
* *
* <p>{@link #initModel(NativeWebRequest, ModelAndViewContainer, HandlerMethod)} populates the model * <p>{@link #initModel(NativeWebRequest, ModelAndViewContainer, HandlerMethod)} populates the model
* with handler session attributes and attributes from model attribute methods. * with handler session attributes and by invoking model attribute methods.
* *
* <p>{@link #updateModel(NativeWebRequest, ModelAndViewContainer, SessionStatus)} updates * <p>{@link #updateModel(NativeWebRequest, ModelAndViewContainer, SessionStatus)} updates
* the model (usually after the {@link RequestMapping} method has been called) promoting attributes * the model (usually after the {@link RequestMapping} method has been called) promoting attributes
@ -70,7 +72,7 @@ public final class ModelFactory {
public ModelFactory(List<InvocableHandlerMethod> attributeMethods, public ModelFactory(List<InvocableHandlerMethod> attributeMethods,
WebDataBinderFactory binderFactory, WebDataBinderFactory binderFactory,
SessionAttributesHandler sessionHandler) { SessionAttributesHandler sessionHandler) {
this.attributeMethods = attributeMethods; this.attributeMethods = (attributeMethods != null) ? attributeMethods : new ArrayList<InvocableHandlerMethod>();
this.binderFactory = binderFactory; this.binderFactory = binderFactory;
this.sessionHandler = sessionHandler; this.sessionHandler = sessionHandler;
} }
@ -78,11 +80,11 @@ public final class ModelFactory {
/** /**
* Populate the model for a request with attributes obtained in the following order: * Populate the model for a request with attributes obtained in the following order:
* <ol> * <ol>
* <li>Add known (i.e. previously accessed) handler session attributes * <li>Retrieve "known" (i.e. have been in the model in prior requests) handler session attributes from the session
* <li>Invoke model attribute methods * <li>Create attributes by invoking model attribute methods
* <li>Check if any {@link ModelAttribute}-annotated arguments need to be added from the session * <li>Check for not yet known handler session attributes in the session
* </ol> * </ol>
* <p>As a general rule model attributes are added only once. * <p>As a general rule model attributes are added only once following the above order.
* *
* @param request the current request * @param request the current request
* @param mavContainer the {@link ModelAndViewContainer} to add model attributes to * @param mavContainer the {@link ModelAndViewContainer} to add model attributes to
@ -97,7 +99,7 @@ public final class ModelFactory {
invokeAttributeMethods(request, mavContainer); invokeAttributeMethods(request, mavContainer);
addSessionAttributesByName(request, mavContainer, requestMethod); checkMissingSessionAttributes(request, mavContainer, requestMethod);
} }
/** /**
@ -123,21 +125,26 @@ public final class ModelFactory {
} }
/** /**
* Check if {@link ModelAttribute}-annotated arguments are handler session attributes and add them from the session. * Checks if any {@link ModelAttribute}-annotated handler method arguments are eligible as handler session
* attributes, as defined by @{@link SessionAttributes}, and are not yet present in the model.
* If so, attempts to retrieve them from the session and add them to the model.
*
* @throws HttpSessionRequiredException raised if a handler session attribute could is missing
*/ */
private void addSessionAttributesByName(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod requestMethod) { private void checkMissingSessionAttributes(NativeWebRequest request,
ModelAndViewContainer mavContainer,
HandlerMethod requestMethod) throws HttpSessionRequiredException {
for (MethodParameter parameter : requestMethod.getMethodParameters()) { for (MethodParameter parameter : requestMethod.getMethodParameters()) {
if (parameter.hasParameterAnnotation(ModelAttribute.class)) { if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
continue; String name = getNameForParameter(parameter);
} if (!mavContainer.containsAttribute(name)) {
String attrName = getNameForParameter(parameter); if (sessionHandler.isHandlerSessionAttribute(name, parameter.getParameterType())) {
if (!mavContainer.containsAttribute(attrName)) { Object attrValue = sessionHandler.retrieveAttribute(request, name);
if (sessionHandler.isHandlerSessionAttribute(attrName, parameter.getParameterType())) {
Object attrValue = sessionHandler.retrieveAttribute(request, attrName);
if (attrValue == null){ if (attrValue == null){
new HttpSessionRequiredException("Session attribute '" + attrName + "' not found in session"); throw new HttpSessionRequiredException("Session attribute '" + name + "' not found in session");
}
mavContainer.addAttribute(name, attrValue);
} }
mavContainer.addAttribute(attrName, attrValue);
} }
} }
} }

View File

@ -38,9 +38,9 @@ import org.springframework.web.method.support.ModelAndViewContainer;
* Resolves method arguments annotated with @{@link ModelAttribute}. Or if created in default resolution mode, * Resolves method arguments annotated with @{@link ModelAttribute}. Or if created in default resolution mode,
* resolves any non-simple type argument even without an @{@link ModelAttribute}. See the constructor for details. * resolves any non-simple type argument even without an @{@link ModelAttribute}. See the constructor for details.
* *
* <p>A model attribute argument value is obtained from the model or is created using its default constructor. * <p>A model attribute argument is obtained from the model or otherwise is created with a default constructor.
* Data binding and optionally validation is then applied through a {@link WebDataBinder} instance. Validation is * Data binding and validation are applied through a {@link WebDataBinder} instance. Validation is applied
* invoked optionally when the argument is annotated with an {@code @Valid}. * only when the argument is also annotated with {@code @Valid}.
* *
* <p>Also handles return values from methods annotated with an @{@link ModelAttribute}. The return value is * <p>Also handles return values from methods annotated with an @{@link ModelAttribute}. The return value is
* added to the {@link ModelAndViewContainer}. * added to the {@link ModelAndViewContainer}.

View File

@ -24,6 +24,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
@ -34,6 +35,7 @@ import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.web.HttpSessionRequiredException;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.annotation.SessionAttributes;
@ -59,6 +61,8 @@ public class ModelFactoryTests {
private InvocableHandlerMethod handleMethod; private InvocableHandlerMethod handleMethod;
private InvocableHandlerMethod handleSessionAttrMethod;
private SessionAttributesHandler handlerSessionAttributeStore; private SessionAttributesHandler handlerSessionAttributeStore;
private SessionAttributeStore sessionAttributeStore; private SessionAttributeStore sessionAttributeStore;
@ -69,48 +73,43 @@ public class ModelFactoryTests {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
handleMethod = new InvocableHandlerMethod(handler, handler.getClass().getDeclaredMethod("handle")); 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(); sessionAttributeStore = new DefaultSessionAttributeStore();
handlerSessionAttributeStore = new SessionAttributesHandler(handler.getClass(), sessionAttributeStore); handlerSessionAttributeStore = new SessionAttributesHandler(handlerType, sessionAttributeStore);
mavContainer = new ModelAndViewContainer(); mavContainer = new ModelAndViewContainer();
webRequest = new ServletWebRequest(new MockHttpServletRequest()); webRequest = new ServletWebRequest(new MockHttpServletRequest());
} }
@Test @Test
public void createModel() throws Exception { public void addAttributeToModel() throws Exception {
ModelFactory modelFactory = createModelFactory("model", Model.class); ModelFactory modelFactory = createModelFactory("modelAttr", Model.class);
modelFactory.initModel(webRequest, mavContainer, handleMethod); modelFactory.initModel(webRequest, mavContainer, handleMethod);
assertEquals(Boolean.TRUE, mavContainer.getAttribute("model")); assertEquals(Boolean.TRUE, mavContainer.getAttribute("modelAttr"));
} }
@Test @Test
public void createModelWithName() throws Exception { public void returnAttributeWithName() throws Exception {
ModelFactory modelFactory = createModelFactory("modelWithName"); ModelFactory modelFactory = createModelFactory("modelAttrWithName");
modelFactory.initModel(webRequest, mavContainer, handleMethod); modelFactory.initModel(webRequest, mavContainer, handleMethod);
assertEquals(Boolean.TRUE, mavContainer.getAttribute("name")); assertEquals(Boolean.TRUE, mavContainer.getAttribute("name"));
} }
@Test @Test
public void createModelWithDefaultName() throws Exception { public void returnAttributeWithNameByConvention() throws Exception {
ModelFactory modelFactory = createModelFactory("modelWithDefaultName"); ModelFactory modelFactory = createModelFactory("modelAttrConvention");
modelFactory.initModel(webRequest, mavContainer, handleMethod); modelFactory.initModel(webRequest, mavContainer, handleMethod);
assertEquals(Boolean.TRUE, mavContainer.getAttribute("boolean")); assertEquals(Boolean.TRUE, mavContainer.getAttribute("boolean"));
} }
@Test @Test
public void createModelWithExistingName() throws Exception { public void returnNullAttributeValue() throws Exception {
ModelFactory modelFactory = createModelFactory("modelWithName"); ModelFactory modelFactory = createModelFactory("nullModelAttr");
modelFactory.initModel(webRequest, mavContainer, handleMethod);
assertEquals(Boolean.TRUE, mavContainer.getAttribute("name"));
}
@Test
public void createModelWithNullAttribute() throws Exception {
ModelFactory modelFactory = createModelFactory("modelWithNullAttribute");
modelFactory.initModel(webRequest, mavContainer, handleMethod); modelFactory.initModel(webRequest, mavContainer, handleMethod);
assertTrue(mavContainer.containsAttribute("name")); assertTrue(mavContainer.containsAttribute("name"));
@ -118,16 +117,31 @@ public class ModelFactoryTests {
} }
@Test @Test
public void createModelExistingSessionAttributes() throws Exception { public void retrieveAttributeFromSession() throws Exception {
sessionAttributeStore.storeAttribute(webRequest, "sessAttr", "sessAttrValue"); sessionAttributeStore.storeAttribute(webRequest, "sessionAttr", "sessionAttrValue");
// Resolve successfully handler session attribute once // Resolve successfully handler session attribute once
assertTrue(handlerSessionAttributeStore.isHandlerSessionAttribute("sessAttr", null)); assertTrue(handlerSessionAttributeStore.isHandlerSessionAttribute("sessionAttr", null));
ModelFactory modelFactory = createModelFactory("model", Model.class); ModelFactory modelFactory = createModelFactory("modelAttr", Model.class);
modelFactory.initModel(webRequest, mavContainer, handleMethod); modelFactory.initModel(webRequest, mavContainer, handleMethod);
assertEquals("sessAttrValue", mavContainer.getAttribute("sessAttr")); assertEquals("sessionAttrValue", mavContainer.getAttribute("sessionAttr"));
}
@Test
public void requiredSessionAttribute() throws Exception {
ModelFactory modelFactory = new ModelFactory(null, null, handlerSessionAttributeStore);
try {
modelFactory.initModel(webRequest, mavContainer, handleSessionAttrMethod);
fail("Expected HttpSessionRequiredException");
} catch (HttpSessionRequiredException e) { }
sessionAttributeStore.storeAttribute(webRequest, "sessionAttr", "sessionAttrValue");
modelFactory.initModel(webRequest, mavContainer, handleSessionAttrMethod);
assertEquals("sessionAttrValue", mavContainer.getAttribute("sessionAttr"));
} }
@Test @Test
@ -170,35 +184,33 @@ public class ModelFactoryTests {
return new ModelFactory(Arrays.asList(handlerMethod), null, handlerSessionAttributeStore); return new ModelFactory(Arrays.asList(handlerMethod), null, handlerSessionAttributeStore);
} }
@SessionAttributes("sessAttr") @SessionAttributes("sessionAttr") @SuppressWarnings("unused")
private static class ModelHandler { private static class ModelHandler {
@SuppressWarnings("unused")
@ModelAttribute @ModelAttribute
public void model(Model model) { public void modelAttr(Model model) {
model.addAttribute("model", Boolean.TRUE); model.addAttribute("modelAttr", Boolean.TRUE);
} }
@SuppressWarnings("unused")
@ModelAttribute("name") @ModelAttribute("name")
public Boolean modelWithName() { public Boolean modelAttrWithName() {
return Boolean.TRUE; return Boolean.TRUE;
} }
@SuppressWarnings("unused")
@ModelAttribute @ModelAttribute
public Boolean modelWithDefaultName() { public Boolean modelAttrConvention() {
return Boolean.TRUE; return Boolean.TRUE;
} }
@SuppressWarnings("unused")
@ModelAttribute("name") @ModelAttribute("name")
public Boolean modelWithNullAttribute() { public Boolean nullModelAttr() {
return null; return null;
} }
@SuppressWarnings("unused")
public void handle() { public void handle() {
} }
public void handleSessionAttr(@ModelAttribute("sessionAttr") String sessionAttr) {
}
} }
} }