added SmartValidator interface with general support for validation hints; added custom @Valid annotation with support for JSR-303 validation groups; JSR-303 SpringValidatorAdapter and MVC data binding provide support for validation groups (SPR-6373)
This commit is contained in:
parent
4bfcb79ae3
commit
49a2aaf023
|
|
@ -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.
|
||||
* <p>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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <code>target</code> object, which must be
|
||||
* of a {@link Class} for which the {@link #supports(Class)} method
|
||||
* typically has (or would) return <code>true</code>.
|
||||
* <p>The supplied {@link Errors errors} instance can be used to report
|
||||
* any resulting validation errors.
|
||||
* <p><b>This variant of <code>validate</code> supports validation hints,
|
||||
* such as validation groups against a JSR-303 provider</b> (in this case,
|
||||
* the provided hint objects need to be annotation arguments of type Class).
|
||||
* <p>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 <code>null</code>)
|
||||
* @param errors contextual state about the validation process (never <code>null</code>)
|
||||
* @param validationHints one or more hint objects to be passed to the validation engine
|
||||
* @see ValidationUtils
|
||||
*/
|
||||
void validate(Object target, Errors errors, Object... validationHints);
|
||||
|
||||
}
|
||||
|
|
@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
* <p>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}.
|
||||
* <p>Other {@link org.springframework.validation.SmartValidator} implementations may
|
||||
* support class arguments in other ways as well.
|
||||
*/
|
||||
Class[] value() default {};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* Support classes for annotation-based constraint evaluation,
|
||||
* e.g. using a JSR-303 Bean Validation provider.
|
||||
*
|
||||
* <p>Provides an extended variant of JSR-303's <code>@Valid</code>,
|
||||
* supporting the specification of validation groups.
|
||||
*/
|
||||
package org.springframework.validation.annotation;
|
||||
|
|
@ -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 <code>javax.validator.Validator</code>
|
||||
|
|
@ -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<String> internalAnnotationAttributes = new HashSet<String>(3);
|
||||
|
||||
|
|
@ -85,8 +86,29 @@ public class SpringValidatorAdapter implements Validator, javax.validation.Valid
|
|||
}
|
||||
|
||||
public void validate(Object target, Errors errors) {
|
||||
Set<ConstraintViolation<Object>> result = this.targetValidator.validate(target);
|
||||
for (ConstraintViolation<Object> violation : result) {
|
||||
processConstraintViolations(this.targetValidator.validate(target), errors);
|
||||
}
|
||||
|
||||
public void validate(Object target, Errors errors, Object[] validationHints) {
|
||||
Set<Class> groups = new LinkedHashSet<Class>();
|
||||
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<ConstraintViolation<Object>> violations, Errors errors) {
|
||||
for (ConstraintViolation<Object> violation : violations) {
|
||||
String field = violation.getPropertyPath().toString();
|
||||
FieldError fieldError = errors.getFieldError(field);
|
||||
if (fieldError == null || !fieldError.isBindingFailure()) {
|
||||
|
|
|
|||
|
|
@ -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,12 +135,16 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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,57 +1921,56 @@ 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
|
||||
public static class ListEditorRegistrar implements PropertyEditorRegistrar {
|
||||
|
||||
@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());
|
||||
public void registerCustomEditors(PropertyEditorRegistry registry) {
|
||||
registry.registerCustomEditor(Set.class, new ListEditor());
|
||||
}
|
||||
catch (NestedServletException ex) {
|
||||
assertTrue(ex.getMessage().contains("not assignable from the actual model"));
|
||||
}
|
||||
|
||||
public static class ListEditor extends PropertyEditorSupport {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public String getAsText() {
|
||||
return StringUtils.collectionToDelimitedString((Collection<String>) getValue(), ";");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAsText(String text) throws IllegalArgumentException {
|
||||
Set<String> s = new LinkedHashSet<String>();
|
||||
for (String t : text.split(";")) {
|
||||
s.add(t);
|
||||
}
|
||||
setValue(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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<String, Object> model,
|
||||
@RequestParam Date date) {
|
||||
private ValidTestBean createTestBean(@RequestParam T defaultName, Map<String, Object> 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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
Loading…
Reference in New Issue