diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java index 665b9325df..cc86beb8b7 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java @@ -23,22 +23,20 @@ import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.validation.Validator; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; -import com.sun.corba.se.impl.presentation.rmi.ExceptionHandler; - /** - * Defines configuration callback methods for customizing the default Spring MVC code-based configuration enabled - * through @{@link EnableWebMvc}. + * Defines callback methods to customize the Java-based configuration for + * Spring MVC enabled via {@code @EnableWebMvc}. * - *

Classes annotated with @{@link EnableWebMvc} can implement this interface in order to be called back and - * given a chance to customize the default configuration. The most convenient way to implement this interface - * is to extend {@link WebMvcConfigurerAdapter}, which provides empty method implementations. + *

{@code @EnableWebMvc}-annotated configuration classes may implement + * this interface to be called back and given a chance to customize the + * default configuration. Consider extending {@link WebMvcConfigurerAdapter}, + * which provides a stub implementation of all interface methods. * * @author Rossen Stoyanchev * @author Keith Donald @@ -48,78 +46,79 @@ import com.sun.corba.se.impl.presentation.rmi.ExceptionHandler; public interface WebMvcConfigurer { /** - * Add {@link Converter}s and {@link Formatter}s in addition to the ones registered by default. + * Add {@link Converter}s and {@link Formatter}s in addition to the ones + * registered by default. */ void addFormatters(FormatterRegistry registry); /** - * Configure the list of {@link HttpMessageConverter}s to use when resolving method arguments or handling - * return values in @{@link RequestMapping} and @{@link ExceptionHandler} methods. - * Adding converters to the list turns off the default converters that would otherwise be registered by default. - * @param converters a list to add message converters to; initially an empty list. + * Configure the {@link HttpMessageConverter}s to use in argument resolvers + * and return value handlers that support reading and/or writing to the + * body of the request and response. If no message converters are added to + * the list, default converters are added instead. + * @param converters initially an empty list of converters */ void configureMessageConverters(List> converters); /** - * Provide a custom {@link Validator} type replacing the one that would be created by default otherwise. If this - * method returns {@code null}, and assuming a JSR-303 implementation is available on the classpath, a validator - * of type {@link org.springframework.validation.beanvalidation.LocalValidatorFactoryBean} is created by default. + * Provide a custom {@link Validator} instead of the one created by default. + * The default implementation, assuming JSR-303 is on the classpath, is: + * {@link org.springframework.validation.beanvalidation.LocalValidatorFactoryBean}. + * Leave the return value as {@code null} to keep the default. */ Validator getValidator(); /** - * Add custom {@link HandlerMethodArgumentResolver}s to use in addition to the ones registered by default. - *

Custom argument resolvers are invoked before built-in resolvers except for those that rely on the presence - * of annotations (e.g. {@code @RequestParameter}, {@code @PathVariable}, etc.). The latter can be customized - * by configuring the {@link RequestMappingHandlerAdapter} directly. - * @param argumentResolvers the list of custom converters; initially an empty list. + * Add resolvers to support custom controller method argument types. + *

This does not override the built-in support for resolving handler + * method arguments. To customize the built-in support for argument + * resolution, configure {@link RequestMappingHandlerAdapter} directly. + * @param argumentResolvers initially an empty list */ void addArgumentResolvers(List argumentResolvers); /** - * Add custom {@link HandlerMethodReturnValueHandler}s in addition to the ones registered by default. - *

Custom return value handlers are invoked before built-in ones except for those that rely on the presence - * of annotations (e.g. {@code @ResponseBody}, {@code @ModelAttribute}, etc.). The latter can be customized - * by configuring the {@link RequestMappingHandlerAdapter} directly. - * @param returnValueHandlers the list of custom handlers; initially an empty list. + * Add handlers to support custom controller method return value types. + *

Using this option does not override the built-in support for handling + * return values. To customize the built-in support for handling return + * values, configure RequestMappingHandlerAdapter directly. + * @param returnValueHandlers initially an empty list */ void addReturnValueHandlers(List returnValueHandlers); /** - * Configure the list of {@link HandlerExceptionResolver}s to use for handling unresolved controller exceptions. - * Adding resolvers to the list turns off the default resolvers that would otherwise be registered by default. - * @param exceptionResolvers a list to add exception resolvers to; initially an empty list. + * Configure the {@link HandlerExceptionResolver}s to handle unresolved + * controller exceptions. If no resolvers are added to the list, default + * exception resolvers are added instead. + * @param exceptionResolvers initially an empty list */ void configureHandlerExceptionResolvers(List exceptionResolvers); /** - * Add Spring MVC lifecycle interceptors for pre- and post-processing of controller method invocations. - * Interceptors can be registered to apply to all requests or to a set of URL path patterns. - * @see InterceptorRegistry + * Add Spring MVC lifecycle interceptors for pre- and post-processing of + * controller method invocations. Interceptors can be registered to apply + * to all requests or be limited to a subset of URL patterns. */ void addInterceptors(InterceptorRegistry registry); /** - * Add view controllers to create a direct mapping between a URL path and view name. This is useful when - * you just want to forward the request to a view such as a JSP without the need for controller logic. - * @see ViewControllerRegistry + * Add view controllers to create a direct mapping between a URL path and + * view name without the need for a controller in between. */ void addViewControllers(ViewControllerRegistry registry); /** - * Add resource handlers to use to serve static resources such as images, js, and, css files through - * the Spring MVC {@link DispatcherServlet} including the setting of cache headers optimized for efficient - * loading in a web browser. Resources can be served out of locations under web application root, - * from the classpath, and others. - * @see ResourceHandlerRegistry + * Add handlers to serve static resources such as images, js, and, css + * files from specific locations under web application root, the classpath, + * and others. */ void addResourceHandlers(ResourceHandlerRegistry registry); /** - * Configure a handler for delegating unhandled requests by forwarding to the Servlet container's "default" - * servlet. The use case for this is when the {@link DispatcherServlet} is mapped to "/" thus overriding - * the Servlet container's default handling of static resources. - * @see DefaultServletHandlerConfigurer + * Configure a handler to delegate unhandled requests by forwarding to the + * Servlet container's "default" servlet. A common use case for this is when + * the {@link DispatcherServlet} is mapped to "/" thus overriding the + * Servlet container's default handling of static resources. */ void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer); diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/AbstractHandlerMethodAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/AbstractHandlerMethodAdapter.java index 3246388be4..653628ceaa 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/AbstractHandlerMethodAdapter.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/AbstractHandlerMethodAdapter.java @@ -26,8 +26,8 @@ import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.support.WebContentGenerator; /** - * Abstract base class for {@link HandlerAdapter} implementations that support the handling of requests through - * the execution of {@link HandlerMethod}s rather than handlers. + * Abstract base class for {@link HandlerAdapter} implementations that support + * handlers of type {@link HandlerMethod}. * * @author Arjen Poutsma * @since 3.1 diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java index 4ce707ea9e..f7b7ae2cad 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java @@ -43,16 +43,12 @@ import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter; import org.springframework.ui.Model; import org.springframework.ui.ModelMap; import org.springframework.util.ReflectionUtils.MethodFilter; -import org.springframework.validation.DataBinder; -import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.support.DefaultDataBinderFactory; import org.springframework.web.bind.support.DefaultSessionAttributeStore; import org.springframework.web.bind.support.SessionAttributeStore; -import org.springframework.web.bind.support.SessionStatus; -import org.springframework.web.bind.support.SimpleSessionStatus; import org.springframework.web.bind.support.WebBindingInitializer; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.ServletWebRequest; @@ -63,12 +59,14 @@ import org.springframework.web.method.annotation.ModelFactory; import org.springframework.web.method.annotation.SessionAttributesHandler; import org.springframework.web.method.annotation.support.ErrorsMethodArgumentResolver; import org.springframework.web.method.annotation.support.ExpressionValueMethodArgumentResolver; +import org.springframework.web.method.annotation.support.MapMethodProcessor; import org.springframework.web.method.annotation.support.ModelAttributeMethodProcessor; import org.springframework.web.method.annotation.support.ModelMethodProcessor; import org.springframework.web.method.annotation.support.RequestHeaderMapMethodArgumentResolver; import org.springframework.web.method.annotation.support.RequestHeaderMethodArgumentResolver; import org.springframework.web.method.annotation.support.RequestParamMapMethodArgumentResolver; import org.springframework.web.method.annotation.support.RequestParamMethodArgumentResolver; +import org.springframework.web.method.annotation.support.SessionStatusMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; @@ -77,7 +75,6 @@ import org.springframework.web.method.support.InvocableHandlerMethod; import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.View; -import org.springframework.web.servlet.mvc.LastModified; import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver; import org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter; import org.springframework.web.servlet.mvc.method.annotation.support.DefaultMethodReturnValueHandler; @@ -92,36 +89,25 @@ import org.springframework.web.servlet.mvc.method.annotation.support.ServletMode import org.springframework.web.servlet.mvc.method.annotation.support.ServletRequestMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.support.ServletResponseMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler; +import org.springframework.web.servlet.mvc.method.annotation.support.ViewNameMethodReturnValueHandler; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.support.RequestContextUtils; import org.springframework.web.util.WebUtils; /** - * An {@link AbstractHandlerMethodAdapter} variant with support for {@link RequestMapping} handler methods. - * - *

Processing a {@link RequestMapping} method typically involves the invocation of {@link ModelAttribute} - * methods for contributing attributes to the model and {@link InitBinder} methods for initializing - * {@link WebDataBinder} instances for data binding and type conversion purposes. - * - *

{@link InvocableHandlerMethod} is the key contributor that helps with the invocation of handler - * methods of all types resolving their arguments through registered {@link HandlerMethodArgumentResolver}s. - * {@link ServletInvocableHandlerMethod} on the other hand adds handling of the return value for - * {@link RequestMapping} methods through registered {@link HandlerMethodReturnValueHandler}s - * resulting in a {@link ModelAndView}. - * - *

{@link ModelFactory} is another contributor that assists with the invocation of all {@link ModelAttribute} - * methods to populate a model while {@link ServletRequestDataBinderFactory} assists with the invocation of - * {@link InitBinder} methods for initializing data binder instances when needed. - * - *

This class is the central point that assembles all mentioned contributors and invokes the actual - * {@link RequestMapping} handler method through a {@link ServletInvocableHandlerMethod}. + * An {@link AbstractHandlerMethodAdapter} that supports {@link HandlerMethod}s + * with the signature -- method argument and return types, defined in + * {@code @RequestMapping}. + * + *

Support for custom argument and return value types can be added via + * {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers}. + * Or alternatively to re-configure all argument and return value types use + * {@link #setArgumentResolvers} and {@link #setReturnValueHandlers(List)}. * * @author Rossen Stoyanchev * @since 3.1 * @see HandlerMethodArgumentResolver * @see HandlerMethodReturnValueHandler - * @see #setCustomArgumentResolvers(List) - * @see #setCustomReturnValueHandlers(List) */ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean { @@ -163,126 +149,169 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i private final Map, ModelFactory> modelFactoryCache = new ConcurrentHashMap, ModelFactory>(); /** - * Create a {@link RequestMappingHandlerAdapter} instance. + * Default constructor. */ public RequestMappingHandlerAdapter() { StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); stringHttpMessageConverter.setWriteAcceptCharset(false); // See SPR-7316 - - messageConverters = new ArrayList>(); - messageConverters.add(new ByteArrayHttpMessageConverter()); - messageConverters.add(stringHttpMessageConverter); - messageConverters.add(new SourceHttpMessageConverter()); - messageConverters.add(new XmlAwareFormHttpMessageConverter()); + + this.messageConverters = new ArrayList>(); + this.messageConverters.add(new ByteArrayHttpMessageConverter()); + this.messageConverters.add(stringHttpMessageConverter); + this.messageConverters.add(new SourceHttpMessageConverter()); + this.messageConverters.add(new XmlAwareFormHttpMessageConverter()); } /** - * Set one or more custom argument resolvers to use with {@link RequestMapping}, {@link ModelAttribute}, and - * {@link InitBinder} methods. - *

Generally custom argument resolvers are invoked first. However this excludes - * default argument resolvers that rely on the presence of annotations (e.g. {@code @RequestParameter}, - * {@code @PathVariable}, etc.) Those resolvers can only be customized via {@link #setArgumentResolvers(List)} + * Provide resolvers for custom argument types. Custom resolvers are ordered + * after built-in ones. To override the built-in support for argument + * resolution use {@link #setArgumentResolvers} instead. */ public void setCustomArgumentResolvers(List argumentResolvers) { this.customArgumentResolvers = argumentResolvers; } - + /** - * Set the argument resolvers to use with {@link RequestMapping} and {@link ModelAttribute} methods. - * This is an optional property providing full control over all argument resolvers in contrast to - * {@link #setCustomArgumentResolvers(List)}, which does not override default registrations. - * @param argumentResolvers argument resolvers for {@link RequestMapping} and {@link ModelAttribute} methods + * Return the custom argument resolvers, or {@code null}. + */ + public List getCustomArgumentResolvers() { + return this.customArgumentResolvers; + } + + /** + * Configure the complete list of supported argument types thus overriding + * the resolvers that would otherwise be configured by default. */ public void setArgumentResolvers(List argumentResolvers) { - if (argumentResolvers != null) { + if (argumentResolvers == null) { + this.argumentResolvers = null; + } + else { this.argumentResolvers = new HandlerMethodArgumentResolverComposite(); this.argumentResolvers.addResolvers(argumentResolvers); } } /** - * Set the argument resolvers to use with {@link InitBinder} methods. This is an optional property - * providing full control over all argument resolvers for {@link InitBinder} methods in contrast to - * {@link #setCustomArgumentResolvers(List)}, which does not override default registrations. - * @param argumentResolvers argument resolvers for {@link InitBinder} methods + * Return the configured argument resolvers, or possibly {@code null} if + * not initialized yet via {@link #afterPropertiesSet()}. + */ + public HandlerMethodArgumentResolverComposite getArgumentResolvers() { + return this.argumentResolvers; + } + + /** + * Configure the supported argument types in {@code @InitBinder} methods. */ public void setInitBinderArgumentResolvers(List argumentResolvers) { - if (argumentResolvers != null) { + if (argumentResolvers == null) { + this.initBinderArgumentResolvers = null; + } + else { this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite(); this.initBinderArgumentResolvers.addResolvers(argumentResolvers); } } /** - * Set custom return value handlers to use to handle the return values of {@link RequestMapping} methods. - *

Generally custom return value handlers are invoked first. However this excludes default return value - * handlers that rely on the presence of annotations like {@code @ResponseBody}, {@code @ModelAttribute}, - * and others. Those handlers can only be customized via {@link #setReturnValueHandlers(List)}. - * @param returnValueHandlers custom return value handlers for {@link RequestMapping} methods + * Return the argument resolvers for {@code @InitBinder} methods, or possibly + * {@code null} if not initialized yet via {@link #afterPropertiesSet()}. + */ + public HandlerMethodArgumentResolverComposite getInitBinderArgumentResolvers() { + return this.initBinderArgumentResolvers; + } + + /** + * Provide handlers for custom return value types. Custom handlers are + * ordered after built-in ones. To override the built-in support for + * return value handling use {@link #setReturnValueHandlers}. */ public void setCustomReturnValueHandlers(List returnValueHandlers) { this.customReturnValueHandlers = returnValueHandlers; } /** - * Set the {@link HandlerMethodReturnValueHandler}s to use to use with {@link RequestMapping} methods. - * This is an optional property providing full control over all return value handlers in contrast to - * {@link #setCustomReturnValueHandlers(List)}, which does not override default registrations. - * @param returnValueHandlers the return value handlers for {@link RequestMapping} methods + * Return the custom return value handlers, or {@code null}. + */ + public List getCustomReturnValueHandlers() { + return this.customReturnValueHandlers; + } + + /** + * Configure the complete list of supported return value types thus + * overriding handlers that would otherwise be configured by default. */ public void setReturnValueHandlers(List returnValueHandlers) { - if (returnValueHandlers != null) { + if (returnValueHandlers == null) { + this.returnValueHandlers = null; + } + else { this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite(); this.returnValueHandlers.addHandlers(returnValueHandlers); } } /** - * Set custom {@link ModelAndViewResolver}s to use to handle the return values of {@link RequestMapping} methods. - *

Custom {@link ModelAndViewResolver}s are provided for backward compatibility and are invoked at the end, - * in {@link DefaultMethodReturnValueHandler}, after all standard {@link HandlerMethodReturnValueHandler}s. - * This is because {@link ModelAndViewResolver}s do not have a method to indicate if they support a given - * return type or not. For this reason it is recommended to use - * {@link HandlerMethodReturnValueHandler} and {@link #setCustomReturnValueHandlers(List)} instead. + * Return the configured handlers, or possibly {@code null} if not + * initialized yet via {@link #afterPropertiesSet()}. + */ + public HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() { + return this.returnValueHandlers; + } + + /** + * Provide custom {@link ModelAndViewResolver}s. This is available for + * backwards compatibility. However it is recommended to use + * {@link HandlerMethodReturnValueHandler}s instead. */ public void setModelAndViewResolvers(List modelAndViewResolvers) { this.modelAndViewResolvers = modelAndViewResolvers; } /** - * Set the message body converters to use. - *

These converters are used to convert from and to HTTP requests and responses. + * Return the configured {@link ModelAndViewResolver}s, or {@code null}. + */ + public List getModelAndViewResolvers() { + return modelAndViewResolvers; + } + + /** + * Provide the converters to use in argument resolvers and return value + * handlers that support reading and/or writing to the body of the + * request and response. */ public void setMessageConverters(List> messageConverters) { this.messageConverters = messageConverters; } /** - * Return the message body converters that this adapter has been configured with. + * Return the configured message body converters. */ public List> getMessageConverters() { return messageConverters; } /** - * Set a WebBindingInitializer to apply configure every DataBinder instance this controller uses. + * Provide a WebBindingInitializer with "global" initialization to apply + * to every DataBinder instance. */ public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) { this.webBindingInitializer = webBindingInitializer; } /** - * Return the WebBindingInitializer which applies pre-configured configuration to {@link DataBinder} instances. + * Return the configured WebBindingInitializer, or {@code null}. */ public WebBindingInitializer getWebBindingInitializer() { return webBindingInitializer; } /** - * Specify the strategy to store session attributes with. - *

Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore}, - * storing session attributes in the HttpSession, using the same attribute name as in the model. + * Specify the strategy to store session attributes with. The default is + * {@link org.springframework.web.bind.support.DefaultSessionAttributeStore}, + * storing session attributes in the HttpSession with the same attribute + * name as in the model. */ public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) { this.sessionAttributeStore = sessionAttributeStore; @@ -291,9 +320,9 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i /** * Cache content produced by @SessionAttributes annotated handlers * for the given number of seconds. Default is 0, preventing caching completely. - *

In contrast to the "cacheSeconds" property which will apply to all general handlers - * (but not to @SessionAttributes annotated handlers), this setting will - * apply to @SessionAttributes annotated handlers only. + *

In contrast to the "cacheSeconds" property which will apply to all general + * handlers (but not to @SessionAttributes annotated handlers), + * this setting will apply to @SessionAttributes handlers only. * @see #setCacheSeconds * @see org.springframework.web.bind.annotation.SessionAttributes */ @@ -324,9 +353,9 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i } /** - * Set the ParameterNameDiscoverer to use for resolving method parameter names if needed - * (e.g. for default attribute names). - *

Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}. + * Set the ParameterNameDiscoverer to use for resolving method parameter + * names if needed (e.g. for default attribute names). Default is a + * {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}. */ public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) { this.parameterNameDiscoverer = parameterNameDiscoverer; @@ -349,101 +378,144 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect; } + /** + * {@inheritDoc} + *

A {@link ConfigurableBeanFactory} is expected for resolving + * expressions in method argument default values. + */ public void setBeanFactory(BeanFactory beanFactory) { if (beanFactory instanceof ConfigurableBeanFactory) { this.beanFactory = (ConfigurableBeanFactory) beanFactory; } } + /** + * Return the owning factory of this bean instance, or {@code null}. + */ + protected ConfigurableBeanFactory getBeanFactory() { + return this.beanFactory; + } + public void afterPropertiesSet() { - initArgumentResolvers(); - initReturnValueHandlers(); - initInitBinderArgumentResolvers(); + if (this.argumentResolvers == null) { + List resolvers = getDefaultArgumentResolvers(); + this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); + } + if (this.initBinderArgumentResolvers == null) { + List resolvers = getDefaultInitBinderArgumentResolvers(); + this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); + } + if (this.returnValueHandlers == null) { + List handlers = getDefaultReturnValueHandlers(); + this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); + } } - private void initArgumentResolvers() { - if (argumentResolvers != null) { - return; + /** + * Return the list of argument resolvers to use including built-in resolvers + * and custom resolvers provided via {@link #setCustomArgumentResolvers}. + */ + protected List getDefaultArgumentResolvers() { + List resolvers = new ArrayList(); + + // Annotation-based argument resolution + resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); + resolvers.add(new RequestParamMapMethodArgumentResolver()); + resolvers.add(new PathVariableMethodArgumentResolver()); + resolvers.add(new ServletModelAttributeMethodProcessor(false)); + resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters())); + resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters())); + resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); + resolvers.add(new RequestHeaderMapMethodArgumentResolver()); + resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); + resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); + + // Type-based argument resolution + resolvers.add(new ServletRequestMethodArgumentResolver()); + resolvers.add(new ServletResponseMethodArgumentResolver()); + resolvers.add(new HttpEntityMethodProcessor(getMessageConverters())); + resolvers.add(new RedirectAttributesMethodArgumentResolver()); + resolvers.add(new ModelMethodProcessor()); + resolvers.add(new MapMethodProcessor()); + resolvers.add(new ErrorsMethodArgumentResolver()); + resolvers.add(new SessionStatusMethodArgumentResolver()); + + // Custom arguments + if (getCustomArgumentResolvers() != null) { + resolvers.addAll(getCustomArgumentResolvers()); } - argumentResolvers = new HandlerMethodArgumentResolverComposite(); + // Catch-all + resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); + resolvers.add(new ServletModelAttributeMethodProcessor(true)); - // Annotation-based resolvers - argumentResolvers.addResolver(new RequestParamMethodArgumentResolver(beanFactory, false)); - argumentResolvers.addResolver(new RequestParamMapMethodArgumentResolver()); - argumentResolvers.addResolver(new PathVariableMethodArgumentResolver()); - argumentResolvers.addResolver(new ServletModelAttributeMethodProcessor(false)); - argumentResolvers.addResolver(new RequestResponseBodyMethodProcessor(messageConverters)); - argumentResolvers.addResolver(new RequestPartMethodArgumentResolver(messageConverters)); - argumentResolvers.addResolver(new RequestHeaderMethodArgumentResolver(beanFactory)); - argumentResolvers.addResolver(new RequestHeaderMapMethodArgumentResolver()); - argumentResolvers.addResolver(new ServletCookieValueMethodArgumentResolver(beanFactory)); - argumentResolvers.addResolver(new ExpressionValueMethodArgumentResolver(beanFactory)); - - // Custom resolvers - argumentResolvers.addResolvers(customArgumentResolvers); - - // Type-based resolvers - argumentResolvers.addResolver(new ServletRequestMethodArgumentResolver()); - argumentResolvers.addResolver(new ServletResponseMethodArgumentResolver()); - argumentResolvers.addResolver(new HttpEntityMethodProcessor(messageConverters)); - argumentResolvers.addResolver(new RedirectAttributesMethodArgumentResolver()); - argumentResolvers.addResolver(new ModelMethodProcessor()); - argumentResolvers.addResolver(new ErrorsMethodArgumentResolver()); - - // Default-mode resolution - argumentResolvers.addResolver(new RequestParamMethodArgumentResolver(beanFactory, true)); - argumentResolvers.addResolver(new ServletModelAttributeMethodProcessor(true)); + return resolvers; } - private void initInitBinderArgumentResolvers() { - if (initBinderArgumentResolvers != null) { - return; + /** + * Return the list of argument resolvers to use for {@code @InitBinder} + * methods including built-in and custom resolvers. + */ + protected List getDefaultInitBinderArgumentResolvers() { + List resolvers = new ArrayList(); + + // Annotation-based argument resolution + resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); + resolvers.add(new RequestParamMapMethodArgumentResolver()); + resolvers.add(new PathVariableMethodArgumentResolver()); + resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); + + // Type-based argument resolution + resolvers.add(new ServletRequestMethodArgumentResolver()); + resolvers.add(new ServletResponseMethodArgumentResolver()); + + // Custom arguments + if (getCustomArgumentResolvers() != null) { + resolvers.addAll(getCustomArgumentResolvers()); } - initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite(); - - // Annotation-based resolvers - initBinderArgumentResolvers.addResolver(new RequestParamMethodArgumentResolver(beanFactory, false)); - initBinderArgumentResolvers.addResolver(new RequestParamMapMethodArgumentResolver()); - initBinderArgumentResolvers.addResolver(new PathVariableMethodArgumentResolver()); - initBinderArgumentResolvers.addResolver(new ExpressionValueMethodArgumentResolver(beanFactory)); - - // Custom resolvers - initBinderArgumentResolvers.addResolvers(customArgumentResolvers); - - // Type-based resolvers - initBinderArgumentResolvers.addResolver(new ServletRequestMethodArgumentResolver()); - initBinderArgumentResolvers.addResolver(new ServletResponseMethodArgumentResolver()); + // Catch-all + resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); - // Default-mode resolution - initBinderArgumentResolvers.addResolver(new RequestParamMethodArgumentResolver(beanFactory, true)); + return resolvers; } - - private void initReturnValueHandlers() { - if (returnValueHandlers != null) { - return; + + /** + * Return the list of return value handlers to use including built-in and + * custom handlers provided via {@link #setReturnValueHandlers}. + */ + protected List getDefaultReturnValueHandlers() { + List handlers = new ArrayList(); + + // Single-purpose return value types + handlers.add(new ModelAndViewMethodReturnValueHandler()); + handlers.add(new ModelMethodProcessor()); + handlers.add(new ViewMethodReturnValueHandler()); + handlers.add(new HttpEntityMethodProcessor(getMessageConverters())); + + // Annotation-based return value types + handlers.add(new ModelAttributeMethodProcessor(false)); + handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters())); + + // Multi-purpose return value types + handlers.add(new ViewNameMethodReturnValueHandler()); + handlers.add(new MapMethodProcessor()); + + // Custom return value types + if (getCustomReturnValueHandlers() != null) { + handlers.addAll(getCustomReturnValueHandlers()); } - returnValueHandlers = new HandlerMethodReturnValueHandlerComposite(); + // Catch-all + handlers.add(new DefaultMethodReturnValueHandler(getModelAndViewResolvers())); - // Annotation-based handlers - returnValueHandlers.addHandler(new RequestResponseBodyMethodProcessor(messageConverters)); - returnValueHandlers.addHandler(new ModelAttributeMethodProcessor(false)); - - // Custom return value handlers - returnValueHandlers.addHandlers(customReturnValueHandlers); - - // Type-based handlers - returnValueHandlers.addHandler(new ModelAndViewMethodReturnValueHandler()); - returnValueHandlers.addHandler(new ModelMethodProcessor()); - returnValueHandlers.addHandler(new ViewMethodReturnValueHandler()); - returnValueHandlers.addHandler(new HttpEntityMethodProcessor(messageConverters)); - - // Default handler - returnValueHandlers.addHandler(new DefaultMethodReturnValueHandler(modelAndViewResolvers)); + return handlers; } + /** + * Return {@code true} if all arguments and the return value of the given + * HandlerMethod are supported by the configured resolvers and handlers. + */ @Override protected boolean supportsInternal(HandlerMethod handlerMethod) { return supportsMethodParameters(handlerMethod.getMethodParameters()) && @@ -465,11 +537,10 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i } /** - * {@inheritDoc} - *

This implementation always returns -1 since {@link HandlerMethod} does not implement {@link LastModified}. - * Instead an @{@link RequestMapping} method, calculate the lastModified value, and call - * {@link WebRequest#checkNotModified(long)}, and return {@code null} if that returns {@code true}. - * @see WebRequest#checkNotModified(long) + * This implementation always returns -1. An {@code @RequestMapping} + * method can calculate the lastModified value, call + * {@link WebRequest#checkNotModified(long)}, and return {@code null} + * if the result of that call is {@code true}. */ @Override protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) { @@ -532,18 +603,16 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i ServletWebRequest webRequest = new ServletWebRequest(request, response); WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); - ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); + ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, requestMappingMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); - SessionStatus sessionStatus = new SimpleSessionStatus(); - - requestMappingMethod.invokeAndHandle(webRequest, mavContainer, sessionStatus); - modelFactory.updateModel(webRequest, mavContainer, sessionStatus); + requestMappingMethod.invokeAndHandle(webRequest, mavContainer); + modelFactory.updateModel(webRequest, mavContainer); if (mavContainer.isRequestHandled()) { return null; diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java index fb8a9d7e6f..fca99efe26 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java @@ -19,7 +19,6 @@ package org.springframework.web.servlet.mvc.method.annotation.support; import java.io.IOException; import java.lang.reflect.Array; import java.lang.reflect.GenericArrayType; -import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.List; @@ -39,8 +38,13 @@ import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.ModelAndViewContainer; /** - * Resolves {@link HttpEntity} method argument values. - * Handles {@link HttpEntity} and {@link ResponseEntity} return values. + * Resolves {@link HttpEntity} method argument values and also handles + * both {@link HttpEntity} and {@link ResponseEntity} return values. + * + *

An {@link HttpEntity} return type has a set purpose. Therefore this + * handler should be configured ahead of handlers that support any return + * value type annotated with {@code @ModelAttribute} or {@code @ResponseBody} + * to ensure they don't take over. * * @author Arjen Poutsma * @author Rossen Stoyanchev diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewMethodReturnValueHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewMethodReturnValueHandler.java index f4ff0727e0..771a2c9ec8 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewMethodReturnValueHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewMethodReturnValueHandler.java @@ -23,9 +23,17 @@ import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.servlet.ModelAndView; /** - * Handles return values of type {@link ModelAndView} transferring their content to the {@link ModelAndViewContainer}. - * If the return value is {@code null}, the {@link ModelAndViewContainer#setRequestHandled(boolean)} flag is set to - * {@code false} to indicate view resolution is not needed. + * Handles return values of type {@link ModelAndView} copying view and model + * information to the {@link ModelAndViewContainer}. + * + *

If the return value is {@code null}, the + * {@link ModelAndViewContainer#setRequestHandled(boolean)} flag is set to + * {@code false} to indicate the request was handled directly. + * + *

A {@link ModelAndView} return type has a set purpose. Therefore this + * handler should be configured ahead of handlers that support any return + * value type annotated with {@code @ModelAttribute} or {@code @ResponseBody} + * to ensure they don't take over. * * @author Rossen Stoyanchev * @since 3.1 diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java index 4052952e6d..e90cd3a20d 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java @@ -17,8 +17,6 @@ package org.springframework.web.servlet.mvc.method.annotation.support; import org.springframework.core.MethodParameter; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.ModelAndViewContainer; @@ -27,27 +25,23 @@ import org.springframework.web.servlet.SmartView; import org.springframework.web.servlet.View; /** - * Handles return values that are of type {@code void}, {@code String} (i.e. - * logical view name), or {@link View}. + * Handles return values that are of type {@link View}. * - *

A {@code null} return value, either due to a void return type or as the - * actual value returned from a method is left unhandled, leaving it to the - * configured {@link RequestToViewNameTranslator} to resolve the request to - * an actual view name. - * - *

Since a {@link String} return value may be handled in combination with - * method annotations such as @{@link ModelAttribute} or @{@link ResponseBody}, - * this handler should be ordered after return value handlers that support - * method annotations. + *

A {@code null} return value is left as-is leaving it to the configured + * {@link RequestToViewNameTranslator} to select a view name by convention. * + *

A {@link View} return type has a set purpose. Therefore this handler + * should be configured ahead of handlers that support any return value type + * annotated with {@code @ModelAttribute} or {@code @ResponseBody} to ensure + * they don't take over. + * * @author Rossen Stoyanchev * @since 3.1 */ public class ViewMethodReturnValueHandler implements HandlerMethodReturnValueHandler { public boolean supportsReturnType(MethodParameter returnType) { - Class type = returnType.getParameterType(); - return (void.class.equals(type) || String.class.equals(type) || View.class.isAssignableFrom(type)); + return View.class.isAssignableFrom(returnType.getParameterType()); } public void handleReturnValue(Object returnValue, @@ -57,50 +51,20 @@ public class ViewMethodReturnValueHandler implements HandlerMethodReturnValueHan if (returnValue == null) { return; } - if (returnValue instanceof String) { - String viewName = (String) returnValue; - mavContainer.setViewName(viewName); - if (isRedirectViewName(viewName)) { - mavContainer.setUseRedirectModel(true); - } - } else if (returnValue instanceof View){ View view = (View) returnValue; mavContainer.setView(view); - if (isRedirectView(view)) { - mavContainer.setUseRedirectModel(true); + if (view instanceof SmartView) { + if (((SmartView) view).isRedirectView()) { + mavContainer.setRedirectModelScenario(true); + } } } else { // should not happen - throw new UnsupportedOperationException("Unknown return type: " + + throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); } } - /** - * Whether the given view name is a redirect view reference. - * @param viewName the view name to check, never {@code null} - * @return "true" if the given view name is recognized as a redirect view - * reference; "false" otherwise. - */ - protected boolean isRedirectViewName(String viewName) { - return viewName.startsWith("redirect:"); - } - - /** - * Whether the given View instance is a redirect view. - * @param view a view instance, never {@code null} - * @return "true" if the given view is recognized as a redirect View; - * "false" otherwise. - */ - protected boolean isRedirectView(View view) { - if (view instanceof SmartView) { - return ((SmartView) view).isRedirectView(); - } - else { - return false; - } - } - } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewNameMethodReturnValueHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewNameMethodReturnValueHandler.java new file mode 100644 index 0000000000..8b983d688d --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewNameMethodReturnValueHandler.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2011 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.mvc.method.annotation.support; + +import org.springframework.core.MethodParameter; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.RequestToViewNameTranslator; + +/** + * Handles return values of types {@code void} and {@code String} interpreting + * them as view name reference. + * + *

A {@code null} return value, either due to a {@code void} return type or + * as the actual return value is left as-is allowing the configured + * {@link RequestToViewNameTranslator} to select a view name by convention. + * + *

A String return value can be interpreted in more than one ways depending + * on the presence of annotations like {@code @ModelAttribute} or + * {@code @ResponseBody}. Therefore this handler should be configured after + * the handlers that support these annotations. + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler { + + public boolean supportsReturnType(MethodParameter returnType) { + Class paramType = returnType.getParameterType(); + return (void.class.equals(paramType) || String.class.equals(paramType)); + } + + public void handleReturnValue(Object returnValue, + MethodParameter returnType, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest) throws Exception { + if (returnValue == null) { + return; + } + else if (returnValue instanceof String) { + String viewName = (String) returnValue; + mavContainer.setViewName(viewName); + if (isRedirectViewName(viewName)) { + mavContainer.setRedirectModelScenario(true); + } + } + else { + // should not happen + throw new UnsupportedOperationException("Unexpected return type: " + + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); + } + } + + /** + * Whether the given view name is a redirect view reference. + * @param viewName the view name to check, never {@code null} + * @return "true" if the given view name is recognized as a redirect view + * reference; "false" otherwise. + */ + protected boolean isRedirectViewName(String viewName) { + return viewName.startsWith("redirect:"); + } + +} diff --git a/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd b/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd index 90b54c88a2..52a9ef429c 100644 --- a/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd +++ b/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd @@ -53,10 +53,9 @@ @@ -64,7 +63,7 @@ @@ -74,9 +73,9 @@ diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java index 85c79335da..a239e23a74 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java @@ -17,6 +17,7 @@ package org.springframework.web.servlet.mvc.method.annotation; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; @@ -72,6 +73,7 @@ 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.SessionStatus; import org.springframework.web.bind.support.WebArgumentResolver; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.RequestContextHolder; @@ -138,7 +140,7 @@ public class RequestMappingHandlerAdapterIntegrationTests { } @Test - public void handleMvc() throws Exception { + public void handle() throws Exception { Class[] parameterTypes = new Class[] { int.class, String.class, String.class, String.class, Map.class, Date.class, Map.class, String.class, String.class, TestBean.class, Errors.class, TestBean.class, @@ -160,15 +162,12 @@ public class RequestMappingHandlerAdapterIntegrationTests { request.setContent("Hello World".getBytes("UTF-8")); request.setUserPrincipal(new User()); request.setContextPath("/contextPath"); - System.setProperty("systemHeader", "systemHeaderValue"); - - /* Set up path variables as RequestMappingHandlerMapping would... */ Map uriTemplateVars = new HashMap(); uriTemplateVars.put("pathvar", "pathvarValue"); request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars); - HandlerMethod handlerMethod = handlerMethod("handleMvc", parameterTypes); + HandlerMethod handlerMethod = handlerMethod("handle", parameterTypes); ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod); ModelMap model = mav.getModelMap(); @@ -259,6 +258,14 @@ public class RequestMappingHandlerAdapterIntegrationTests { assertEquals("content", mav.getModelMap().get("requestPart")); } + @Test + public void handleAndCompleteSession() throws Exception { + HandlerMethod handlerMethod = handlerMethod("handleAndCompleteSession", SessionStatus.class); + ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod); + + assertFalse(request.getSession().getAttributeNames().hasMoreElements()); + } + private HandlerMethod handlerMethod(String methodName, Class... paramTypes) throws Exception { Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes); return new InvocableHandlerMethod(handler, method); @@ -287,7 +294,7 @@ public class RequestMappingHandlerAdapterIntegrationTests { model.addAttribute(new OtherUser()); } - public String handleMvc( + public String handle( @CookieValue("cookie") int cookie, @PathVariable("pathvar") String pathvar, @RequestHeader("header") String header, @@ -336,6 +343,10 @@ public class RequestMappingHandlerAdapterIntegrationTests { public void handleRequestPart(@RequestPart String requestPart, Model model) { model.addAttribute("requestPart", requestPart); } + + public void handleAndCompleteSession(SessionStatus sessionStatus) { + sessionStatus.setComplete(); + } } private static class StubValidator implements Validator { diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java index 616e6de1b5..1b1b648f87 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java @@ -20,35 +20,27 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; -import org.springframework.beans.DirectFieldAccessor; -import org.springframework.core.MethodParameter; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.SessionAttributes; -import org.springframework.web.bind.support.WebDataBinderFactory; -import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.support.GenericWebApplicationContext; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.annotation.support.ModelMethodProcessor; import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; -import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite; import org.springframework.web.method.support.InvocableHandlerMethod; -import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.servlet.FlashMap; import org.springframework.web.servlet.FlashMapManager; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.method.annotation.support.RedirectAttributesMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.support.ServletRequestMethodArgumentResolver; -import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler; +import org.springframework.web.servlet.mvc.method.annotation.support.ViewNameMethodReturnValueHandler; /** * Unit tests for {@link RequestMappingHandlerAdapter}. @@ -61,12 +53,28 @@ import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodR */ public class RequestMappingHandlerAdapterTests { + private static int RESOLVER_COUNT; + + private static int INIT_BINDER_RESOLVER_COUNT; + + private static int HANDLER_COUNT; + private RequestMappingHandlerAdapter handlerAdapter; private MockHttpServletRequest request; private MockHttpServletResponse response; + @BeforeClass + public static void setupOnce() { + RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter(); + adapter.afterPropertiesSet(); + + RESOLVER_COUNT = adapter.getArgumentResolvers().getResolvers().size(); + INIT_BINDER_RESOLVER_COUNT = adapter.getInitBinderArgumentResolvers().getResolvers().size(); + HANDLER_COUNT = adapter.getReturnValueHandlers().getHandlers().size(); + } + @Before public void setup() throws Exception { this.handlerAdapter = new RequestMappingHandlerAdapter(); @@ -77,17 +85,17 @@ public class RequestMappingHandlerAdapterTests { @Test public void cacheControlWithoutSessionAttributes() throws Exception { - SimpleHandler handler = new SimpleHandler(); + HandlerMethod handlerMethod = handlerMethod(new SimpleController(), "handle"); handlerAdapter.afterPropertiesSet(); handlerAdapter.setCacheSeconds(100); - handlerAdapter.handle(request, response, handlerMethod(handler, "handle")); + handlerAdapter.handle(request, response, handlerMethod); assertTrue(response.getHeader("Cache-Control").toString().contains("max-age")); } @Test public void cacheControlWithSessionAttributes() throws Exception { - SessionAttributeHandler handler = new SessionAttributeHandler(); + SessionAttributeController handler = new SessionAttributeController(); handlerAdapter.afterPropertiesSet(); handlerAdapter.setCacheSeconds(100); handlerAdapter.handle(request, response, handlerMethod(handler, "handle")); @@ -99,7 +107,7 @@ public class RequestMappingHandlerAdapterTests { public void setAlwaysUseRedirectAttributes() throws Exception { HandlerMethodArgumentResolver redirectAttributesResolver = new RedirectAttributesMethodArgumentResolver(); HandlerMethodArgumentResolver modelResolver = new ModelMethodProcessor(); - HandlerMethodReturnValueHandler viewHandler = new ViewMethodReturnValueHandler(); + HandlerMethodReturnValueHandler viewHandler = new ViewNameMethodReturnValueHandler(); handlerAdapter.setArgumentResolvers(Arrays.asList(redirectAttributesResolver, modelResolver)); handlerAdapter.setReturnValueHandlers(Arrays.asList(viewHandler)); @@ -108,104 +116,57 @@ public class RequestMappingHandlerAdapterTests { request.setAttribute(FlashMapManager.OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); - HandlerMethod handlerMethod = handlerMethod(new RedirectAttributeHandler(), "handle", Model.class); + HandlerMethod handlerMethod = handlerMethod(new RedirectAttributeController(), "handle", Model.class); ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod); assertTrue("Without RedirectAttributes arg, model should be empty", mav.getModel().isEmpty()); } @Test - @SuppressWarnings("unchecked") - public void setArgumentResolvers() { - List argumentResolvers = new ArrayList(); - argumentResolvers.add(new ServletRequestMethodArgumentResolver()); - - handlerAdapter.setArgumentResolvers(argumentResolvers); - handlerAdapter.afterPropertiesSet(); + public void setCustomArgumentResolvers() throws Exception { + HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver(); + this.handlerAdapter.setCustomArgumentResolvers(Arrays.asList(resolver)); + this.handlerAdapter.afterPropertiesSet(); - HandlerMethodArgumentResolverComposite composite = (HandlerMethodArgumentResolverComposite) - new DirectFieldAccessor(handlerAdapter).getPropertyValue("argumentResolvers"); - - List actual = (List) - new DirectFieldAccessor(composite).getPropertyValue("argumentResolvers"); - - assertEquals(argumentResolvers, actual); + assertTrue(this.handlerAdapter.getArgumentResolvers().getResolvers().contains(resolver)); + assertMethodProcessorCount(RESOLVER_COUNT + 1, INIT_BINDER_RESOLVER_COUNT + 1, HANDLER_COUNT); } @Test - @SuppressWarnings("unchecked") - public void setInitBinderArgumentResolvers() { - List argumentResolvers = new ArrayList(); - argumentResolvers.add(new ServletRequestMethodArgumentResolver()); - - handlerAdapter.setInitBinderArgumentResolvers(argumentResolvers); - handlerAdapter.afterPropertiesSet(); - - HandlerMethodArgumentResolverComposite composite = (HandlerMethodArgumentResolverComposite) - new DirectFieldAccessor(handlerAdapter).getPropertyValue("initBinderArgumentResolvers"); + public void setArgumentResolvers() throws Exception { + HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver(); + this.handlerAdapter.setArgumentResolvers(Arrays.asList(resolver)); + this.handlerAdapter.afterPropertiesSet(); - List actual = (List) - new DirectFieldAccessor(composite).getPropertyValue("argumentResolvers"); - - assertEquals(argumentResolvers, actual); + assertMethodProcessorCount(1, INIT_BINDER_RESOLVER_COUNT, HANDLER_COUNT); } @Test - @SuppressWarnings("unchecked") + public void setInitBinderArgumentResolvers() throws Exception { + HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver(); + handlerAdapter.setInitBinderArgumentResolvers(Arrays.asList(resolver)); + handlerAdapter.afterPropertiesSet(); + + assertMethodProcessorCount(RESOLVER_COUNT, 1, HANDLER_COUNT); + } + + @Test + public void setCustomReturnValueHandlers() { + HandlerMethodReturnValueHandler handler = new ViewNameMethodReturnValueHandler(); + handlerAdapter.setCustomReturnValueHandlers(Arrays.asList(handler)); + handlerAdapter.afterPropertiesSet(); + + assertTrue(this.handlerAdapter.getReturnValueHandlers().getHandlers().contains(handler)); + assertMethodProcessorCount(RESOLVER_COUNT, INIT_BINDER_RESOLVER_COUNT, HANDLER_COUNT + 1); + } + + @Test public void setReturnValueHandlers() { HandlerMethodReturnValueHandler handler = new ModelMethodProcessor(); - List handlers = Arrays.asList(handler); - - handlerAdapter.setReturnValueHandlers(handlers); + handlerAdapter.setReturnValueHandlers(Arrays.asList(handler)); handlerAdapter.afterPropertiesSet(); - - HandlerMethodReturnValueHandlerComposite composite = (HandlerMethodReturnValueHandlerComposite) - new DirectFieldAccessor(handlerAdapter).getPropertyValue("returnValueHandlers"); - List actual = (List) - new DirectFieldAccessor(composite).getPropertyValue("returnValueHandlers"); - - assertEquals(handlers, actual); - } - - @Test - @SuppressWarnings("unchecked") - public void setCustomArgumentResolvers() { - HandlerMethodArgumentResolver resolver = new TestHanderMethodArgumentResolver(); - handlerAdapter.setCustomArgumentResolvers(Arrays.asList(resolver)); - handlerAdapter.afterPropertiesSet(); - - HandlerMethodArgumentResolverComposite composite = (HandlerMethodArgumentResolverComposite) - new DirectFieldAccessor(handlerAdapter).getPropertyValue("argumentResolvers"); - - List actual = (List) - new DirectFieldAccessor(composite).getPropertyValue("argumentResolvers"); - - assertTrue(actual.contains(resolver)); - - composite = (HandlerMethodArgumentResolverComposite) - new DirectFieldAccessor(handlerAdapter).getPropertyValue("initBinderArgumentResolvers"); - - actual = (List) - new DirectFieldAccessor(composite).getPropertyValue("argumentResolvers"); - - assertTrue(actual.contains(resolver)); - } - - @Test - @SuppressWarnings("unchecked") - public void setCustomReturnValueHandlers() { - TestHandlerMethodReturnValueHandler handler = new TestHandlerMethodReturnValueHandler(); - handlerAdapter.setCustomReturnValueHandlers(Arrays.asList(handler)); - handlerAdapter.afterPropertiesSet(); - - HandlerMethodReturnValueHandlerComposite composite = (HandlerMethodReturnValueHandlerComposite) - new DirectFieldAccessor(handlerAdapter).getPropertyValue("returnValueHandlers"); - - List actual = (List) - new DirectFieldAccessor(composite).getPropertyValue("returnValueHandlers"); - - assertTrue(actual.contains(handler)); + assertMethodProcessorCount(RESOLVER_COUNT, INIT_BINDER_RESOLVER_COUNT, 1); } private HandlerMethod handlerMethod(Object handler, String methodName, Class... paramTypes) throws Exception { @@ -213,46 +174,37 @@ public class RequestMappingHandlerAdapterTests { return new InvocableHandlerMethod(handler, method); } - private final class TestHanderMethodArgumentResolver implements HandlerMethodArgumentResolver { + - public boolean supportsParameter(MethodParameter parameter) { - return false; - } + private void assertMethodProcessorCount(int resolverCount, int initBinderResolverCount, int handlerCount) { + assertEquals(resolverCount, this.handlerAdapter.getArgumentResolvers().getResolvers().size()); + assertEquals(initBinderResolverCount, this.handlerAdapter.getInitBinderArgumentResolvers().getResolvers().size()); + assertEquals(handlerCount, this.handlerAdapter.getReturnValueHandlers().getHandlers().size()); + } - public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + @SuppressWarnings("unused") + private static class SimpleController { + + public String handle() { return null; } } - private final class TestHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler{ - - public boolean supportsReturnType(MethodParameter returnType) { - return false; - } - - public void handleReturnValue(Object returnValue, MethodParameter returnType, - ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { - } - } - - static class SimpleHandler { - public void handle() { - } - } - @SessionAttributes("attr1") - static class SessionAttributeHandler { + private static class SessionAttributeController { + + @SuppressWarnings("unused") public void handle() { } } - static class RedirectAttributeHandler { + @SuppressWarnings("unused") + private static class RedirectAttributeController { + public String handle(Model model) { model.addAttribute("someAttr", "someAttrValue"); return "redirect:/path"; } } - } \ No newline at end of file diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandlerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandlerTests.java index 6cfafd20ce..d49891708b 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandlerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandlerTests.java @@ -16,7 +16,6 @@ package org.springframework.web.servlet.mvc.method.annotation.support; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -35,7 +34,7 @@ import org.springframework.web.servlet.view.InternalResourceView; import org.springframework.web.servlet.view.RedirectView; /** - * Test fixture with {@link DefaultMethodReturnValueHandler}. + * Test fixture with {@link ViewMethodReturnValueHandler}. * * @author Rossen Stoyanchev */ @@ -57,7 +56,6 @@ public class ViewMethodReturnValueHandlerTests { @Test public void supportsReturnType() throws Exception { assertTrue(this.handler.supportsReturnType(createReturnValueParam("view"))); - assertTrue(this.handler.supportsReturnType(createReturnValueParam("viewName"))); } @Test @@ -79,25 +77,6 @@ public class ViewMethodReturnValueHandlerTests { assertSame(redirectView, this.mavContainer.getView()); assertSame("Should have switched to the RedirectModel", redirectModel, this.mavContainer.getModel()); } - - @Test - public void returnViewName() throws Exception { - MethodParameter param = createReturnValueParam("viewName"); - this.handler.handleReturnValue("testView", param, this.mavContainer, this.webRequest); - - assertEquals("testView", this.mavContainer.getViewName()); - } - - @Test - public void returnViewNameRedirect() throws Exception { - ModelMap redirectModel = new RedirectAttributesModelMap(); - this.mavContainer.setRedirectModel(redirectModel); - MethodParameter param = createReturnValueParam("viewName"); - this.handler.handleReturnValue("redirect:testView", param, this.mavContainer, this.webRequest); - - assertEquals("redirect:testView", this.mavContainer.getViewName()); - assertSame("Should have switched to the RedirectModel", redirectModel, this.mavContainer.getModel()); - } private MethodParameter createReturnValueParam(String methodName) throws Exception { Method method = getClass().getDeclaredMethod(methodName); @@ -108,8 +87,4 @@ public class ViewMethodReturnValueHandlerTests { return null; } - String viewName() { - return null; - } - } \ No newline at end of file diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewNameMethodReturnValueHandlerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewNameMethodReturnValueHandlerTests.java new file mode 100644 index 0000000000..f4b7d64288 --- /dev/null +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewNameMethodReturnValueHandlerTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2011 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.mvc.method.annotation.support; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Method; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.MethodParameter; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.ui.ModelMap; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; + +/** + * Test fixture with {@link ViewNameMethodReturnValueHandler}. + * + * @author Rossen Stoyanchev + */ +public class ViewNameMethodReturnValueHandlerTests { + + private ViewNameMethodReturnValueHandler handler; + + private ModelAndViewContainer mavContainer; + + private ServletWebRequest webRequest; + + @Before + public void setUp() { + this.handler = new ViewNameMethodReturnValueHandler(); + this.mavContainer = new ModelAndViewContainer(); + this.webRequest = new ServletWebRequest(new MockHttpServletRequest()); + } + + @Test + public void supportsReturnType() throws Exception { + assertTrue(this.handler.supportsReturnType(createReturnValueParam("viewName"))); + } + + @Test + public void returnViewName() throws Exception { + MethodParameter param = createReturnValueParam("viewName"); + this.handler.handleReturnValue("testView", param, this.mavContainer, this.webRequest); + + assertEquals("testView", this.mavContainer.getViewName()); + } + + @Test + public void returnViewNameRedirect() throws Exception { + ModelMap redirectModel = new RedirectAttributesModelMap(); + this.mavContainer.setRedirectModel(redirectModel); + MethodParameter param = createReturnValueParam("viewName"); + this.handler.handleReturnValue("redirect:testView", param, this.mavContainer, this.webRequest); + + assertEquals("redirect:testView", this.mavContainer.getViewName()); + assertSame("Should have switched to the RedirectModel", redirectModel, this.mavContainer.getModel()); + } + + private MethodParameter createReturnValueParam(String methodName) throws Exception { + Method method = getClass().getDeclaredMethod(methodName); + return new MethodParameter(method, -1); + } + + String viewName() { + return null; + } + +} \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java index 24ab6e84a3..d1985e684c 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java @@ -198,13 +198,11 @@ public final class ModelFactory { * promotes model attributes to the session, and adds {@link BindingResult} attributes where missing. * @param request the current request * @param mavContainer the {@link ModelAndViewContainer} for the current request - * @param sessionStatus the session status for the current request * @throws Exception if the process of creating {@link BindingResult} attributes causes an error */ - public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer, SessionStatus sessionStatus) - throws Exception { + public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception { - if (sessionStatus.isComplete()){ + if (mavContainer.getSessionStatus().isComplete()){ this.sessionAttributesHandler.cleanupAttributes(request); } else { diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractNamedValueMethodArgumentResolver.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractNamedValueMethodArgumentResolver.java index a2ca492ef2..37d2599377 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractNamedValueMethodArgumentResolver.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractNamedValueMethodArgumentResolver.java @@ -55,7 +55,7 @@ import org.springframework.web.method.support.ModelAndViewContainer; */ public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver { - private final ConfigurableBeanFactory beanFactory; + private final ConfigurableBeanFactory configurableBeanFactory; private final BeanExpressionContext expressionContext; @@ -67,7 +67,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle * in default values, or {@code null} if default values are not expected to contain expressions */ public AbstractNamedValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory) { - this.beanFactory = beanFactory; + this.configurableBeanFactory = beanFactory; this.expressionContext = (beanFactory != null) ? new BeanExpressionContext(beanFactory, new RequestScope()) : null; } @@ -105,11 +105,11 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle * Obtain the named value for the given method parameter. */ private NamedValueInfo getNamedValueInfo(MethodParameter parameter) { - NamedValueInfo namedValueInfo = namedValueInfoCache.get(parameter); + NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter); if (namedValueInfo == null) { namedValueInfo = createNamedValueInfo(parameter); namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo); - namedValueInfoCache.put(parameter, namedValueInfo); + this.namedValueInfoCache.put(parameter, namedValueInfo); } return namedValueInfo; } @@ -153,15 +153,15 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle * Resolves the given default value into an argument value. */ private Object resolveDefaultValue(String defaultValue) { - if (beanFactory == null) { + if (this.configurableBeanFactory == null) { return defaultValue; } - String placeholdersResolved = beanFactory.resolveEmbeddedValue(defaultValue); - BeanExpressionResolver exprResolver = beanFactory.getBeanExpressionResolver(); + String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(defaultValue); + BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver(); if (exprResolver == null) { return defaultValue; } - return exprResolver.evaluate(placeholdersResolved, expressionContext); + return exprResolver.evaluate(placeholdersResolved, this.expressionContext); } /** diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/MapMethodProcessor.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/MapMethodProcessor.java new file mode 100644 index 0000000000..bd265bb9a5 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/MapMethodProcessor.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2011 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.method.annotation.support; + +import java.util.Map; + +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.method.support.ModelAndViewContainer; + +/** + * Resolves {@link Map} method arguments and handles {@link Map} return values. + * + *

A Map return value can be interpreted in more than one ways depending + * on the presence of annotations like {@code @ModelAttribute} or + * {@code @ResponseBody}. Therefore this handler should be configured after + * the handlers that support these annotations. + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler { + + public boolean supportsParameter(MethodParameter parameter) { + return Map.class.isAssignableFrom(parameter.getParameterType()); + } + + public Object resolveArgument(MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory) throws Exception { + return mavContainer.getModel(); + } + + public boolean supportsReturnType(MethodParameter returnType) { + return Map.class.isAssignableFrom(returnType.getParameterType()); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void handleReturnValue(Object returnValue, + MethodParameter returnType, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest) throws Exception { + if (returnValue == null) { + return; + } + else if (returnValue instanceof Map){ + mavContainer.addAllAttributes((Map) returnValue); + } + else { + // should not happen + throw new UnsupportedOperationException("Unexpected return type: " + + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); + } + } +} \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java index ce0da85ed4..2e175e4a67 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java @@ -16,12 +16,8 @@ package org.springframework.web.method.annotation.support; -import java.lang.reflect.Method; -import java.util.Map; - import org.springframework.core.MethodParameter; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -29,12 +25,12 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.ModelAndViewContainer; /** - * Resolves {@link Map} and {@link Model} method arguments. + * Resolves {@link Model} method arguments and handles {@link Model} return values. * - *

Handles {@link Model} return values adding their attributes to the {@link ModelAndViewContainer}. - * Handles {@link Map} return values in the same way as long as the method does not have an @{@link ModelAttribute}. - * If the method does have an @{@link ModelAttribute}, it is assumed the returned {@link Map} is a model attribute - * and not a model. + *

A {@link Model} return type has a set purpose. Therefore this handler + * should be configured ahead of handlers that support any return value type + * annotated with {@code @ModelAttribute} or {@code @ResponseBody} to ensure + * they don't take over. * * @author Rossen Stoyanchev * @since 3.1 @@ -42,8 +38,7 @@ import org.springframework.web.method.support.ModelAndViewContainer; public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler { public boolean supportsParameter(MethodParameter parameter) { - Class paramType = parameter.getParameterType(); - return Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType); + return Model.class.isAssignableFrom(parameter.getParameterType()); } public Object resolveArgument(MethodParameter parameter, @@ -54,12 +49,9 @@ public class ModelMethodProcessor implements HandlerMethodArgumentResolver, Hand } public boolean supportsReturnType(MethodParameter returnType) { - Class paramType = returnType.getParameterType(); - boolean hasModelAttr = returnType.getMethodAnnotation(ModelAttribute.class) != null; - return (Model.class.isAssignableFrom(paramType) || (Map.class.isAssignableFrom(paramType) && !hasModelAttr)); + return Model.class.isAssignableFrom(returnType.getParameterType()); } - @SuppressWarnings({ "unchecked", "rawtypes" }) public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, @@ -67,17 +59,13 @@ public class ModelMethodProcessor implements HandlerMethodArgumentResolver, Hand if (returnValue == null) { return; } - if (returnValue instanceof Model) { + else if (returnValue instanceof Model) { mavContainer.addAllAttributes(((Model) returnValue).asMap()); } - else if (returnValue instanceof Map){ - mavContainer.addAllAttributes((Map) returnValue); - } else { // should not happen - Method method = returnType.getMethod(); - String returnTypeName = returnType.getParameterType().getName(); - throw new UnsupportedOperationException("Unknown return type: " + returnTypeName + " in method: " + method); + throw new UnsupportedOperationException("Unexpected return type: " + + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); } } } \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/SessionStatusMethodArgumentResolver.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/SessionStatusMethodArgumentResolver.java new file mode 100644 index 0000000000..895bf5022d --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/SessionStatusMethodArgumentResolver.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2011 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.method.annotation.support; + +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.SessionStatus; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +/** + * Resolves {@link SessionStatus} arguments by obtaining it from the + * {@link ModelAndViewContainer}. + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +public class SessionStatusMethodArgumentResolver implements HandlerMethodArgumentResolver { + + public boolean supportsParameter(MethodParameter parameter) { + return SessionStatus.class.equals(parameter.getParameterType()); + } + + public Object resolveArgument(MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory) throws Exception { + return mavContainer.getSessionStatus(); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java b/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java index 82b04f2cd9..c6c6f8ee33 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java @@ -17,6 +17,7 @@ package org.springframework.web.method.support; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -45,6 +46,13 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu private final Map argumentResolverCache = new ConcurrentHashMap(); + /** + * Return a read-only list with the contained resolvers, or an empty list. + */ + public List getResolvers() { + return Collections.unmodifiableList(this.argumentResolvers); + } + /** * Whether the given {@linkplain MethodParameter method parameter} is supported by any registered * {@link HandlerMethodArgumentResolver}. @@ -90,19 +98,22 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu /** * Add the given {@link HandlerMethodArgumentResolver}. */ - public void addResolver(HandlerMethodArgumentResolver argumentResolver) { + public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver argumentResolver) { this.argumentResolvers.add(argumentResolver); + return this; } /** * Add the given {@link HandlerMethodArgumentResolver}s. */ - public void addResolvers(List argumentResolvers) { + public HandlerMethodArgumentResolverComposite addResolvers( + List argumentResolvers) { if (argumentResolvers != null) { for (HandlerMethodArgumentResolver resolver : argumentResolvers) { this.argumentResolvers.add(resolver); } } + return this; } } \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java b/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java index a6f8a57477..7652c879ef 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java @@ -17,6 +17,7 @@ package org.springframework.web.method.support; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -44,6 +45,13 @@ public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodRe private final Map returnValueHandlerCache = new ConcurrentHashMap(); + /** + * Return a read-only list with the registered handlers, or an empty list. + */ + public List getHandlers() { + return Collections.unmodifiableList(this.returnValueHandlers); + } + /** * Whether the given {@linkplain MethodParameter method return type} is supported by any registered * {@link HandlerMethodReturnValueHandler}. @@ -89,19 +97,22 @@ public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodRe /** * Add the given {@link HandlerMethodReturnValueHandler}. */ - public void addHandler(HandlerMethodReturnValueHandler returnValuehandler) { + public HandlerMethodReturnValueHandlerComposite addHandler(HandlerMethodReturnValueHandler returnValuehandler) { returnValueHandlers.add(returnValuehandler); + return this; } /** * Add the given {@link HandlerMethodReturnValueHandler}s. */ - public void addHandlers(List returnValueHandlers) { + public HandlerMethodReturnValueHandlerComposite addHandlers( + List returnValueHandlers) { if (returnValueHandlers != null) { for (HandlerMethodReturnValueHandler handler : returnValueHandlers) { this.returnValueHandlers.add(handler); } } + return this; } } \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java b/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java index 8efcd1a0b8..94096df006 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java @@ -21,6 +21,8 @@ import java.util.Map; import org.springframework.ui.Model; import org.springframework.ui.ModelMap; import org.springframework.validation.support.BindingAwareModelMap; +import org.springframework.web.bind.support.SessionStatus; +import org.springframework.web.bind.support.SimpleSessionStatus; /** * Records model and view related decisions made by @@ -33,7 +35,7 @@ import org.springframework.validation.support.BindingAwareModelMap; * *

A default {@link Model} is automatically created at instantiation. * An alternate model instance may be provided via {@link #setRedirectModel} - * for use in a redirect scenario. When {@link #setUseRedirectModel} is set + * for use in a redirect scenario. When {@link #setRedirectModelScenario} is set * to {@code true} signalling a redirect scenario, the {@link #getModel()} * returns the redirect model instead of the default model. * @@ -46,14 +48,16 @@ public class ModelAndViewContainer { private boolean requestHandled = false; - private final ModelMap model = new BindingAwareModelMap(); + private final ModelMap defaultModel = new BindingAwareModelMap(); private ModelMap redirectModel; - private boolean ignoreDefaultModelOnRedirect = false; - - private boolean useRedirectModel = false; + private boolean redirectModelScenario = false; + private boolean ignoreDefaultModelOnRedirect = false; + + private final SessionStatus sessionStatus = new SimpleSessionStatus(); + /** * Create a new instance. */ @@ -123,34 +127,45 @@ public class ModelAndViewContainer { } /** - * Return the model to use. This is either the default model created at - * instantiation or the redirect model if {@link #setUseRedirectModel} - * is set to {@code true}. If a redirect model was never provided via - * {@link #setRedirectModel}, return the default model unless - * {@link #setIgnoreDefaultModelOnRedirect} is set to {@code true}. + * 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}. */ public ModelMap getModel() { - if (!this.useRedirectModel) { - return this.model; - } - else if (this.redirectModel != null) { - return this.redirectModel; + if (useDefaultModel()) { + return this.defaultModel; } else { - return this.ignoreDefaultModelOnRedirect ? new ModelMap() : this.model; + return (this.redirectModel != null) ? this.redirectModel : new ModelMap(); } } + /** + * Whether to use the default model or the redirect model. + */ + private boolean useDefaultModel() { + return !this.redirectModelScenario || ((this.redirectModel == null) && !this.ignoreDefaultModelOnRedirect); + } + /** * Provide a separate model instance to use in a redirect scenario. * The provided additional model however is not used used unless - * {@link #setUseRedirectModel(boolean)} gets set to {@code true} to signal + * {@link #setRedirectModelScenario(boolean)} gets set to {@code true} to signal * a redirect scenario. */ public void setRedirectModel(ModelMap redirectModel) { this.redirectModel = redirectModel; } + /** + * Signal the conditions are in place for using a redirect model. + * Typically that means the controller has returned a redirect instruction. + */ + 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 @@ -164,11 +179,11 @@ public class ModelAndViewContainer { } /** - * Signal the conditions for using a redirect model are in place -- e.g. - * the controller has requested a redirect. + * Return the {@link SessionStatus} instance to use that can be used to + * signal that session processing is complete. */ - public void setUseRedirectModel(boolean useRedirectModel) { - this.useRedirectModel = useRedirectModel; + public SessionStatus getSessionStatus() { + return sessionStatus; } /** @@ -229,7 +244,13 @@ public class ModelAndViewContainer { else { sb.append("View is [").append(this.view).append(']'); } - sb.append("; model is ").append(getModel()); + if (useDefaultModel()) { + sb.append("; default model "); + } + else { + sb.append("; redirect model "); + } + sb.append(getModel()); } else { sb.append("Request handled directly"); diff --git a/org.springframework.web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java index df0eb30b8e..6357216c03 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java @@ -41,8 +41,6 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.support.DefaultSessionAttributeStore; import org.springframework.web.bind.support.SessionAttributeStore; -import org.springframework.web.bind.support.SessionStatus; -import org.springframework.web.bind.support.SimpleSessionStatus; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; @@ -161,7 +159,7 @@ public class ModelFactoryTests { replay(binderFactory); ModelFactory modelFactory = new ModelFactory(null, binderFactory, sessionAttrsHandler); - modelFactory.updateModel(webRequest, mavContainer, new SimpleSessionStatus()); + modelFactory.updateModel(webRequest, mavContainer); assertEquals(attrValue, mavContainer.getModel().remove(attrName)); assertSame(dataBinder.getBindingResult(), mavContainer.getModel().remove(bindingResultKey(attrName))); @@ -177,6 +175,7 @@ public class ModelFactoryTests { ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAttribute(attrName, attrValue); + mavContainer.getSessionStatus().setComplete(); sessionAttributeStore.storeAttribute(webRequest, attrName, attrValue); // Resolve successfully handler session attribute once @@ -187,11 +186,8 @@ public class ModelFactoryTests { expect(binderFactory.createBinder(webRequest, attrValue, attrName)).andReturn(dataBinder); replay(binderFactory); - SessionStatus sessionStatus = new SimpleSessionStatus(); - sessionStatus.setComplete(); - ModelFactory modelFactory = new ModelFactory(null, binderFactory, sessionAttrsHandler); - modelFactory.updateModel(webRequest, mavContainer, sessionStatus); + modelFactory.updateModel(webRequest, mavContainer); assertEquals(attrValue, mavContainer.getModel().get(attrName)); assertNull(sessionAttributeStore.retrieveAttribute(webRequest, attrName)); diff --git a/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/MapMethodProcessorTests.java b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/MapMethodProcessorTests.java new file mode 100644 index 0000000000..475e57fd03 --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/MapMethodProcessorTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2011 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.method.annotation.support; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Method; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.MethodParameter; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.ui.ModelMap; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; + +/** + * Test fixture with {@link MapMethodProcessor}. + * + * @author Rossen Stoyanchev + */ +public class MapMethodProcessorTests { + + private MapMethodProcessor processor; + + private ModelAndViewContainer mavContainer; + + private MethodParameter paramMap; + + private MethodParameter returnParamMap; + + private NativeWebRequest webRequest; + + @Before + public void setUp() throws Exception { + processor = new MapMethodProcessor(); + mavContainer = new ModelAndViewContainer(); + + Method method = getClass().getDeclaredMethod("map", Map.class); + paramMap = new MethodParameter(method, 0); + returnParamMap = new MethodParameter(method, 0); + + webRequest = new ServletWebRequest(new MockHttpServletRequest()); + } + + @Test + public void supportsParameter() { + assertTrue(processor.supportsParameter(paramMap)); + } + + @Test + public void supportsReturnType() { + assertTrue(processor.supportsReturnType(returnParamMap)); + } + + @Test + public void resolveArgumentValue() throws Exception { + assertSame(mavContainer.getModel(), processor.resolveArgument(paramMap, mavContainer, webRequest, null)); + } + + @Test + public void handleMapReturnValue() throws Exception { + mavContainer.addAttribute("attr1", "value1"); + Map returnValue = new ModelMap("attr2", "value2"); + + processor.handleReturnValue(returnValue , returnParamMap, mavContainer, webRequest); + + assertEquals("value1", mavContainer.getModel().get("attr1")); + assertEquals("value2", mavContainer.getModel().get("attr2")); + } + + @SuppressWarnings("unused") + private Map map(Map map) { + return null; + } + +} \ No newline at end of file diff --git a/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelMethodProcessorTests.java b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelMethodProcessorTests.java index fa830f1057..108bec635d 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelMethodProcessorTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelMethodProcessorTests.java @@ -21,14 +21,13 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.lang.reflect.Method; -import java.util.Map; import org.junit.Before; import org.junit.Test; import org.springframework.core.MethodParameter; import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.ui.ExtendedModelMap; import org.springframework.ui.Model; -import org.springframework.ui.ModelMap; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.method.support.ModelAndViewContainer; @@ -48,10 +47,6 @@ public class ModelMethodProcessorTests { private MethodParameter returnParamModel; - private MethodParameter paramMap; - - private MethodParameter returnParamMap; - private NativeWebRequest webRequest; @Before @@ -63,64 +58,39 @@ public class ModelMethodProcessorTests { paramModel = new MethodParameter(method, 0); returnParamModel = new MethodParameter(method, -1); - method = getClass().getDeclaredMethod("map", Map.class); - paramMap = new MethodParameter(method, 0); - returnParamMap = new MethodParameter(method, 0); - webRequest = new ServletWebRequest(new MockHttpServletRequest()); } @Test public void supportsParameter() { assertTrue(processor.supportsParameter(paramModel)); - assertTrue(processor.supportsParameter(paramMap)); } @Test public void supportsReturnType() { assertTrue(processor.supportsReturnType(returnParamModel)); - assertTrue(processor.supportsReturnType(returnParamMap)); } @Test public void resolveArgumentValue() throws Exception { - Object result = processor.resolveArgument(paramModel, mavContainer, webRequest, null); - assertSame(mavContainer.getModel(), result); - - result = processor.resolveArgument(paramMap, mavContainer, webRequest, null); - assertSame(mavContainer.getModel(), result); + assertSame(mavContainer.getModel(), processor.resolveArgument(paramModel, mavContainer, webRequest, null)); } @Test public void handleModelReturnValue() throws Exception { mavContainer.addAttribute("attr1", "value1"); - ModelMap returnValue = new ModelMap("attr2", "value2"); + Model returnValue = new ExtendedModelMap(); + returnValue.addAttribute("attr2", "value2"); processor.handleReturnValue(returnValue , returnParamModel, mavContainer, webRequest); assertEquals("value1", mavContainer.getModel().get("attr1")); assertEquals("value2", mavContainer.getModel().get("attr2")); } - - @Test - public void handleMapReturnValue() throws Exception { - mavContainer.addAttribute("attr1", "value1"); - Map returnValue = new ModelMap("attr2", "value2"); - - processor.handleReturnValue(returnValue , returnParamMap, mavContainer, webRequest); - - assertEquals("value1", mavContainer.getModel().get("attr1")); - assertEquals("value2", mavContainer.getModel().get("attr2")); - } @SuppressWarnings("unused") private Model model(Model model) { return null; } - - @SuppressWarnings("unused") - private Map map(Map map) { - return null; - } } \ No newline at end of file diff --git a/org.springframework.web/src/test/java/org/springframework/web/method/support/ModelAndViewContainerTests.java b/org.springframework.web/src/test/java/org/springframework/web/method/support/ModelAndViewContainerTests.java index cd369258b9..7e98d2c4e6 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/method/support/ModelAndViewContainerTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/method/support/ModelAndViewContainerTests.java @@ -52,7 +52,7 @@ public class ModelAndViewContainerTests { assertEquals("Default model should be used if not in redirect scenario", "value", this.mavContainer.getModel().get("name")); - this.mavContainer.setUseRedirectModel(true); + this.mavContainer.setRedirectModelScenario(true); assertEquals("Redirect model should be used in redirect scenario", "redirectValue", this.mavContainer.getModel().get("name")); @@ -61,7 +61,7 @@ public class ModelAndViewContainerTests { @Test public void getModelIgnoreDefaultModelOnRedirect() { this.mavContainer.addAttribute("name", "value"); - this.mavContainer.setUseRedirectModel(true); + this.mavContainer.setRedirectModelScenario(true); assertEquals("Default model should be used since no redirect model was provided", 1, this.mavContainer.getModel().size());