From 61cdc842e08f468c8aec9d10a900f3ba087f348e Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 17 Aug 2017 11:02:40 +0200 Subject: [PATCH] BindingResult support for constructor argument mismatch on immutable data object Issue: SPR-15542 --- .../ModelAttributeMethodProcessor.java | 137 ++++- .../ServletModelAttributeMethodProcessor.java | 15 +- .../AbstractServletHandlerMethodTests.java | 37 +- ...nnotationControllerHandlerMethodTests.java | 571 +++++++++--------- 4 files changed, 415 insertions(+), 345 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java index 7c8245ef695..f35e2619984 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java @@ -20,11 +20,13 @@ import java.beans.ConstructorProperties; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.util.Map; +import java.util.Optional; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; +import org.springframework.beans.TypeMismatchException; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; @@ -32,7 +34,9 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.validation.BindException; +import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; +import org.springframework.validation.FieldError; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.ModelAttribute; @@ -110,9 +114,6 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory"); String name = ModelFactory.getNameForParameter(parameter); - Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) : - createAttribute(name, parameter, binderFactory, webRequest)); - if (!mavContainer.isBindingDisabled(name)) { ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null && !ann.binding()) { @@ -120,51 +121,113 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol } } - WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); - if (binder.getTarget() != null) { - if (!mavContainer.isBindingDisabled(name)) { - bindRequestParameters(binder, webRequest); + Object attribute = null; + BindingResult bindingResult = null; + + if (mavContainer.containsAttribute(name)) { + attribute = mavContainer.getModel().get(name); + } + else { + // Create attribute instance + try { + attribute = createAttribute(name, parameter, binderFactory, webRequest); } - validateIfApplicable(binder, parameter); - if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { - throw new BindException(binder.getBindingResult()); + catch (BindException ex) { + if (isBindExceptionRequired(parameter)) { + // No BindingResult parameter -> fail with BindException + throw ex; + } + // Otherwise, expose null/empty value and associated BindingResult + if (parameter.getParameterType() == Optional.class) { + attribute = Optional.empty(); + } + bindingResult = ex.getBindingResult(); } } + if (bindingResult == null) { + // Bean property binding and validation; + // skipped in case of binding failure on construction. + WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); + if (binder.getTarget() != null) { + if (!mavContainer.isBindingDisabled(name)) { + bindRequestParameters(binder, webRequest); + } + validateIfApplicable(binder, parameter); + if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { + throw new BindException(binder.getBindingResult()); + } + } + // Value type adaptation, also covering java.util.Optional + if (!parameter.getParameterType().isInstance(attribute)) { + attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); + } + bindingResult = binder.getBindingResult(); + } + // Add resolved attribute and BindingResult at the end of the model - Map bindingResultModel = binder.getBindingResult().getModel(); + Map bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); - return (parameter.getParameterType().isInstance(attribute) ? attribute : - binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter)); + return attribute; } /** * Extension point to create the model attribute if not found in the model, * with subsequent parameter binding through bean properties (unless suppressed). - *

The default implementation uses the unique public no-arg constructor, if any, - * which may have arguments: It understands the JavaBeans {@link ConstructorProperties} - * annotation as well as runtime-retained parameter names in the bytecode, - * associating request parameters with constructor arguments by name. If no such - * constructor is found, the default constructor will be used (even if not public), - * assuming subsequent bean property bindings through setter methods. + *

The default implementation typically uses the unique public no-arg constructor + * if available but also handles a "primary constructor" approach for data classes: + * It understands the JavaBeans {@link ConstructorProperties} annotation as well as + * runtime-retained parameter names in the bytecode, associating request parameters + * with constructor arguments by name. If no such constructor is found, the default + * constructor will be used (even if not public), assuming subsequent bean property + * bindings through setter methods. * @param attributeName the name of the attribute (never {@code null}) * @param parameter the method parameter declaration * @param binderFactory for creating WebDataBinder instance * @param webRequest the current request * @return the created model attribute (never {@code null}) + * @throws BindException in case of constructor argument binding failure + * @throws Exception in case of constructor invocation failure + * @see #constructAttribute(Constructor, String, WebDataBinderFactory, NativeWebRequest) + * @see BeanUtils#findPrimaryConstructor(Class) */ protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception { - Class type = parameter.getParameterType(); - + MethodParameter nestedParameter = parameter.nestedIfOptional(); + Class type = nestedParameter.getNestedParameterType(); + Constructor ctor = BeanUtils.findPrimaryConstructor(type); if (ctor == null) { throw new IllegalStateException("No primary constructor found for " + type.getName()); } + Object attribute = constructAttribute(ctor, attributeName, binderFactory, webRequest); + if (parameter != nestedParameter) { + attribute = Optional.of(attribute); + } + return attribute; + } + + /** + * Construct a new attribute instance with the given constructor. + *

Called from + * {@link #createAttribute(String, MethodParameter, WebDataBinderFactory, NativeWebRequest)} + * after constructor resolution. + * @param ctor the constructor to use + * @param attributeName the name of the attribute (never {@code null}) + * @param binderFactory for creating WebDataBinder instance + * @param webRequest the current request + * @return the created model attribute (never {@code null}) + * @throws BindException in case of constructor argument binding failure + * @throws Exception in case of constructor invocation failure + * @since 5.0 + */ + protected Object constructAttribute(Constructor ctor, String attributeName, + WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception { + if (ctor.getParameterCount() == 0) { // A single default constructor -> clearly a standard JavaBeans arrangement. return BeanUtils.instantiateClass(ctor); @@ -179,10 +242,22 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol () -> "Invalid number of parameter names: " + paramNames.length + " for constructor " + ctor); Object[] args = new Object[paramTypes.length]; WebDataBinder binder = binderFactory.createBinder(webRequest, null, attributeName); + boolean bindingFailure = false; for (int i = 0; i < paramNames.length; i++) { - String[] parameterValues = webRequest.getParameterValues(paramNames[i]); - args[i] = (parameterValues != null ? binder.convertIfNecessary(parameterValues, paramTypes[i], - new MethodParameter(ctor, i)) : null); + String[] paramValues = webRequest.getParameterValues(paramNames[i]); + try { + args[i] = (paramValues != null ? + binder.convertIfNecessary(paramValues, paramTypes[i], new MethodParameter(ctor, i)) : null); + } + catch (TypeMismatchException ex) { + bindingFailure = true; + binder.getBindingResult().addError(new FieldError( + binder.getObjectName(), paramNames[i], ex.getValue(), true, + new String[] {ex.getErrorCode()}, null, ex.getLocalizedMessage())); + } + } + if (bindingFailure) { + throw new BindException(binder.getBindingResult()); } return BeanUtils.instantiateClass(ctor, args); } @@ -219,11 +294,23 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol /** * Whether to raise a fatal bind exception on validation errors. + *

The default implementation delegates to {@link #isBindExceptionRequired(MethodParameter)}. * @param binder the data binder used to perform data binding * @param parameter the method parameter declaration - * @return {@code true} if the next method argument is not of type {@link Errors} + * @return {@code true} if the next method parameter is not of type {@link Errors} + * @see #isBindExceptionRequired(MethodParameter) */ protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) { + return isBindExceptionRequired(parameter); + } + + /** + * Whether to raise a fatal bind exception on validation errors. + * @param parameter the method parameter declaration + * @return {@code true} if the next method parameter is not of type {@link Errors} + * @since 5.0 + */ + protected boolean isBindExceptionRequired(MethodParameter parameter) { int i = parameter.getParameterIndex(); Class[] paramTypes = parameter.getExecutable().getParameterTypes(); boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1])); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletModelAttributeMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletModelAttributeMethodProcessor.java index a40aa2c9b5a..d2fb86225b2 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletModelAttributeMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletModelAttributeMethodProcessor.java @@ -45,6 +45,7 @@ import org.springframework.web.servlet.HandlerMapping; * model attribute name and there is an appropriate type conversion strategy. * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 3.1 */ public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor { @@ -68,19 +69,19 @@ public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodPr * @see #createAttributeFromRequestValue */ @Override - protected final Object createAttribute(String attributeName, MethodParameter methodParam, + protected final Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception { String value = getRequestValueForAttribute(attributeName, request); if (value != null) { Object attribute = createAttributeFromRequestValue( - value, attributeName, methodParam, binderFactory, request); + value, attributeName, parameter, binderFactory, request); if (attribute != null) { return attribute; } } - return super.createAttribute(attributeName, methodParam, binderFactory, request); + return super.createAttribute(attributeName, parameter, binderFactory, request); } /** @@ -120,7 +121,7 @@ public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodPr * {@link Converter} that can perform the conversion. * @param sourceValue the source value to create the model attribute from * @param attributeName the name of the attribute (never {@code null}) - * @param methodParam the method parameter + * @param parameter the method parameter * @param binderFactory for creating WebDataBinder instance * @param request the current request * @return the created model attribute, or {@code null} if no suitable @@ -129,16 +130,16 @@ public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodPr */ @Nullable protected Object createAttributeFromRequestValue(String sourceValue, String attributeName, - MethodParameter methodParam, WebDataBinderFactory binderFactory, NativeWebRequest request) + MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception { DataBinder binder = binderFactory.createBinder(request, null, attributeName); ConversionService conversionService = binder.getConversionService(); if (conversionService != null) { TypeDescriptor source = TypeDescriptor.valueOf(String.class); - TypeDescriptor target = new TypeDescriptor(methodParam); + TypeDescriptor target = new TypeDescriptor(parameter); if (conversionService.canConvert(source, target)) { - return binder.convertIfNecessary(sourceValue, methodParam.getParameterType(), methodParam); + return binder.convertIfNecessary(sourceValue, parameter.getParameterType(), parameter); } } return null; diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/AbstractServletHandlerMethodTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/AbstractServletHandlerMethodTests.java index 8596fce97e7..0992df5c878 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/AbstractServletHandlerMethodTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/AbstractServletHandlerMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 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. @@ -35,9 +35,9 @@ import static org.junit.Assert.*; /** * Base class for tests using on the DispatcherServlet and HandlerMethod infrastructure classes: *

    - *
  • RequestMappingHandlerMapping - *
  • RequestMappingHandlerAdapter - *
  • ExceptionHandlerExceptionResolver + *
  • RequestMappingHandlerMapping + *
  • RequestMappingHandlerAdapter + *
  • ExceptionHandlerExceptionResolver *
* * @author Rossen Stoyanchev @@ -46,21 +46,23 @@ public abstract class AbstractServletHandlerMethodTests { private DispatcherServlet servlet; - @After - public void tearDown() { - this.servlet = null; - } protected DispatcherServlet getServlet() { assertNotNull("DispatcherServlet not initialized", servlet); return servlet; } + @After + public void tearDown() { + this.servlet = null; + } + /** * Initialize a DispatcherServlet instance registering zero or more controller classes. */ protected WebApplicationContext initServletWithControllers(final Class... controllerClasses) throws ServletException { + return initServlet(null, controllerClasses); } @@ -82,22 +84,17 @@ public abstract class AbstractServletHandlerMethodTests { wac.registerBeanDefinition(clazz.getSimpleName(), new RootBeanDefinition(clazz)); } - Class mappingType = RequestMappingHandlerMapping.class; - RootBeanDefinition beanDef = new RootBeanDefinition(mappingType); - beanDef.getPropertyValues().add("removeSemicolonContent", "false"); - wac.registerBeanDefinition("handlerMapping", beanDef); + RootBeanDefinition mappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class); + mappingDef.getPropertyValues().add("removeSemicolonContent", "false"); + wac.registerBeanDefinition("handlerMapping", mappingDef); - Class adapterType = RequestMappingHandlerAdapter.class; - wac.registerBeanDefinition("handlerAdapter", new RootBeanDefinition(adapterType)); + wac.registerBeanDefinition("handlerAdapter", new RootBeanDefinition(RequestMappingHandlerAdapter.class)); - Class resolverType = ExceptionHandlerExceptionResolver.class; - wac.registerBeanDefinition("requestMappingResolver", new RootBeanDefinition(resolverType)); + wac.registerBeanDefinition("requestMappingResolver", new RootBeanDefinition(ExceptionHandlerExceptionResolver.class)); - resolverType = ResponseStatusExceptionResolver.class; - wac.registerBeanDefinition("responseStatusResolver", new RootBeanDefinition(resolverType)); + wac.registerBeanDefinition("responseStatusResolver", new RootBeanDefinition(ResponseStatusExceptionResolver.class)); - resolverType = DefaultHandlerExceptionResolver.class; - wac.registerBeanDefinition("defaultResolver", new RootBeanDefinition(resolverType)); + wac.registerBeanDefinition("defaultResolver", new RootBeanDefinition(DefaultHandlerExceptionResolver.class)); if (initializer != null) { initializer.initialize(wac); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java index 35b4450f4cb..5f4653f4114 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java @@ -42,10 +42,10 @@ import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Set; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; -import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -66,7 +66,6 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.propertyeditors.CustomDateEditor; -import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.core.MethodParameter; import org.springframework.core.convert.converter.Converter; @@ -131,7 +130,6 @@ import org.springframework.web.bind.support.WebBindingInitializer; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.WebRequest; -import org.springframework.web.context.support.GenericWebApplicationContext; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.multipart.support.StringMultipartFileEditor; import org.springframework.web.servlet.ModelAndView; @@ -142,14 +140,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.support.RequestContextUtils; import org.springframework.web.servlet.view.InternalResourceViewResolver; -import static org.junit.Assert.assertArrayEquals; -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; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; /** * @author Rossen Stoyanchev @@ -247,13 +238,10 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void defaultExpressionParameters() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext context) { - RootBeanDefinition ppc = new RootBeanDefinition(PropertyPlaceholderConfigurer.class); - ppc.getPropertyValues().add("properties", "myKey=foo"); - context.registerBeanDefinition("ppc", ppc); - } + initServlet(wac -> { + RootBeanDefinition ppc = new RootBeanDefinition(PropertyPlaceholderConfigurer.class); + ppc.getPropertyValues().add("properties", "myKey=foo"); + wac.registerBeanDefinition("ppc", ppc); }, DefaultExpressionValueParamController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myApp/myPath.do"); @@ -271,17 +259,14 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void typeNestedSetBinding() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext context) { - RootBeanDefinition csDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class); - csDef.getPropertyValues().add("converters", new TestBeanConverter()); - RootBeanDefinition wbiDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class); - wbiDef.getPropertyValues().add("conversionService", csDef); - RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - adapterDef.getPropertyValues().add("webBindingInitializer", wbiDef); - context.registerBeanDefinition("handlerAdapter", adapterDef); - } + initServlet(wac -> { + RootBeanDefinition csDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class); + csDef.getPropertyValues().add("converters", new TestBeanConverter()); + RootBeanDefinition wbiDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class); + wbiDef.getPropertyValues().add("conversionService", csDef); + RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + adapterDef.getPropertyValues().add("webBindingInitializer", wbiDef); + wac.registerBeanDefinition("handlerAdapter", adapterDef); }, NestedSetController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do"); @@ -293,17 +278,14 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test // SPR-12903 public void pathVariableWithCustomConverter() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext context) { - RootBeanDefinition csDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class); - csDef.getPropertyValues().add("converters", new AnnotatedExceptionRaisingConverter()); - RootBeanDefinition wbiDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class); - wbiDef.getPropertyValues().add("conversionService", csDef); - RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - adapterDef.getPropertyValues().add("webBindingInitializer", wbiDef); - context.registerBeanDefinition("handlerAdapter", adapterDef); - } + initServlet(wac -> { + RootBeanDefinition csDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class); + csDef.getPropertyValues().add("converters", new AnnotatedExceptionRaisingConverter()); + RootBeanDefinition wbiDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class); + wbiDef.getPropertyValues().add("conversionService", csDef); + RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + adapterDef.getPropertyValues().add("webBindingInitializer", wbiDef); + wac.registerBeanDefinition("handlerAdapter", adapterDef); }, PathVariableWithCustomConverterController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath/1"); @@ -335,13 +317,10 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void emptyParameterListHandleMethod() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext context) { - RootBeanDefinition vrDef = new RootBeanDefinition(InternalResourceViewResolver.class); - vrDef.getPropertyValues().add("suffix", ".jsp"); - context.registerBeanDefinition("viewResolver", vrDef); - } + initServlet(wac -> { + RootBeanDefinition vrDef = new RootBeanDefinition(InternalResourceViewResolver.class); + vrDef.getPropertyValues().add("suffix", ".jsp"); + wac.registerBeanDefinition("viewResolver", vrDef); }, EmptyParameterListHandlerMethodController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/emptyParameterListHandler"); @@ -356,12 +335,9 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @SuppressWarnings("rawtypes") @Test public void sessionAttributeExposure() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext context) { - context.registerBeanDefinition("viewResolver", new RootBeanDefinition(ModelExposingViewResolver.class)); - } - }, MySessionAttributesController.class); + initServlet( + wac -> wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(ModelExposingViewResolver.class)), + MySessionAttributesController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPage"); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -387,15 +363,12 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @SuppressWarnings("rawtypes") @Test public void sessionAttributeExposureWithInterface() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext context) { - context.registerBeanDefinition("viewResolver", new RootBeanDefinition(ModelExposingViewResolver.class)); - DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); - autoProxyCreator.setBeanFactory(context.getBeanFactory()); - context.getBeanFactory().addBeanPostProcessor(autoProxyCreator); - context.getBeanFactory().registerSingleton("advisor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor())); - } + initServlet(wac -> { + wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(ModelExposingViewResolver.class)); + DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); + autoProxyCreator.setBeanFactory(wac.getBeanFactory()); + wac.getBeanFactory().addBeanPostProcessor(autoProxyCreator); + wac.getBeanFactory().registerSingleton("advisor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor())); }, MySessionAttributesControllerImpl.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPage"); @@ -422,12 +395,9 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @SuppressWarnings("rawtypes") @Test public void parameterizedAnnotatedInterface() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext context) { - context.registerBeanDefinition("viewResolver", new RootBeanDefinition(ModelExposingViewResolver.class)); - } - }, MyParameterizedControllerImpl.class); + initServlet( + wac -> wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(ModelExposingViewResolver.class)), + MyParameterizedControllerImpl.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPage"); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -455,12 +425,9 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @SuppressWarnings("rawtypes") @Test public void parameterizedAnnotatedInterfaceWithOverriddenMappingsInImpl() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext context) { - context.registerBeanDefinition("viewResolver", new RootBeanDefinition(ModelExposingViewResolver.class)); - } - }, MyParameterizedControllerImplWithOverriddenMappings.class); + initServlet( + wac -> wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(ModelExposingViewResolver.class)), + MyParameterizedControllerImplWithOverriddenMappings.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPage"); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -540,12 +507,9 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void formController() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)); - } - }, MyFormController.class); + initServlet( + wac -> wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)), + MyFormController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do"); request.addParameter("name", "name1"); @@ -557,12 +521,9 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void modelFormController() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)); - } - }, MyModelFormController.class); + initServlet( + wac -> wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)), + MyModelFormController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do"); request.addParameter("name", "name1"); @@ -574,16 +535,13 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void proxiedFormController() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)); - DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); - autoProxyCreator.setBeanFactory(wac.getBeanFactory()); - wac.getBeanFactory().addBeanPostProcessor(autoProxyCreator); - wac.getBeanFactory() - .registerSingleton("advisor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor())); - } + initServlet(wac -> { + wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)); + DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); + autoProxyCreator.setBeanFactory(wac.getBeanFactory()); + wac.getBeanFactory().addBeanPostProcessor(autoProxyCreator); + wac.getBeanFactory() + .registerSingleton("advisor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor())); }, MyFormController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do"); @@ -596,14 +554,11 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void commandProvidingFormControllerWithCustomEditor() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)); - RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - adapterDef.getPropertyValues().add("webBindingInitializer", new MyWebBindingInitializer()); - wac.registerBeanDefinition("handlerAdapter", adapterDef); - } + initServlet(wac -> { + wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)); + RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + adapterDef.getPropertyValues().add("webBindingInitializer", new MyWebBindingInitializer()); + wac.registerBeanDefinition("handlerAdapter", adapterDef); }, MyCommandProvidingFormController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do"); @@ -617,17 +572,14 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void typedCommandProvidingFormController() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)); - RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - adapterDef.getPropertyValues().add("webBindingInitializer", new MyWebBindingInitializer()); - List argumentResolvers = new ArrayList<>(); - argumentResolvers.add(new ServletWebArgumentResolverAdapter(new MySpecialArgumentResolver())); - adapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers); - wac.registerBeanDefinition("handlerAdapter", adapterDef); - } + initServlet(wac -> { + wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)); + RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + adapterDef.getPropertyValues().add("webBindingInitializer", new MyWebBindingInitializer()); + List argumentResolvers = new ArrayList<>(); + argumentResolvers.add(new ServletWebArgumentResolverAdapter(new MySpecialArgumentResolver())); + adapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers); + wac.registerBeanDefinition("handlerAdapter", adapterDef); }, MyTypedCommandProvidingFormController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do"); @@ -657,12 +609,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void binderInitializingCommandProvidingFormController() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)); - } - }, MyBinderInitializingCommandProvidingFormController.class); + initServlet(wac -> wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)), MyBinderInitializingCommandProvidingFormController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do"); request.addParameter("defaultName", "myDefaultName"); @@ -675,12 +622,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void specificBinderInitializingCommandProvidingFormController() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)); - } - }, MySpecificBinderInitializingCommandProvidingFormController.class); + initServlet(wac -> wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)), MySpecificBinderInitializingCommandProvidingFormController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do"); request.addParameter("defaultName", "myDefaultName"); @@ -696,15 +638,12 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl final MockServletContext servletContext = new MockServletContext(); final MockServletConfig servletConfig = new MockServletConfig(servletContext); - WebApplicationContext webAppContext = - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - wac.setServletContext(servletContext); - AnnotationConfigUtils.registerAnnotationConfigProcessors(wac); - wac.getBeanFactory().registerResolvableDependency(ServletConfig.class, servletConfig); - } - }, MyParameterDispatchingController.class); + WebApplicationContext webAppContext = + initServlet(wac -> { + wac.setServletContext(servletContext); + AnnotationConfigUtils.registerAnnotationConfigProcessors(wac); + wac.getBeanFactory().registerResolvableDependency(ServletConfig.class, servletConfig); + }, MyParameterDispatchingController.class); MockHttpServletRequest request = new MockHttpServletRequest(servletContext, "GET", "/myPath.do"); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -830,7 +769,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void pathOrdering() throws ServletException, IOException { + public void pathOrdering() throws Exception { initServletWithControllers(PathOrderingController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/dir/myPath1.do"); @@ -840,7 +779,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void requestBodyResponseBody() throws ServletException, IOException { + public void requestBodyResponseBody() throws Exception { initServletWithControllers(RequestResponseBodyController.class); MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/something"); @@ -855,7 +794,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void httpPatch() throws ServletException, IOException { + public void httpPatch() throws Exception { initServletWithControllers(RequestResponseBodyController.class); MockHttpServletRequest request = new MockHttpServletRequest("PATCH", "/something"); @@ -870,15 +809,12 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void responseBodyNoAcceptableMediaType() throws ServletException, IOException { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - StringHttpMessageConverter converter = new StringHttpMessageConverter(); - adapterDef.getPropertyValues().add("messageConverters", converter); - wac.registerBeanDefinition("handlerAdapter", adapterDef); - } + public void responseBodyNoAcceptableMediaType() throws Exception { + initServlet(wac -> { + RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + StringHttpMessageConverter converter = new StringHttpMessageConverter(); + adapterDef.getPropertyValues().add("messageConverters", converter); + wac.registerBeanDefinition("handlerAdapter", adapterDef); }, RequestResponseBodyProducesController.class); MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/something"); @@ -892,7 +828,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void responseBodyWildCardMediaType() throws ServletException, IOException { + public void responseBodyWildCardMediaType() throws Exception { initServletWithControllers(RequestResponseBodyController.class); MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/something"); @@ -906,14 +842,11 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void unsupportedRequestBody() throws ServletException, IOException { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - adapterDef.getPropertyValues().add("messageConverters", new ByteArrayHttpMessageConverter()); - wac.registerBeanDefinition("handlerAdapter", adapterDef); - } + public void unsupportedRequestBody() throws Exception { + initServlet(wac -> { + RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + adapterDef.getPropertyValues().add("messageConverters", new ByteArrayHttpMessageConverter()); + wac.registerBeanDefinition("handlerAdapter", adapterDef); }, RequestResponseBodyController.class); MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/something"); @@ -927,7 +860,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void responseBodyNoAcceptHeader() throws ServletException, IOException { + public void responseBodyNoAcceptHeader() throws Exception { initServletWithControllers(RequestResponseBodyController.class); MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/something"); @@ -941,14 +874,11 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void badRequestRequestBody() throws ServletException, IOException { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - adapterDef.getPropertyValues().add("messageConverters", new NotReadableMessageConverter()); - wac.registerBeanDefinition("handlerAdapter", adapterDef); - } + public void badRequestRequestBody() throws Exception { + initServlet(wac -> { + RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + adapterDef.getPropertyValues().add("messageConverters", new NotReadableMessageConverter()); + wac.registerBeanDefinition("handlerAdapter", adapterDef); }, RequestResponseBodyController.class); MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/something"); @@ -961,7 +891,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void httpEntity() throws ServletException, IOException { + public void httpEntity() throws Exception { initServletWithControllers(ResponseEntityController.class); MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo"); @@ -983,23 +913,16 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl assertEquals(404, response.getStatus()); } - - /* - * See SPR-6877 - */ - @Test - public void overlappingMessageConvertersRequestBody() throws ServletException, IOException { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - List> messageConverters = new ArrayList<>(); - messageConverters.add(new StringHttpMessageConverter()); - messageConverters - .add(new SimpleMessageConverter(new MediaType("application","json"), MediaType.ALL)); - adapterDef.getPropertyValues().add("messageConverters", messageConverters); - wac.registerBeanDefinition("handlerAdapter", adapterDef); - } + @Test // SPR-6877 + public void overlappingMessageConvertersRequestBody() throws Exception { + initServlet(wac -> { + RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + List> messageConverters = new ArrayList<>(); + messageConverters.add(new StringHttpMessageConverter()); + messageConverters + .add(new SimpleMessageConverter(new MediaType("application","json"), MediaType.ALL)); + adapterDef.getPropertyValues().add("messageConverters", messageConverters); + wac.registerBeanDefinition("handlerAdapter", adapterDef); }, RequestResponseBodyController.class); MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/something"); @@ -1012,7 +935,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void responseBodyVoid() throws ServletException, IOException { + public void responseBodyVoid() throws Exception { initServletWithControllers(ResponseBodyVoidController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/something"); @@ -1023,24 +946,21 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void responseBodyArgMismatch() throws ServletException, IOException { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); - marshaller.setClassesToBeBound(A.class, B.class); - try { - marshaller.afterPropertiesSet(); - } - catch (Exception ex) { - throw new BeanCreationException(ex.getMessage(), ex); - } - MarshallingHttpMessageConverter messageConverter = new MarshallingHttpMessageConverter(marshaller); - - RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - adapterDef.getPropertyValues().add("messageConverters", messageConverter); - wac.registerBeanDefinition("handlerAdapter", adapterDef); + public void responseBodyArgMismatch() throws Exception { + initServlet(wac -> { + Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); + marshaller.setClassesToBeBound(A.class, B.class); + try { + marshaller.afterPropertiesSet(); } + catch (Exception ex) { + throw new BeanCreationException(ex.getMessage(), ex); + } + MarshallingHttpMessageConverter messageConverter = new MarshallingHttpMessageConverter(marshaller); + + RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + adapterDef.getPropertyValues().add("messageConverters", messageConverter); + wac.registerBeanDefinition("handlerAdapter", adapterDef); }, RequestBodyArgMismatchController.class); MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/something"); @@ -1054,7 +974,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test - public void contentTypeHeaders() throws ServletException, IOException { + public void contentTypeHeaders() throws Exception { initServletWithControllers(ContentTypeHeadersController.class); MockHttpServletRequest request = new MockHttpServletRequest("POST", "/something"); @@ -1077,7 +997,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void consumes() throws ServletException, IOException { + public void consumes() throws Exception { initServletWithControllers(ConsumesController.class); MockHttpServletRequest request = new MockHttpServletRequest("POST", "/something"); @@ -1100,7 +1020,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void negatedContentTypeHeaders() throws ServletException, IOException { + public void negatedContentTypeHeaders() throws Exception { initServletWithControllers(NegatedContentTypeHeadersController.class); MockHttpServletRequest request = new MockHttpServletRequest("POST", "/something"); @@ -1117,7 +1037,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void acceptHeaders() throws ServletException, IOException { + public void acceptHeaders() throws Exception { initServletWithControllers(AcceptHeadersController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/something"); @@ -1152,7 +1072,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void produces() throws ServletException, IOException { + public void produces() throws Exception { initServletWithControllers(ProducesController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/something"); @@ -1187,7 +1107,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void responseStatus() throws ServletException, IOException { + public void responseStatus() throws Exception { initServletWithControllers(ResponseStatusController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/something"); @@ -1199,15 +1119,12 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void mavResolver() throws ServletException, IOException { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - ModelAndViewResolver[] mavResolvers = new ModelAndViewResolver[] {new MyModelAndViewResolver()}; - adapterDef.getPropertyValues().add("modelAndViewResolvers", mavResolvers); - wac.registerBeanDefinition("handlerAdapter", adapterDef); - } + public void mavResolver() throws Exception { + initServlet(wac -> { + RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + ModelAndViewResolver[] mavResolvers = new ModelAndViewResolver[] {new MyModelAndViewResolver()}; + adapterDef.getPropertyValues().add("modelAndViewResolvers", mavResolvers); + wac.registerBeanDefinition("handlerAdapter", adapterDef); }, ModelAndViewResolverController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); @@ -1218,7 +1135,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void bindingCookieValue() throws ServletException, IOException { + public void bindingCookieValue() throws Exception { initServletWithControllers(BindingCookieValueController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test"); @@ -1229,7 +1146,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void ambiguousParams() throws ServletException, IOException { + public void ambiguousParams() throws Exception { initServletWithControllers(AmbiguousParamsController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test"); @@ -1327,14 +1244,11 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void requestMappingInterfaceWithProxy() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); - autoProxyCreator.setBeanFactory(wac.getBeanFactory()); - wac.getBeanFactory().addBeanPostProcessor(autoProxyCreator); - wac.getBeanFactory().registerSingleton("advisor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor())); - } + initServlet(wac -> { + DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); + autoProxyCreator.setBeanFactory(wac.getBeanFactory()); + wac.getBeanFactory().addBeanPostProcessor(autoProxyCreator); + wac.getBeanFactory().registerSingleton("advisor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor())); }, IMyControllerImpl.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/handle"); @@ -1465,16 +1379,13 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void parameterCsvAsStringArray() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - RootBeanDefinition csDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class); - RootBeanDefinition wbiDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class); - wbiDef.getPropertyValues().add("conversionService", csDef); - RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - adapterDef.getPropertyValues().add("webBindingInitializer", wbiDef); - wac.registerBeanDefinition("handlerAdapter", adapterDef); - } + initServlet(wac -> { + RootBeanDefinition csDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class); + RootBeanDefinition wbiDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class); + wbiDef.getPropertyValues().add("conversionService", csDef); + RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + adapterDef.getPropertyValues().add("webBindingInitializer", wbiDef); + wac.registerBeanDefinition("handlerAdapter", adapterDef); }, CsvController.class); MockHttpServletRequest request = new MockHttpServletRequest(); @@ -1595,13 +1506,10 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void prototypeController() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext context) { - RootBeanDefinition beanDef = new RootBeanDefinition(PrototypeController.class); - beanDef.setScope(BeanDefinition.SCOPE_PROTOTYPE); - context.registerBeanDefinition("controller", beanDef); - } + initServlet(wac -> { + RootBeanDefinition beanDef = new RootBeanDefinition(PrototypeController.class); + beanDef.setScope(BeanDefinition.SCOPE_PROTOTYPE); + wac.registerBeanDefinition("controller", beanDef); }); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); @@ -1652,15 +1560,12 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void responseBodyAsHtml() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean(); - factoryBean.afterPropertiesSet(); - RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - adapterDef.getPropertyValues().add("contentNegotiationManager", factoryBean.getObject()); - wac.registerBeanDefinition("handlerAdapter", adapterDef); - } + initServlet(wac -> { + ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean(); + factoryBean.afterPropertiesSet(); + RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + adapterDef.getPropertyValues().add("contentNegotiationManager", factoryBean.getObject()); + wac.registerBeanDefinition("handlerAdapter", adapterDef); }, TextRestController.class); byte[] content = "alert('boo')".getBytes(StandardCharsets.ISO_8859_1); @@ -1678,15 +1583,12 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void responseBodyAsHtmlWithSuffixPresent() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean(); - factoryBean.afterPropertiesSet(); - RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - adapterDef.getPropertyValues().add("contentNegotiationManager", factoryBean.getObject()); - wac.registerBeanDefinition("handlerAdapter", adapterDef); - } + initServlet(wac -> { + ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean(); + factoryBean.afterPropertiesSet(); + RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + adapterDef.getPropertyValues().add("contentNegotiationManager", factoryBean.getObject()); + wac.registerBeanDefinition("handlerAdapter", adapterDef); }, TextRestController.class); byte[] content = "alert('boo')".getBytes(StandardCharsets.ISO_8859_1); @@ -1704,15 +1606,12 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void responseBodyAsHtmlWithProducesCondition() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean(); - factoryBean.afterPropertiesSet(); - RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - adapterDef.getPropertyValues().add("contentNegotiationManager", factoryBean.getObject()); - wac.registerBeanDefinition("handlerAdapter", adapterDef); - } + initServlet(wac -> { + ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean(); + factoryBean.afterPropertiesSet(); + RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + adapterDef.getPropertyValues().add("contentNegotiationManager", factoryBean.getObject()); + wac.registerBeanDefinition("handlerAdapter", adapterDef); }, TextRestController.class); byte[] content = "alert('boo')".getBytes(StandardCharsets.ISO_8859_1); @@ -1730,15 +1629,12 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Test public void responseBodyAsTextWithCssExtension() throws Exception { - initServlet(new ApplicationContextInitializer() { - @Override - public void initialize(GenericWebApplicationContext wac) { - ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean(); - factoryBean.afterPropertiesSet(); - RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - adapterDef.getPropertyValues().add("contentNegotiationManager", factoryBean.getObject()); - wac.registerBeanDefinition("handlerAdapter", adapterDef); - } + initServlet(wac -> { + ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean(); + factoryBean.afterPropertiesSet(); + RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + adapterDef.getPropertyValues().add("contentNegotiationManager", factoryBean.getObject()); + wac.registerBeanDefinition("handlerAdapter", adapterDef); }, TextRestController.class); byte[] content = "body".getBytes(StandardCharsets.ISO_8859_1); @@ -1779,7 +1675,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void httpHead() throws ServletException, IOException { + public void httpHead() throws Exception { initServletWithControllers(ResponseEntityController.class); MockHttpServletRequest request = new MockHttpServletRequest("HEAD", "/baz"); @@ -1803,7 +1699,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void httpHeadExplicit() throws ServletException, IOException { + public void httpHeadExplicit() throws Exception { initServletWithControllers(ResponseEntityController.class); MockHttpServletRequest request = new MockHttpServletRequest("HEAD", "/stores"); @@ -1815,7 +1711,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void httpOptions() throws ServletException, IOException { + public void httpOptions() throws Exception { initServletWithControllers(ResponseEntityController.class); MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/baz"); @@ -1828,7 +1724,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void dataClassBinding() throws ServletException, IOException { + public void dataClassBinding() throws Exception { initServletWithControllers(DataClassController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind"); @@ -1840,7 +1736,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } @Test - public void dataClassBindingWithAdditionalSetter() throws ServletException, IOException { + public void dataClassBindingWithAdditionalSetter() throws Exception { initServletWithControllers(DataClassController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind"); @@ -1852,6 +1748,66 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl assertEquals("value1-2-3", response.getContentAsString()); } + @Test + public void dataClassBindingWithResult() throws Exception { + initServletWithControllers(ValidatedDataClassController.class); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind"); + request.addParameter("param1", "value1"); + request.addParameter("param2", "2"); + request.addParameter("param3", "3"); + MockHttpServletResponse response = new MockHttpServletResponse(); + getServlet().service(request, response); + assertEquals("value1-2-3", response.getContentAsString()); + } + + @Test + public void dataClassBindingWithConversionError() throws Exception { + initServletWithControllers(ValidatedDataClassController.class); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind"); + request.addParameter("param2", "x"); + MockHttpServletResponse response = new MockHttpServletResponse(); + getServlet().service(request, response); + assertTrue(response.getContentAsString().contains("field 'param2'")); + } + + @Test + public void dataClassBindingWithValidationError() throws Exception { + initServletWithControllers(ValidatedDataClassController.class); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind"); + request.addParameter("param2", "2"); + request.addParameter("param3", "3"); + MockHttpServletResponse response = new MockHttpServletResponse(); + getServlet().service(request, response); + assertTrue(response.getContentAsString().contains("field 'param1'")); + } + + @Test + public void dataClassBindingWithOptional() throws Exception { + initServletWithControllers(OptionalDataClassController.class); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind"); + request.addParameter("param1", "value1"); + request.addParameter("param2", "2"); + request.addParameter("param3", "3"); + MockHttpServletResponse response = new MockHttpServletResponse(); + getServlet().service(request, response); + assertEquals("value1-2-3", response.getContentAsString()); + } + + @Test + public void dataClassBindingWithOptionalAndConversionError() throws Exception { + initServletWithControllers(OptionalDataClassController.class); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind"); + request.addParameter("param2", "x"); + MockHttpServletResponse response = new MockHttpServletResponse(); + getServlet().service(request, response); + assertTrue(response.getContentAsString().contains("field 'param2'")); + } + @Controller static class ControllerWithEmptyValueMapping { @@ -2195,11 +2151,8 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @Controller static class MyCommandProvidingFormController extends MyFormController { - @SuppressWarnings("unused") @ModelAttribute("myCommand") - private ValidTestBean createTestBean(@RequestParam T defaultName, - Map model, - @RequestParam Date date) { + public ValidTestBean createTestBean(@RequestParam T defaultName, Map model, @RequestParam Date date) { model.put("myKey", "myOriginalValue"); ValidTestBean tb = new ValidTestBean(); tb.setName(defaultName.getClass().getSimpleName() + ":" + defaultName.toString()); @@ -2253,9 +2206,8 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl static class MyBinderInitializingCommandProvidingFormController extends MyCommandProvidingFormController { - @SuppressWarnings("unused") @InitBinder - private void initBinder(WebDataBinder binder) { + public void initBinder(WebDataBinder binder) { binder.initBeanPropertyAccess(); binder.setRequiredFields("sex"); LocalValidatorFactoryBean vf = new LocalValidatorFactoryBean(); @@ -2280,9 +2232,8 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl static class MySpecificBinderInitializingCommandProvidingFormController extends MyCommandProvidingFormController { - @SuppressWarnings("unused") @InitBinder({"myCommand", "date"}) - private void initBinder(WebDataBinder binder, String date, @RequestParam("date") String[] date2) { + public void initBinder(WebDataBinder binder, String date, @RequestParam("date") String[] date2) { LocalValidatorFactoryBean vf = new LocalValidatorFactoryBean(); vf.afterPropertiesSet(); binder.setValidator(vf); @@ -3382,6 +3333,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl public static class DataClass { + @NotNull public final String param1; public final int param2; @@ -3408,4 +3360,37 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } } + @RestController + public static class ValidatedDataClassController { + + @InitBinder + public void initBinder(WebDataBinder binder) { + LocalValidatorFactoryBean vf = new LocalValidatorFactoryBean(); + vf.afterPropertiesSet(); + binder.setValidator(vf); + } + + @RequestMapping("/bind") + public String handle(@Valid DataClass data, BindingResult result) { + if (result.hasErrors()) { + return result.toString(); + } + return data.param1 + "-" + data.param2 + "-" + data.param3; + } + } + + @RestController + public static class OptionalDataClassController { + + @RequestMapping("/bind") + public String handle(Optional optionalData, BindingResult result) { + if (result.hasErrors()) { + assertNotNull(optionalData); + assertFalse(optionalData.isPresent()); + return result.toString(); + } + return optionalData.map(data -> data.param1 + "-" + data.param2 + "-" + data.param3).orElse(""); + } + } + }