From d3eda09c01c151f5e08feade1df599cb9939f177 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 10 May 2013 12:31:00 -0400 Subject: [PATCH] Allow treating empty @RequestParam as missing value If type conversion turns an empty request parameter value (i.e. "") to null, we should treat it as a missing value. By default the ConversionService doesn't change empty strings and therefore one must explicitly convert them to null for example by registering a StringTrimmerEditor with emptyAsNull=true. Issue: SPR-10402 --- ...tractNamedValueMethodArgumentResolver.java | 7 ++++ ...questParamMethodArgumentResolverTests.java | 32 +++++++++++++++---- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java index a5154792101..64cc3681e6b 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java @@ -74,6 +74,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle this.expressionContext = (beanFactory != null) ? new BeanExpressionContext(beanFactory, new RequestScope()) : null; } + @Override public final Object resolveArgument( MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) @@ -96,11 +97,17 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle arg = resolveDefaultValue(namedValueInfo.defaultValue); } + boolean emptyArgValue = "".equals(arg); + if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); arg = binder.convertIfNecessary(arg, paramType, parameter); } + if (emptyArgValue && (arg == null)) { + handleMissingValue(namedValueInfo.name, parameter); + } + handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg; diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java index bda24d4892a..d678d5b84c0 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java @@ -16,13 +16,6 @@ package org.springframework.web.method.annotation; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; @@ -32,6 +25,7 @@ import javax.servlet.http.Part; import org.junit.Before; import org.junit.Test; +import org.springframework.beans.propertyeditors.StringTrimmerEditor; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; @@ -41,13 +35,20 @@ import org.springframework.mock.web.test.MockMultipartFile; import org.springframework.mock.web.test.MockMultipartHttpServletRequest; import org.springframework.mock.web.test.MockPart; import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.bind.support.WebRequestDataBinder; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.multipart.MultipartException; import org.springframework.web.multipart.MultipartFile; +import static org.junit.Assert.*; +import static org.mockito.BDDMockito.*; +import static org.mockito.Mockito.*; + /** * Test fixture with {@link org.springframework.web.method.annotation.RequestParamMethodArgumentResolver}. * @@ -242,6 +243,23 @@ public class RequestParamMethodArgumentResolverTests { fail("Expected exception"); } + // SPR-10402 + + @Test(expected = MissingServletRequestParameterException.class) + public void missingRequestParamEmptyValueConvertedToNull() throws Exception { + + WebDataBinder binder = new WebRequestDataBinder(null); + binder.registerCustomEditor(String.class, new StringTrimmerEditor(true)); + + WebDataBinderFactory binderFactory = mock(WebDataBinderFactory.class); + given(binderFactory.createBinder(webRequest, null, "stringNotAnnot")).willReturn(binder); + + this.request.addParameter("stringNotAnnot", ""); + + resolver.resolveArgument(paramStringNotAnnot, null, webRequest, binderFactory); + fail("Expected exception"); + } + @Test public void resolveSimpleTypeParam() throws Exception { request.setParameter("stringNotAnnot", "plainValue");