diff --git a/org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java b/org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java
index 0d39ff6eb7..448da57812 100644
--- a/org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java
+++ b/org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java
@@ -706,8 +706,22 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
* @see #getBindingResult()
*/
public void validate() {
+ this.validator.validate(getTarget(), getBindingResult());
+ }
+
+ /**
+ * Invoke the specified Validator, if any, with the given validation hints.
+ *
Note: Validation hints may get ignored by the actual target Validator.
+ * @param validationHints one or more hint objects to be passed to a {@link SmartValidator}
+ * @see #setValidator(Validator)
+ * @see SmartValidator#validate(Object, Errors, Object...)
+ */
+ public void validate(Object... validationHints) {
Validator validator = getValidator();
- if (validator != null) {
+ if (validator instanceof SmartValidator) {
+ ((SmartValidator) validator).validate(getTarget(), getBindingResult(), validationHints);
+ }
+ else if (validator != null) {
validator.validate(getTarget(), getBindingResult());
}
}
diff --git a/org.springframework.context/src/main/java/org/springframework/validation/SmartValidator.java b/org.springframework.context/src/main/java/org/springframework/validation/SmartValidator.java
new file mode 100644
index 0000000000..afb7fda442
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/validation/SmartValidator.java
@@ -0,0 +1,47 @@
+/*
+ * 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.validation;
+
+/**
+ * Extended variant of the {@link Validator} interface, adding support for
+ * validation 'hints'.
+ *
+ * @author Juergen Hoeller
+ * @since 3.1
+ */
+public interface SmartValidator extends Validator {
+
+ /**
+ * Validate the supplied target object, which must be
+ * of a {@link Class} for which the {@link #supports(Class)} method
+ * typically has (or would) return true.
+ *
The supplied {@link Errors errors} instance can be used to report
+ * any resulting validation errors.
+ *
This variant of validate supports validation hints,
+ * such as validation groups against a JSR-303 provider (in this case,
+ * the provided hint objects need to be annotation arguments of type Class).
+ *
Note: Validation hints may get ignored by the actual target Validator,
+ * in which case this method is supposed to be behave just like its regular
+ * {@link #validate(Object, Errors)} sibling.
+ * @param target the object that is to be validated (can be null)
+ * @param errors contextual state about the validation process (never null)
+ * @param validationHints one or more hint objects to be passed to the validation engine
+ * @see ValidationUtils
+ */
+ void validate(Object target, Errors errors, Object... validationHints);
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/validation/annotation/Valid.java b/org.springframework.context/src/main/java/org/springframework/validation/annotation/Valid.java
new file mode 100644
index 0000000000..b7807ab912
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/validation/annotation/Valid.java
@@ -0,0 +1,56 @@
+/*
+ * 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.validation.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Extended variant of JSR-303's {@link javax.validation.Valid},
+ * supporting the specification of validation groups. Designed for
+ * convenient use with Spring's JSR-303 support but not JSR-303 specific.
+ *
+ *
Can be used e.g. with Spring MVC handler methods arguments.
+ * Supported through {@link org.springframework.validation.SmartValidator}'s
+ * validation hint concept, with validation group classes acting as hint objects.
+ *
+ * @author Juergen Hoeller
+ * @since 3.1
+ * @see javax.validation.Validator#validate(Object, Class[])
+ * @see org.springframework.validation.SmartValidator#validate(Object, org.springframework.validation.Errors, Object...)
+ * @see org.springframework.validation.beanvalidation.SpringValidatorAdapter
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Valid {
+
+ /**
+ * Specify one or more validation groups to apply to the validation step
+ * kicked off by this annotation.
+ *
JSR-303 defines validation groups as custom annotations which an application declares
+ * for the sole purpose of using them as type-safe group arguments, as implemented in
+ * {@link org.springframework.validation.beanvalidation.SpringValidatorAdapter}.
+ *
Other {@link org.springframework.validation.SmartValidator} implementations may
+ * support class arguments in other ways as well.
+ */
+ Class[] value() default {};
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/validation/annotation/package-info.java b/org.springframework.context/src/main/java/org/springframework/validation/annotation/package-info.java
new file mode 100644
index 0000000000..f13f798131
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/validation/annotation/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * Support classes for annotation-based constraint evaluation,
+ * e.g. using a JSR-303 Bean Validation provider.
+ *
+ *
Provides an extended variant of JSR-303's @Valid,
+ * supporting the specification of validation groups.
+ */
+package org.springframework.validation.annotation;
diff --git a/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java b/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java
index a03c621631..490fffdebd 100644
--- a/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java
+++ b/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java
@@ -17,6 +17,7 @@
package org.springframework.validation.beanvalidation;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -33,7 +34,7 @@ import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
-import org.springframework.validation.Validator;
+import org.springframework.validation.SmartValidator;
/**
* Adapter that takes a JSR-303 javax.validator.Validator
@@ -46,7 +47,7 @@ import org.springframework.validation.Validator;
* @author Juergen Hoeller
* @since 3.0
*/
-public class SpringValidatorAdapter implements Validator, javax.validation.Validator {
+public class SpringValidatorAdapter implements SmartValidator, javax.validation.Validator {
private static final Set internalAnnotationAttributes = new HashSet(3);
@@ -85,8 +86,29 @@ public class SpringValidatorAdapter implements Validator, javax.validation.Valid
}
public void validate(Object target, Errors errors) {
- Set> result = this.targetValidator.validate(target);
- for (ConstraintViolation violation : result) {
+ processConstraintViolations(this.targetValidator.validate(target), errors);
+ }
+
+ public void validate(Object target, Errors errors, Object[] validationHints) {
+ Set groups = new LinkedHashSet();
+ if (validationHints != null) {
+ for (Object hint : validationHints) {
+ if (hint instanceof Class) {
+ groups.add((Class) hint);
+ }
+ }
+ }
+ processConstraintViolations(this.targetValidator.validate(target, groups.toArray(new Class[groups.size()])), errors);
+ }
+
+ /**
+ * Process the given JSR-303 ConstraintViolations, adding corresponding errors to
+ * the provided Spring {@link Errors} object.
+ * @param violations the JSR-303 ConstraintViolation results
+ * @param errors the Spring errors object to register to
+ */
+ protected void processConstraintViolations(Set> violations, Errors errors) {
+ for (ConstraintViolation violation : violations) {
String field = violation.getPropertyPath().toString();
FieldError fieldError = errors.getFieldError(field);
if (fieldError == null || !fieldError.isBindingFailure()) {
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java
index bd03b9d3c1..3e2c15f27e 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java
@@ -19,11 +19,11 @@ package org.springframework.web.servlet.mvc.method.annotation.support;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.List;
-
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.Assert;
@@ -106,14 +106,12 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
}
}
- public Object resolveArgument(MethodParameter parameter,
- ModelAndViewContainer mavContainer,
- NativeWebRequest request,
- WebDataBinderFactory binderFactory) throws Exception {
+ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
+ NativeWebRequest request, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
if (!isMultipartRequest(servletRequest)) {
- throw new MultipartException("The current request is not a multipart request.");
+ throw new MultipartException("The current request is not a multipart request");
}
MultipartHttpServletRequest multipartRequest =
@@ -137,15 +135,19 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
try {
HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, partName);
arg = readWithMessageConverters(inputMessage, parameter, parameter.getParameterType());
- if (isValidationApplicable(arg, parameter)) {
- WebDataBinder binder = binderFactory.createBinder(request, arg, partName);
- binder.validate();
- BindingResult bindingResult = binder.getBindingResult();
- if (bindingResult.hasErrors()) {
- throw new MethodArgumentNotValidException(parameter, bindingResult);
+ Annotation[] annotations = parameter.getParameterAnnotations();
+ for (Annotation annot : annotations) {
+ if ("Valid".equals(annot.annotationType().getSimpleName())) {
+ WebDataBinder binder = binderFactory.createBinder(request, arg, partName);
+ Object hints = AnnotationUtils.getValue(annot);
+ binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
+ BindingResult bindingResult = binder.getBindingResult();
+ if (bindingResult.hasErrors()) {
+ throw new MethodArgumentNotValidException(parameter, bindingResult);
+ }
}
}
- }
+ }
catch (MissingServletRequestPartException e) {
// handled below
arg = null;
@@ -153,7 +155,7 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
}
RequestPart annot = parameter.getParameterAnnotation(RequestPart.class);
- boolean isRequired = (annot != null) ? annot.required() : true;
+ boolean isRequired = (annot == null || annot.required());
if (arg == null && isRequired) {
throw new MissingServletRequestPartException(partName);
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java
index 74e07211bb..711b5ace4f 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java
@@ -22,6 +22,7 @@ import java.util.List;
import org.springframework.core.Conventions;
import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
@@ -64,43 +65,30 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
return returnType.getMethodAnnotation(ResponseBody.class) != null;
}
- public Object resolveArgument(MethodParameter parameter,
- ModelAndViewContainer mavContainer,
- NativeWebRequest webRequest,
- WebDataBinderFactory binderFactory) throws Exception {
+ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
+ NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
+
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getParameterType());
- if (isValidationApplicable(arg, parameter)) {
- String name = Conventions.getVariableNameForParameter(parameter);
- WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
- binder.validate();
- BindingResult bindingResult = binder.getBindingResult();
- if (bindingResult.hasErrors()) {
- throw new MethodArgumentNotValidException(parameter, bindingResult);
+ Annotation[] annotations = parameter.getParameterAnnotations();
+ for (Annotation annot : annotations) {
+ if ("Valid".equals(annot.annotationType().getSimpleName())) {
+ String name = Conventions.getVariableNameForParameter(parameter);
+ WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
+ Object hints = AnnotationUtils.getValue(annot);
+ binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
+ BindingResult bindingResult = binder.getBindingResult();
+ if (bindingResult.hasErrors()) {
+ throw new MethodArgumentNotValidException(parameter, bindingResult);
+ }
}
}
return arg;
}
- /**
- * Whether to validate the given {@code @RequestBody} method argument.
- * The default implementation looks for {@code @javax.validation.Valid}.
- * @param argument the resolved argument value
- * @param parameter the method argument
- */
- protected boolean isValidationApplicable(Object argument, MethodParameter parameter) {
- Annotation[] annotations = parameter.getParameterAnnotations();
- for (Annotation annot : annotations) {
- if ("Valid".equals(annot.annotationType().getSimpleName())) {
- return true;
- }
- }
- return false;
- }
-
- public void handleReturnValue(Object returnValue,
- MethodParameter returnType,
- ModelAndViewContainer mavContainer,
- NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException {
+ public void handleReturnValue(Object returnValue, MethodParameter returnType,
+ ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
+ throws IOException, HttpMediaTypeNotAcceptableException {
+
mavContainer.setRequestHandled(true);
if (returnValue != null) {
writeWithMessageConverters(returnValue, returnType, webRequest);
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
index f996a5a822..a0ae6f79b9 100644
--- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
@@ -16,24 +16,18 @@
package org.springframework.web.servlet.config;
-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 java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.Locale;
-
import javax.servlet.RequestDispatcher;
-import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.junit.Before;
import org.junit.Test;
+
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
@@ -53,6 +47,7 @@ import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
+import org.springframework.validation.annotation.Valid;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@@ -75,6 +70,8 @@ import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
+import static org.junit.Assert.*;
+
/**
* @author Keith Donald
* @author Arjen Poutsma
@@ -453,12 +450,8 @@ public class MvcNamespaceTests {
private boolean recordedValidationError;
@RequestMapping
- public void testBind(@RequestParam @DateTimeFormat(iso=ISO.DATE) Date date, @Valid TestBean bean, BindingResult result) {
- if (result.getErrorCount() == 1) {
- this.recordedValidationError = true;
- } else {
- this.recordedValidationError = false;
- }
+ public void testBind(@RequestParam @DateTimeFormat(iso=ISO.DATE) Date date, @Valid(MyGroup.class) TestBean bean, BindingResult result) {
+ this.recordedValidationError = (result.getErrorCount() == 1);
}
}
@@ -475,9 +468,13 @@ public class MvcNamespaceTests {
}
}
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface MyGroup {
+ }
+
private static class TestBean {
- @NotNull
+ @NotNull(groups=MyGroup.class)
private String field;
@SuppressWarnings("unused")
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java
index cc27df192c..48799de180 100644
--- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java
@@ -16,14 +16,6 @@
package org.springframework.web.servlet.mvc.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;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.io.Serializable;
@@ -38,16 +30,17 @@ import java.security.Principal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
-
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@@ -55,11 +48,11 @@ import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
-import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.xml.bind.annotation.XmlRootElement;
import org.junit.Test;
+
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
@@ -67,6 +60,8 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.DerivedTestBean;
import org.springframework.beans.GenericBean;
import org.springframework.beans.ITestBean;
+import org.springframework.beans.PropertyEditorRegistrar;
+import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.beans.TestBean;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
@@ -108,6 +103,7 @@ import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
+import org.springframework.validation.annotation.Valid;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.CookieValue;
@@ -142,6 +138,8 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.util.NestedServletException;
+import static org.junit.Assert.*;
+
/**
* @author Juergen Hoeller
* @author Sam Brannen
@@ -1718,6 +1716,61 @@ public class ServletAnnotationControllerTests {
assertEquals("templatePath", response.getContentAsString());
}
+ @Test
+ public void testMatchWithoutMethodLevelPath() throws Exception {
+ initServlet(NoPathGetAndM2PostController.class);
+
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/t1/m2");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ servlet.service(request, response);
+ assertEquals(405, response.getStatus());
+ }
+
+ // SPR-8536
+
+ @Test
+ public void testHeadersCondition() throws Exception {
+ initServlet(HeadersConditionController.class);
+
+ // No "Accept" header
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ servlet.service(request, response);
+
+ assertEquals(200, response.getStatus());
+ assertEquals("home", response.getForwardedUrl());
+
+ // Accept "*/*"
+ request = new MockHttpServletRequest("GET", "/");
+ request.addHeader("Accept", "*/*");
+ response = new MockHttpServletResponse();
+ servlet.service(request, response);
+
+ assertEquals(200, response.getStatus());
+ assertEquals("home", response.getForwardedUrl());
+
+ // Accept "application/json"
+ request = new MockHttpServletRequest("GET", "/");
+ request.addHeader("Accept", "application/json");
+ response = new MockHttpServletResponse();
+ servlet.service(request, response);
+
+ assertEquals(200, response.getStatus());
+ assertEquals("application/json", response.getHeader("Content-Type"));
+ assertEquals("homeJson", response.getContentAsString());
+ }
+
+ @Test
+ public void redirectAttribute() throws Exception {
+ initServlet(RedirectAttributesController.class);
+ try {
+ servlet.service(new MockHttpServletRequest("GET", "/"), new MockHttpServletResponse());
+ }
+ catch (NestedServletException ex) {
+ assertTrue(ex.getMessage().contains("not assignable from the actual model"));
+ }
+ }
+
/*
* See SPR-6021
*/
@@ -1868,60 +1921,59 @@ public class ServletAnnotationControllerTests {
}
@Test
- public void testMatchWithoutMethodLevelPath() throws Exception {
- initServlet(NoPathGetAndM2PostController.class);
+ public void parameterCsvAsIntegerSetWithCustomSeparator() throws Exception {
+ servlet = new DispatcherServlet() {
+ @Override
+ protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
+ GenericWebApplicationContext wac = new GenericWebApplicationContext();
+ wac.registerBeanDefinition("controller", new RootBeanDefinition(CsvController.class));
+ RootBeanDefinition csDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class);
+ RootBeanDefinition wbiDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
+ wbiDef.getPropertyValues().add("conversionService", csDef);
+ wbiDef.getPropertyValues().add("propertyEditorRegistrars", new RootBeanDefinition(ListEditorRegistrar.class));
+ RootBeanDefinition adapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class);
+ adapterDef.getPropertyValues().add("webBindingInitializer", wbiDef);
+ wac.registerBeanDefinition("handlerAdapter", adapterDef);
+ wac.refresh();
+ return wac;
+ }
+ };
+ servlet.init(new MockServletConfig());
- MockHttpServletRequest request = new MockHttpServletRequest("GET", "/t1/m2");
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setRequestURI("/integerSet");
+ request.setMethod("POST");
+ request.addParameter("content", "1;2");
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
- assertEquals(405, response.getStatus());
+ assertEquals("1-2", response.getContentAsString());
}
-
- // SPR-8536
- @Test
- public void testHeadersCondition() throws Exception {
- initServlet(HeadersConditionController.class);
+ public static class ListEditorRegistrar implements PropertyEditorRegistrar {
- // No "Accept" header
- MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
- MockHttpServletResponse response = new MockHttpServletResponse();
- servlet.service(request, response);
-
- assertEquals(200, response.getStatus());
- assertEquals("home", response.getForwardedUrl());
-
- // Accept "*/*"
- request = new MockHttpServletRequest("GET", "/");
- request.addHeader("Accept", "*/*");
- response = new MockHttpServletResponse();
- servlet.service(request, response);
-
- assertEquals(200, response.getStatus());
- assertEquals("home", response.getForwardedUrl());
-
- // Accept "application/json"
- request = new MockHttpServletRequest("GET", "/");
- request.addHeader("Accept", "application/json");
- response = new MockHttpServletResponse();
- servlet.service(request, response);
-
- assertEquals(200, response.getStatus());
- assertEquals("application/json", response.getHeader("Content-Type"));
- assertEquals("homeJson", response.getContentAsString());
- }
-
- @Test
- public void redirectAttribute() throws Exception {
- initServlet(RedirectAttributesController.class);
- try {
- servlet.service(new MockHttpServletRequest("GET", "/"), new MockHttpServletResponse());
- }
- catch (NestedServletException ex) {
- assertTrue(ex.getMessage().contains("not assignable from the actual model"));
+ public void registerCustomEditors(PropertyEditorRegistry registry) {
+ registry.registerCustomEditor(Set.class, new ListEditor());
}
}
-
+
+ public static class ListEditor extends PropertyEditorSupport {
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public String getAsText() {
+ return StringUtils.collectionToDelimitedString((Collection) getValue(), ";");
+ }
+
+ @Override
+ public void setAsText(String text) throws IllegalArgumentException {
+ Set s = new LinkedHashSet();
+ for (String t : text.split(";")) {
+ s.add(t);
+ }
+ setValue(s);
+ }
+ }
+
/*
* Controllers
@@ -2226,7 +2278,7 @@ public class ServletAnnotationControllerTests {
public static class ValidTestBean extends TestBean {
- @NotNull
+ @NotNull(groups = MyGroup.class)
private String validCountry;
public void setValidCountry(String validCountry) {
@@ -2264,9 +2316,7 @@ public class ServletAnnotationControllerTests {
@SuppressWarnings("unused")
@ModelAttribute("myCommand")
- private ValidTestBean createTestBean(@RequestParam T defaultName,
- Map model,
- @RequestParam Date date) {
+ private 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());
@@ -2275,7 +2325,7 @@ public class ServletAnnotationControllerTests {
@Override
@RequestMapping("/myPath.do")
- public String myHandle(@ModelAttribute("myCommand") @Valid TestBean tb, BindingResult errors, ModelMap model) {
+ public String myHandle(@ModelAttribute("myCommand") @Valid(MyGroup.class) TestBean tb, BindingResult errors, ModelMap model) {
if (!errors.hasFieldErrors("validCountry")) {
throw new IllegalStateException("Declarative validation not applied");
}
@@ -2333,7 +2383,7 @@ public class ServletAnnotationControllerTests {
@Override
@RequestMapping("/myPath.do")
- public String myHandle(@ModelAttribute("myCommand") @Valid TestBean tb, BindingResult errors, ModelMap model) {
+ public String myHandle(@ModelAttribute("myCommand") @Valid(MyGroup.class) TestBean tb, BindingResult errors, ModelMap model) {
if (!errors.hasFieldErrors("sex")) {
throw new IllegalStateException("requiredFields not applied");
}
@@ -2360,6 +2410,10 @@ public class ServletAnnotationControllerTests {
}
}
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface MyGroup {
+ }
+
private static class MyWebBindingInitializer implements WebBindingInitializer {
public void initBinder(WebDataBinder binder, WebRequest request) {
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java
index fb4c82772f..42bc358a03 100644
--- a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java
@@ -34,6 +34,7 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.BridgeMethodResolver;
@@ -251,6 +252,7 @@ public class HandlerMethodInvoker {
boolean required = false;
String defaultValue = null;
boolean validate = false;
+ Object[] validationHints = null;
int annotationsFound = 0;
Annotation[] paramAnns = methodParam.getParameterAnnotations();
@@ -295,6 +297,8 @@ public class HandlerMethodInvoker {
}
else if ("Valid".equals(paramAnn.annotationType().getSimpleName())) {
validate = true;
+ Object value = AnnotationUtils.getValue(paramAnn);
+ validationHints = (value instanceof Object[] ? (Object[]) value : new Object[] {value});
}
}
@@ -360,7 +364,7 @@ public class HandlerMethodInvoker {
resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
if (binder.getTarget() != null) {
- doBind(binder, webRequest, validate, !assignBindingResult);
+ doBind(binder, webRequest, validate, validationHints, !assignBindingResult);
}
args[i] = binder.getTarget();
if (assignBindingResult) {
@@ -803,12 +807,12 @@ public class HandlerMethodInvoker {
return new WebRequestDataBinder(target, objectName);
}
- private void doBind(WebDataBinder binder, NativeWebRequest webRequest, boolean validate, boolean failOnErrors)
- throws Exception {
+ private void doBind(WebDataBinder binder, NativeWebRequest webRequest, boolean validate,
+ Object[] validationHints, boolean failOnErrors) throws Exception {
doBind(binder, webRequest);
if (validate) {
- binder.validate();
+ binder.validate(validationHints);
}
if (failOnErrors && binder.getBindingResult().hasErrors()) {
throw new BindException(binder.getBindingResult());
diff --git a/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessorTests.java b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessorTests.java
index fdca12571d..ad530bdcd1 100644
--- a/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessorTests.java
+++ b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessorTests.java
@@ -140,16 +140,6 @@ public class ModelAttributeMethodProcessorTests {
assertTrue(processor.supportsReturnType(returnParamNonSimpleType));
}
- @Test
- public void validationApplicable() throws Exception {
- assertTrue(processor.isValidationApplicable(null, paramNamedValidModelAttr));
- }
-
- @Test
- public void validationNotApplicable() throws Exception {
- assertFalse(processor.isValidationApplicable(null, paramNonSimpleType));
- }
-
@Test
public void bindExceptionRequired() throws Exception {
assertTrue(processor.isBindExceptionRequired(null, paramNonSimpleType));