diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestBodyNotValidException.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestBodyNotValidException.java new file mode 100644 index 00000000000..ab8482bdc15 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestBodyNotValidException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.mvc.method.annotation.support; + +import org.springframework.validation.Errors; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * Thrown by {@link RequestResponseBodyMethodProcessor} when an @{@link RequestBody} argument annotated with + * {@code @Valid} results in validation errors. + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +@SuppressWarnings("serial") +public class RequestBodyNotValidException extends RuntimeException { + + private final Errors errors; + + /** + * @param errors contains the results of validating an @{@link RequestBody} argument. + */ + public RequestBodyNotValidException(Errors errors) { + this.errors = errors; + } + + /** + * Returns an Errors instance with validation errors. + */ + public Errors getErrors() { + return errors; + } + +} 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 a1b968bae6a..4fcb9c73d55 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 @@ -17,21 +17,30 @@ package org.springframework.web.servlet.mvc.method.annotation.support; import java.io.IOException; +import java.lang.annotation.Annotation; import java.util.List; +import org.springframework.core.Conventions; import org.springframework.core.MethodParameter; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; import org.springframework.web.HttpMediaTypeNotAcceptableException; -import org.springframework.web.HttpMediaTypeNotSupportedException; +import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; /** - * Resolves method arguments annotated with @{@link RequestBody}. Handles return values from methods annotated with - * {@link ResponseBody}. + * Resolves method arguments annotated with @{@link RequestBody} and handles return values from methods + * annotated with {@link ResponseBody}. + * + *
An @{@link RequestBody} method argument will be validated if annotated with {@code @Valid}. A
+ * {@link Validator} instance can be configured globally in XML configuration with the Spring MVC namespace
+ * or in Java-based configuration with @{@link EnableWebMvc}.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
@@ -54,11 +63,37 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
- WebDataBinderFactory binderFactory)
- throws IOException, HttpMediaTypeNotSupportedException {
- return readWithMessageConverters(webRequest, parameter, parameter.getParameterType());
+ WebDataBinderFactory binderFactory) throws Exception {
+ Object arg = readWithMessageConverters(webRequest, parameter, parameter.getParameterType());
+ if (shouldValidate(parameter, arg)) {
+ String argName = Conventions.getVariableNameForParameter(parameter);
+ WebDataBinder binder = binderFactory.createBinder(webRequest, arg, argName);
+ binder.validate();
+ Errors errors = binder.getBindingResult();
+ if (errors.hasErrors()) {
+ throw new RequestBodyNotValidException(errors);
+ }
+ }
+ return arg;
}
+ /**
+ * Whether to validate the given @{@link RequestBody} method argument. The default implementation checks
+ * if the parameter is also annotated with {@code @Valid}.
+ * @param parameter the method argument for which to check if validation is needed
+ * @param argumentValue the method argument value (instantiated with a message converter)
+ * @return {@code true} if validation should be invoked, {@code false} otherwise.
+ */
+ protected boolean shouldValidate(MethodParameter parameter, Object argumentValue) {
+ 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,
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessorTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessorTests.java
index 376334ba1c6..b7d69f3f8d1 100644
--- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessorTests.java
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessorTests.java
@@ -16,15 +16,30 @@
package org.springframework.web.servlet.mvc.method.annotation.support;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.isA;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+
import org.junit.Before;
import org.junit.Test;
-
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
@@ -34,18 +49,19 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.validation.DataBinder;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
+import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerMapping;
-import static org.easymock.EasyMock.*;
-import static org.junit.Assert.*;
-
/**
* Test fixture with {@link RequestResponseBodyMethodProcessor} and mock {@link HttpMessageConverter}.
*
@@ -60,9 +76,9 @@ public class RequestResponseBodyMethodProcessorTests {
private MethodParameter paramRequestBodyString;
private MethodParameter paramInt;
+ private MethodParameter paramValidBean;
private MethodParameter returnTypeString;
private MethodParameter returnTypeInt;
-
private MethodParameter returnTypeStringProduces;
private ModelAndViewContainer mavContainer;
@@ -87,10 +103,9 @@ public class RequestResponseBodyMethodProcessorTests {
paramRequestBodyString = new MethodParameter(handle, 0);
paramInt = new MethodParameter(handle, 1);
returnTypeString = new MethodParameter(handle, -1);
-
returnTypeInt = new MethodParameter(getClass().getMethod("handle2"), -1);
-
returnTypeStringProduces = new MethodParameter(getClass().getMethod("handle3"), -1);
+ paramValidBean = new MethodParameter(getClass().getMethod("handle4", ValidBean.class), 0);
mavContainer = new ModelAndViewContainer();
@@ -119,7 +134,6 @@ public class RequestResponseBodyMethodProcessorTests {
String body = "Foo";
expect(messageConverter.canRead(String.class, contentType)).andReturn(true);
expect(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).andReturn(body);
-
replay(messageConverter);
Object result = processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, null);
@@ -129,12 +143,50 @@ public class RequestResponseBodyMethodProcessorTests {
verify(messageConverter);
}
+ @SuppressWarnings("unchecked")
+ @Test
+ public void resolveArgumentNotValid() throws Exception {
+ MediaType contentType = MediaType.TEXT_PLAIN;
+ servletRequest.addHeader("Content-Type", contentType.toString());
+
+ HttpMessageConverter