From 9cb1338b94583e21d7738208a505d15fae201ece Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 25 Sep 2009 10:42:49 +0000 Subject: [PATCH] all @SessionAttributes get exposed to the model before handler method execution; MultipartRequest is available as a mixin interface on (Native)WebRequest as well --- .../ServletAnnotationControllerTests.java | 89 ++++++++++++++----- .../support/HandlerMethodInvoker.java | 26 ++++-- .../bind/support/WebRequestDataBinder.java | 11 +-- .../web/context/request/FacesWebRequest.java | 54 ++++++++++- .../web/context/request/NativeWebRequest.java | 6 +- .../context/request/ServletWebRequest.java | 55 +++++++++++- 6 files changed, 194 insertions(+), 47 deletions(-) diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java index 6a5d4d0d46..014ce2d600 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java @@ -102,6 +102,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.bind.support.WebArgumentResolver; import org.springframework.web.bind.support.WebBindingInitializer; @@ -117,7 +118,6 @@ import org.springframework.web.servlet.mvc.AbstractController; import org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver; import org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping; import org.springframework.web.servlet.view.InternalResourceViewResolver; -import org.springframework.web.servlet.view.RedirectView; import org.springframework.web.util.NestedServletException; /** @@ -293,6 +293,39 @@ public class ServletAnnotationControllerTests { assertEquals("", response.getContentAsString()); } + @Test + public void sessionAttributeExposure() throws Exception { + @SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() { + @Override + protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) { + GenericWebApplicationContext wac = new GenericWebApplicationContext(); + wac.registerBeanDefinition("controller", new RootBeanDefinition(MySessionAttributesController.class)); + wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(ModelExposingViewResolver.class)); + wac.refresh(); + return wac; + } + }; + servlet.init(new MockServletConfig()); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPage"); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.service(request, response); + HttpSession session = request.getSession(); + assertTrue(session.getAttribute("object1") != null); + assertTrue(session.getAttribute("object2") != null); + assertTrue(((Map) session.getAttribute("model")).containsKey("object1")); + assertTrue(((Map) session.getAttribute("model")).containsKey("object2")); + + request = new MockHttpServletRequest("POST", "/myPage"); + request.setSession(session); + response = new MockHttpServletResponse(); + servlet.service(request, response); + assertTrue(session.getAttribute("object1") != null); + assertTrue(session.getAttribute("object2") != null); + assertTrue(((Map) session.getAttribute("model")).containsKey("object1")); + assertTrue(((Map) session.getAttribute("model")).containsKey("object2")); + } + @Test public void adaptedHandleMethods() throws Exception { doTestAdaptedHandleMethods(MyAdaptedController.class); @@ -1029,16 +1062,6 @@ public class ServletAnnotationControllerTests { assertEquals(201, response.getStatus()); } - @Test - public void responseStatusRedirect() throws ServletException, IOException { - initServlet(ResponseStatusRedirectController.class); - - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/something"); - MockHttpServletResponse response = new MockHttpServletResponse(); - servlet.service(request, response); - assertEquals(201, response.getStatus()); - } - @Test public void mavResolver() throws ServletException, IOException { @SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() { @@ -1248,6 +1271,25 @@ public class ServletAnnotationControllerTests { } } + @Controller + @RequestMapping("/myPage") + @SessionAttributes({"object1", "object2"}) + public static class MySessionAttributesController { + + @RequestMapping(method = RequestMethod.GET) + public String get(Model model) { + model.addAttribute("object1", new Object()); + model.addAttribute("object2", new Object()); + return "myPage"; + } + + @RequestMapping(method = RequestMethod.POST) + public String post(@ModelAttribute("object1") Object object1) { + //do something with object1 + return "myPage"; + } + } + @Controller public static class MyFormController { @@ -1592,6 +1634,20 @@ public class ServletAnnotationControllerTests { } } + private static class ModelExposingViewResolver implements ViewResolver { + + public View resolveViewName(String viewName, Locale locale) throws Exception { + return new View() { + public String getContentType() { + return null; + } + public void render(Map model, HttpServletRequest request, HttpServletResponse response) { + request.getSession().setAttribute("model", model); + } + }; + } + } + public static class ParentController { @RequestMapping(method = RequestMethod.GET) @@ -1769,17 +1825,6 @@ public class ServletAnnotationControllerTests { } } - @Controller - public static class ResponseStatusRedirectController { - - @RequestMapping("/something") - @ResponseStatus(HttpStatus.CREATED) - public RedirectView handle(Writer writer) throws IOException { - return new RedirectView("somelocation.html", false, false); - } - } - - @Controller public static class ModelAndViewResolverController { diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java index e28a69ae9f..5723ddf4f5 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java +++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java @@ -66,7 +66,6 @@ import org.springframework.web.bind.support.WebBindingInitializer; import org.springframework.web.bind.support.WebRequestDataBinder; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.WebRequest; -import org.springframework.web.multipart.MultipartRequest; /** * Support class for invoking an annotated handler method. Operates on the introspection results of a @@ -127,21 +126,32 @@ public class HandlerMethodInvoker { Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod); try { boolean debug = logger.isDebugEnabled(); + for (String attrName : this.methodResolver.getActualSessionAttributeNames()) { + Object attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName); + if (attrValue != null) { + implicitModel.addAttribute(attrName, attrValue); + } + } for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) { Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod); Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel); if (debug) { logger.debug("Invoking model attribute method: " + attributeMethodToInvoke); } - Object attrValue = doInvokeMethod(attributeMethodToInvoke, handler, args); String attrName = AnnotationUtils.findAnnotation(attributeMethodToInvoke, ModelAttribute.class).value(); + if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) { + continue; + } + Object attrValue = doInvokeMethod(attributeMethodToInvoke, handler, args); if ("".equals(attrName)) { Class resolvedType = GenericTypeResolver.resolveReturnType( attributeMethodToInvoke, handler.getClass()); attrName = Conventions.getVariableNameForReturnType( attributeMethodToInvoke, resolvedType, attrValue); } - implicitModel.addAttribute(attrName, attrValue); + if (!implicitModel.containsAttribute(attrName)) { + implicitModel.addAttribute(attrName, attrValue); + } } Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel); if (debug) { @@ -394,15 +404,15 @@ public class HandlerMethodInvoker { if (paramName.length() == 0) { paramName = getRequiredParameterName(methodParam); } - Object paramValue = null; - if (webRequest.getNativeRequest() instanceof MultipartRequest) { - paramValue = ((MultipartRequest) webRequest.getNativeRequest()).getFile(paramName); - } + Object paramValue = webRequest.getFile(paramName); if (paramValue == null) { String[] paramValues = webRequest.getParameterValues(paramName); - if (paramValues != null) { + if (paramValues != null && !paramType.isArray()) { paramValue = (paramValues.length == 1 ? paramValues[0] : paramValues); } + else { + paramValue = paramValues; + } } if (paramValue == null) { if (StringUtils.hasText(defaultValue)) { diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java b/org.springframework.web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java index 5f13693817..4c3c025886 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java +++ b/org.springframework.web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -19,7 +19,6 @@ package org.springframework.web.bind.support; import org.springframework.beans.MutablePropertyValues; import org.springframework.validation.BindException; import org.springframework.web.bind.WebDataBinder; -import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.WebRequest; import org.springframework.web.multipart.MultipartRequest; @@ -100,12 +99,8 @@ public class WebRequestDataBinder extends WebDataBinder { */ public void bind(WebRequest request) { MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap()); - if (request instanceof NativeWebRequest) { - Object nativeRequest = ((NativeWebRequest) request).getNativeRequest(); - if (nativeRequest instanceof MultipartRequest) { - MultipartRequest multipartRequest = (MultipartRequest) request; - bindMultipartFiles(multipartRequest.getFileMap(), mpvs); - } + if (request instanceof MultipartRequest) { + bindMultipartFiles(((MultipartRequest) request).getFileMap(), mpvs); } doBind(mpvs); } diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java index 5734b03d6b..3a387fccb3 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -17,14 +17,19 @@ package org.springframework.web.context.request; import java.security.Principal; +import java.util.Collections; import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.Map; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; -import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartRequest; /** * {@link WebRequest} adapter for a JSF {@link javax.faces.context.FacesContext}. @@ -34,6 +39,9 @@ import org.springframework.util.StringUtils; */ public class FacesWebRequest extends FacesRequestAttributes implements NativeWebRequest { + private MultipartRequest multipartRequest; + + /** * Create a new FacesWebRequest adapter for the given FacesContext. * @param facesContext the current FacesContext @@ -41,6 +49,9 @@ public class FacesWebRequest extends FacesRequestAttributes implements NativeWeb */ public FacesWebRequest(FacesContext facesContext) { super(facesContext); + if (facesContext.getExternalContext().getRequest() instanceof MultipartRequest) { + this.multipartRequest = (MultipartRequest) facesContext.getExternalContext().getRequest(); + } } @@ -105,7 +116,6 @@ public class FacesWebRequest extends FacesRequestAttributes implements NativeWeb return false; } - public String getDescription(boolean includeClientInfo) { ExternalContext externalContext = getExternalContext(); StringBuilder sb = new StringBuilder(); @@ -123,6 +133,44 @@ public class FacesWebRequest extends FacesRequestAttributes implements NativeWeb return sb.toString(); } + + @SuppressWarnings("unchecked") + public Iterator getFileNames() { + if (this.multipartRequest == null) { + return (Iterator) Collections.EMPTY_SET.iterator(); + } + return this.multipartRequest.getFileNames(); + } + + public MultipartFile getFile(String name) { + if (this.multipartRequest == null) { + return null; + } + return this.multipartRequest.getFile(name); + } + + public List getFiles(String name) { + if (this.multipartRequest == null) { + return null; + } + return this.multipartRequest.getFiles(name); + } + + public Map getFileMap() { + if (this.multipartRequest == null) { + return Collections.emptyMap(); + } + return this.multipartRequest.getFileMap(); + } + + public MultiValueMap getMultiFileMap() { + if (this.multipartRequest == null) { + return new LinkedMultiValueMap(); + } + return this.multipartRequest.getMultiFileMap(); + } + + @Override public String toString() { return "FacesWebRequest: " + getDescription(true); diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/NativeWebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/NativeWebRequest.java index f3b0855e21..c66a54f27a 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/request/NativeWebRequest.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/NativeWebRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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,6 +16,8 @@ package org.springframework.web.context.request; +import org.springframework.web.multipart.MultipartRequest; + /** * Extension of the {@link WebRequest} interface, exposing the * native request and response objects in a generic fashion. @@ -26,7 +28,7 @@ package org.springframework.web.context.request; * @author Juergen Hoeller * @since 2.5.2 */ -public interface NativeWebRequest extends WebRequest { +public interface NativeWebRequest extends WebRequest, MultipartRequest { /** * Return the underlying native request object, if available. diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java index 4cf208481b..c3745eaf85 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -17,7 +17,9 @@ package org.springframework.web.context.request; import java.security.Principal; +import java.util.Collections; import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.Map; import javax.servlet.http.HttpServletRequest; @@ -25,8 +27,12 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartRequest; /** * {@link WebRequest} adapter for an {@link javax.servlet.http.HttpServletRequest}. @@ -41,6 +47,8 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ private static final String HEADER_LAST_MODIFIED = "Last-Modified"; + private MultipartRequest multipartRequest; + private HttpServletResponse response; private boolean notModified = false; @@ -52,6 +60,9 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ */ public ServletWebRequest(HttpServletRequest request) { super(request); + if (request instanceof MultipartRequest) { + this.multipartRequest = (MultipartRequest) request; + } } /** @@ -60,7 +71,7 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ * @param response current HTTP response (for automatic last-modified handling) */ public ServletWebRequest(HttpServletRequest request, HttpServletResponse response) { - super(request); + this(request); this.response = response; } @@ -133,7 +144,6 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ return getRequest().isSecure(); } - public boolean checkNotModified(long lastModifiedTimestamp) { if (lastModifiedTimestamp >= 0 && !this.notModified && (this.response == null || !this.response.containsHeader(HEADER_LAST_MODIFIED))) { @@ -155,7 +165,6 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ return this.notModified; } - public String getDescription(boolean includeClientInfo) { HttpServletRequest request = getRequest(); StringBuilder sb = new StringBuilder(); @@ -177,6 +186,44 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ return sb.toString(); } + + @SuppressWarnings("unchecked") + public Iterator getFileNames() { + if (this.multipartRequest == null) { + return (Iterator) Collections.EMPTY_SET.iterator(); + } + return this.multipartRequest.getFileNames(); + } + + public MultipartFile getFile(String name) { + if (this.multipartRequest == null) { + return null; + } + return this.multipartRequest.getFile(name); + } + + public List getFiles(String name) { + if (this.multipartRequest == null) { + return null; + } + return this.multipartRequest.getFiles(name); + } + + public Map getFileMap() { + if (this.multipartRequest == null) { + return Collections.emptyMap(); + } + return this.multipartRequest.getFileMap(); + } + + public MultiValueMap getMultiFileMap() { + if (this.multipartRequest == null) { + return new LinkedMultiValueMap(); + } + return this.multipartRequest.getMultiFileMap(); + } + + @Override public String toString() { return "ServletWebRequest: " + getDescription(true);