Allow Errors after @RequestBody and @RequestPart
An @RequestBody or an @RequestPart argument can now be followed by an Errors/BindingResult argument making it possible to handle validation errors (as a result of an @Valid annotation) locally within the @RequestMapping method. Issue: SPR-7114
This commit is contained in:
parent
06d95915a0
commit
af1561634c
|
|
@ -100,9 +100,8 @@ import java.util.concurrent.Callable;
|
|||
* converted to the declared method argument type using
|
||||
* {@linkplain org.springframework.http.converter.HttpMessageConverter message
|
||||
* converters}. Such parameters may optionally be annotated with {@code @Valid}
|
||||
* but do not support access to validation results through a
|
||||
* {@link org.springframework.validation.Errors} /
|
||||
* {@link org.springframework.validation.BindingResult} argument.
|
||||
* and also support access to validation results through an
|
||||
* {@link org.springframework.validation.Errors} argument.
|
||||
* Instead a {@link org.springframework.web.servlet.mvc.method.annotation.MethodArgumentNotValidException}
|
||||
* exception is raised.
|
||||
* <li>{@link RequestPart @RequestPart} annotated parameters
|
||||
|
|
@ -112,9 +111,8 @@ import java.util.concurrent.Callable;
|
|||
* converted to the declared method argument type using
|
||||
* {@linkplain org.springframework.http.converter.HttpMessageConverter message
|
||||
* converters}. Such parameters may optionally be annotated with {@code @Valid}
|
||||
* but do not support access to validation results through a
|
||||
* {@link org.springframework.validation.Errors} /
|
||||
* {@link org.springframework.validation.BindingResult} argument.
|
||||
* and support access to validation results through a
|
||||
* {@link org.springframework.validation.Errors} argument.
|
||||
* Instead a {@link org.springframework.web.servlet.mvc.method.annotation.MethodArgumentNotValidException}
|
||||
* exception is raised.
|
||||
* <li>{@link org.springframework.http.HttpEntity HttpEntity<?>} parameters
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ public class ErrorsMethodArgumentResolver implements HandlerMethodArgumentResolv
|
|||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
"An Errors/BindingResult argument is expected to be immediately after the model attribute " +
|
||||
"argument in the controller method signature: " + parameter.getMethod());
|
||||
"An Errors/BindingResult argument is expected to be declared immediately after the model attribute, " +
|
||||
"the @RequestBody or the @RequestPart arguments to which they apply: " + parameter.getMethod());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,16 +157,16 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
|
|||
if (annot.annotationType().getSimpleName().startsWith("Valid")) {
|
||||
Object hints = AnnotationUtils.getValue(annot);
|
||||
binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to raise a {@link BindException} on bind or validation errors.
|
||||
* The default implementation returns {@code true} if the next method
|
||||
* argument is not of type {@link Errors}.
|
||||
* Whether to raise a {@link BindException} on validation errors.
|
||||
* @param binder the data binder used to perform data binding
|
||||
* @param parameter the method argument
|
||||
* @return {@code true} if the next method argument is not of type {@link Errors}.
|
||||
*/
|
||||
protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
|
||||
int i = parameter.getParameterIndex();
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ import org.springframework.web.context.request.NativeWebRequest;
|
|||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
|
||||
/**
|
||||
* A base class for resolving method argument values by reading from the body of a request
|
||||
* with {@link HttpMessageConverter}s.
|
||||
* A base class for resolving method argument values by reading from the body of
|
||||
* a request with {@link HttpMessageConverter}s.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
|
|
@ -48,9 +48,9 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
|||
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
|
||||
protected final List<HttpMessageConverter<?>> messageConverters;
|
||||
|
||||
|
||||
protected final List<MediaType> allSupportedMediaTypes;
|
||||
|
||||
public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters) {
|
||||
|
|
@ -60,8 +60,8 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the media types supported by all provided message converters preserving their ordering and
|
||||
* further sorting by specificity via {@link MediaType#sortBySpecificity(List)}.
|
||||
* Return the media types supported by all provided message converters sorted
|
||||
* by specificity via {@link MediaType#sortBySpecificity(List)}.
|
||||
*/
|
||||
private static List<MediaType> getAllSupportedMediaTypes(List<HttpMessageConverter<?>> messageConverters) {
|
||||
Set<MediaType> allSupportedMediaTypes = new LinkedHashSet<MediaType>();
|
||||
|
|
@ -72,11 +72,12 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
|
|||
MediaType.sortBySpecificity(result);
|
||||
return Collections.unmodifiableList(result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the method argument value of the expected parameter type by reading from the given request.
|
||||
*
|
||||
* @param <T> the expected type of the argument value to be created
|
||||
* Creates the method argument value of the expected parameter type by
|
||||
* reading from the given request.
|
||||
*
|
||||
* @param <T> the expected type of the argument value to be created
|
||||
* @param webRequest the current request
|
||||
* @param methodParam the method argument
|
||||
* @param paramType the type of the argument value to be created
|
||||
|
|
@ -86,15 +87,16 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
|
|||
*/
|
||||
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam, Class<T> paramType) throws IOException,
|
||||
HttpMediaTypeNotSupportedException {
|
||||
|
||||
HttpInputMessage inputMessage = createInputMessage(webRequest);
|
||||
return readWithMessageConverters(inputMessage, methodParam, paramType);
|
||||
}
|
||||
|
||||
HttpInputMessage inputMessage = createInputMessage(webRequest);
|
||||
return readWithMessageConverters(inputMessage, methodParam, paramType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the method argument value of the expected parameter type by reading from the given HttpInputMessage.
|
||||
*
|
||||
* @param <T> the expected type of the argument value to be created
|
||||
* Creates the method argument value of the expected parameter type by reading
|
||||
* from the given HttpInputMessage.
|
||||
*
|
||||
* @param <T> the expected type of the argument value to be created
|
||||
* @param inputMessage the HTTP input message representing the current request
|
||||
* @param methodParam the method argument
|
||||
* @param paramType the type of the argument value to be created
|
||||
|
|
@ -103,14 +105,14 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
|
|||
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter methodParam, Class<T> paramType) throws IOException,
|
||||
HttpMediaTypeNotSupportedException {
|
||||
|
||||
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter methodParam,
|
||||
Class<T> paramType) throws IOException, HttpMediaTypeNotSupportedException {
|
||||
|
||||
MediaType contentType = inputMessage.getHeaders().getContentType();
|
||||
if (contentType == null) {
|
||||
contentType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
|
||||
|
||||
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
|
||||
if (messageConverter.canRead(paramType, contentType)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
|
@ -120,7 +122,7 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
|
|||
return ((HttpMessageConverter<T>) messageConverter).read(paramType, inputMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import org.springframework.http.HttpInputMessage;
|
|||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
|
@ -49,26 +50,26 @@ import org.springframework.web.util.WebUtils;
|
|||
* Resolves the following method arguments:
|
||||
* <ul>
|
||||
* <li>Annotated with {@code @RequestPart}
|
||||
* <li>Of type {@link MultipartFile} in conjunction with Spring's
|
||||
* <li>Of type {@link MultipartFile} in conjunction with Spring's
|
||||
* {@link MultipartResolver} abstraction
|
||||
* <li>Of type {@code javax.servlet.http.Part} in conjunction with
|
||||
* <li>Of type {@code javax.servlet.http.Part} in conjunction with
|
||||
* Servlet 3.0 multipart requests
|
||||
* </ul>
|
||||
*
|
||||
* <p>When a parameter is annotated with {@code @RequestPart} the content of the
|
||||
* part is passed through an {@link HttpMessageConverter} to resolve the method
|
||||
* argument with the 'Content-Type' of the request part in mind. This is
|
||||
*
|
||||
* <p>When a parameter is annotated with {@code @RequestPart} the content of the
|
||||
* part is passed through an {@link HttpMessageConverter} to resolve the method
|
||||
* argument with the 'Content-Type' of the request part in mind. This is
|
||||
* analogous to what @{@link RequestBody} does to resolve an argument based on
|
||||
* the content of a regular request.
|
||||
*
|
||||
* <p>When a parameter is not annotated or the name of the part is not specified,
|
||||
*
|
||||
* <p>When a parameter is not annotated or the name of the part is not specified,
|
||||
* it is derived from the name of the method argument.
|
||||
*
|
||||
* <p>Automatic validation may be applied if the argument is annotated with
|
||||
* {@code @javax.validation.Valid}. In case of validation failure, a
|
||||
*
|
||||
* <p>Automatic validation may be applied if the argument is annotated with
|
||||
* {@code @javax.validation.Valid}. In case of validation failure, a
|
||||
* {@link MethodArgumentNotValidException} is raised and a 400 response status
|
||||
* code returned if {@link DefaultHandlerExceptionResolver} is configured.
|
||||
*
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
|
|
@ -111,8 +112,8 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
|
|||
|
||||
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
|
||||
assertIsMultipartRequest(servletRequest);
|
||||
|
||||
MultipartHttpServletRequest multipartRequest =
|
||||
|
||||
MultipartHttpServletRequest multipartRequest =
|
||||
WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
|
||||
|
||||
String partName = getPartName(parameter);
|
||||
|
|
@ -133,20 +134,11 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
|
|||
try {
|
||||
HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, partName);
|
||||
arg = readWithMessageConverters(inputMessage, parameter, parameter.getParameterType());
|
||||
WebDataBinder binder = binderFactory.createBinder(request, arg, partName);
|
||||
if (arg != null) {
|
||||
Annotation[] annotations = parameter.getParameterAnnotations();
|
||||
for (Annotation annot : annotations) {
|
||||
if (annot.annotationType().getSimpleName().startsWith("Valid")) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
validate(binder, parameter);
|
||||
}
|
||||
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + partName, binder.getBindingResult());
|
||||
}
|
||||
catch (MissingServletRequestPartException ex) {
|
||||
// handled below
|
||||
|
|
@ -160,7 +152,7 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
|
|||
if (arg == null && isRequired) {
|
||||
throw new MissingServletRequestPartException(partName);
|
||||
}
|
||||
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
|
|
@ -170,7 +162,7 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
|
|||
throw new MultipartException("The current request is not a multipart request");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getPartName(MethodParameter parameter) {
|
||||
RequestPart annot = parameter.getParameterAnnotation(RequestPart.class);
|
||||
String partName = (annot != null) ? annot.value() : "";
|
||||
|
|
@ -181,7 +173,7 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
|
|||
}
|
||||
return partName;
|
||||
}
|
||||
|
||||
|
||||
private boolean isMultipartFileCollection(MethodParameter parameter) {
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
if (Collection.class.equals(paramType) || List.class.isAssignableFrom(paramType)){
|
||||
|
|
@ -193,4 +185,35 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
|
|||
return false;
|
||||
}
|
||||
|
||||
private void validate(WebDataBinder binder, MethodParameter parameter) throws MethodArgumentNotValidException {
|
||||
|
||||
Annotation[] annotations = parameter.getParameterAnnotations();
|
||||
for (Annotation annot : annotations) {
|
||||
if (annot.annotationType().getSimpleName().startsWith("Valid")) {
|
||||
Object hints = AnnotationUtils.getValue(annot);
|
||||
binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
|
||||
BindingResult bindingResult = binder.getBindingResult();
|
||||
if (bindingResult.hasErrors()) {
|
||||
if (isBindExceptionRequired(binder, parameter)) {
|
||||
throw new MethodArgumentNotValidException(parameter, bindingResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to raise a {@link MethodArgumentNotValidException} on validation errors.
|
||||
* @param binder the data binder used to perform data binding
|
||||
* @param parameter the method argument
|
||||
* @return {@code true} if the next method argument is not of type {@link Errors}.
|
||||
*/
|
||||
private boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
|
||||
int i = parameter.getParameterIndex();
|
||||
Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
|
||||
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
|
||||
|
||||
return !hasBindingResult;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import org.springframework.http.HttpInputMessage;
|
|||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
|
|
@ -85,33 +86,52 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
|
|||
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
|
||||
|
||||
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getParameterType());
|
||||
validate(parameter, webRequest, binderFactory, arg);
|
||||
return arg;
|
||||
Object argument = readWithMessageConverters(webRequest, parameter, parameter.getParameterType());
|
||||
|
||||
String name = Conventions.getVariableNameForParameter(parameter);
|
||||
WebDataBinder binder = binderFactory.createBinder(webRequest, argument, name);
|
||||
|
||||
if (argument != null) {
|
||||
validate(binder, parameter);
|
||||
}
|
||||
|
||||
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
|
||||
|
||||
return argument;
|
||||
}
|
||||
|
||||
private void validate(MethodParameter parameter, NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory, Object arg) throws Exception, MethodArgumentNotValidException {
|
||||
private void validate(WebDataBinder binder, MethodParameter parameter) throws Exception, MethodArgumentNotValidException {
|
||||
|
||||
if (arg == null) {
|
||||
return;
|
||||
}
|
||||
Annotation[] annotations = parameter.getParameterAnnotations();
|
||||
for (Annotation annot : annotations) {
|
||||
if (!annot.annotationType().getSimpleName().startsWith("Valid")) {
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
if (annot.annotationType().getSimpleName().startsWith("Valid")) {
|
||||
Object hints = AnnotationUtils.getValue(annot);
|
||||
binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
|
||||
BindingResult bindingResult = binder.getBindingResult();
|
||||
if (bindingResult.hasErrors()) {
|
||||
if (isBindExceptionRequired(binder, parameter)) {
|
||||
throw new MethodArgumentNotValidException(parameter, bindingResult);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to raise a {@link MethodArgumentNotValidException} on validation errors.
|
||||
* @param binder the data binder used to perform data binding
|
||||
* @param parameter the method argument
|
||||
* @return {@code true} if the next method argument is not of type {@link Errors}.
|
||||
*/
|
||||
private boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
|
||||
int i = parameter.getParameterIndex();
|
||||
Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
|
||||
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
|
||||
|
||||
return !hasBindingResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage,
|
||||
MethodParameter methodParam, Class<T> paramType) throws IOException, HttpMediaTypeNotSupportedException {
|
||||
|
|
|
|||
|
|
@ -89,29 +89,29 @@ import org.springframework.web.util.UriComponentsBuilder;
|
|||
|
||||
/**
|
||||
* A test fixture with a controller with all supported method signature styles
|
||||
* and arguments. A convenient place to test or confirm a problem with a
|
||||
* specific argument or return value type.
|
||||
* and arguments. A convenient place to test or confirm a problem with a
|
||||
* specific argument or return value type.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*
|
||||
*
|
||||
* @see HandlerMethodAnnotationDetectionTests
|
||||
* @see ServletAnnotationControllerHandlerMethodTests
|
||||
*/
|
||||
public class RequestMappingHandlerAdapterIntegrationTests {
|
||||
|
||||
private final Object handler = new Handler();
|
||||
|
||||
|
||||
private RequestMappingHandlerAdapter handlerAdapter;
|
||||
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
ConfigurableWebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer();
|
||||
bindingInitializer.setValidator(new StubValidator());
|
||||
|
||||
|
||||
List<HandlerMethodArgumentResolver> customResolvers = new ArrayList<HandlerMethodArgumentResolver>();
|
||||
customResolvers.add(new ServletWebArgumentResolverAdapter(new ColorArgumentResolver()));
|
||||
|
||||
|
|
@ -127,11 +127,11 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||
|
||||
request = new MockHttpServletRequest();
|
||||
response = new MockHttpServletResponse();
|
||||
|
||||
|
||||
// Expose request to the current thread (for SpEL expressions)
|
||||
RequestContextHolder.setRequestAttributes(new ServletWebRequest(request));
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
public void teardown() {
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
|
|
@ -139,7 +139,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||
|
||||
@Test
|
||||
public void handle() throws Exception {
|
||||
|
||||
|
||||
Class<?>[] parameterTypes = new Class<?>[] { int.class, String.class, String.class, String.class, Map.class,
|
||||
Date.class, Map.class, String.class, String.class, TestBean.class, Errors.class, TestBean.class,
|
||||
Color.class, HttpServletRequest.class, HttpServletResponse.class, User.class, OtherUser.class,
|
||||
|
|
@ -186,7 +186,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||
assertEquals("paramByConventionValue", map.get("paramByConvention"));
|
||||
|
||||
assertEquals("/contextPath", model.get("value"));
|
||||
|
||||
|
||||
TestBean modelAttr = (TestBean) model.get("modelAttr");
|
||||
assertEquals(25, modelAttr.getAge());
|
||||
assertEquals("Set by model method [modelAttr]", modelAttr.getName());
|
||||
|
|
@ -208,13 +208,13 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||
assertTrue(model.get("customArg") instanceof Color);
|
||||
assertEquals(User.class, model.get("user").getClass());
|
||||
assertEquals(OtherUser.class, model.get("otherUser").getClass());
|
||||
|
||||
|
||||
assertEquals(new URI("http://localhost/contextPath/main/path"), model.get("url"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void handleRequestBody() throws Exception {
|
||||
|
||||
|
||||
Class<?>[] parameterTypes = new Class<?>[] { byte[].class };
|
||||
|
||||
request.addHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
|
|
@ -229,6 +229,23 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||
assertEquals(HttpStatus.ACCEPTED.value(), response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleAndValidateRequestBody() throws Exception {
|
||||
|
||||
Class<?>[] parameterTypes = new Class<?>[] { TestBean.class, Errors.class };
|
||||
|
||||
request.addHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
request.setContent("Hello Server".getBytes("UTF-8"));
|
||||
|
||||
HandlerMethod handlerMethod = handlerMethod("handleAndValidateRequestBody", parameterTypes);
|
||||
|
||||
ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod);
|
||||
|
||||
assertNull(mav);
|
||||
assertEquals("Error count [1]", new String(response.getContentAsByteArray(), "UTF-8"));
|
||||
assertEquals(HttpStatus.ACCEPTED.value(), response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleHttpEntity() throws Exception {
|
||||
|
||||
|
|
@ -254,11 +271,23 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||
|
||||
HandlerMethod handlerMethod = handlerMethod("handleRequestPart", String.class, Model.class);
|
||||
ModelAndView mav = handlerAdapter.handle(multipartRequest, response, handlerMethod);
|
||||
|
||||
|
||||
assertNotNull(mav);
|
||||
assertEquals("content", mav.getModelMap().get("requestPart"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void handleAndValidateRequestPart() throws Exception {
|
||||
MockMultipartHttpServletRequest multipartRequest = new MockMultipartHttpServletRequest();
|
||||
multipartRequest.addFile(new MockMultipartFile("requestPart", "", "text/plain", "content".getBytes("UTF-8")));
|
||||
|
||||
HandlerMethod handlerMethod = handlerMethod("handleAndValidateRequestPart", String.class, Errors.class, Model.class);
|
||||
ModelAndView mav = handlerAdapter.handle(multipartRequest, response, handlerMethod);
|
||||
|
||||
assertNotNull(mav);
|
||||
assertEquals(1, mav.getModelMap().get("error count"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleAndCompleteSession() throws Exception {
|
||||
HandlerMethod handlerMethod = handlerMethod("handleAndCompleteSession", SessionStatus.class);
|
||||
|
|
@ -266,7 +295,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||
|
||||
assertFalse(request.getSession().getAttributeNames().hasMoreElements());
|
||||
}
|
||||
|
||||
|
||||
private HandlerMethod handlerMethod(String methodName, Class<?>... paramTypes) throws Exception {
|
||||
Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes);
|
||||
return new InvocableHandlerMethod(handler, method);
|
||||
|
|
@ -277,7 +306,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||
private static class Handler {
|
||||
|
||||
@InitBinder("dateParam")
|
||||
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String datePattern) {
|
||||
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String datePattern) {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern);
|
||||
dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
|
||||
}
|
||||
|
|
@ -291,7 +320,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||
modelAttr = new TestBean();
|
||||
modelAttr.setName("Set by model method [modelAttrByConvention]");
|
||||
model.addAttribute(modelAttr);
|
||||
|
||||
|
||||
model.addAttribute(new OtherUser());
|
||||
}
|
||||
|
||||
|
|
@ -322,13 +351,13 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||
.addAttribute("paramByConvention", paramByConvention).addAttribute("value", value)
|
||||
.addAttribute("customArg", customArg).addAttribute(user)
|
||||
.addAttribute("url", builder.path("/path").build().toUri());
|
||||
|
||||
|
||||
assertNotNull(request);
|
||||
assertNotNull(response);
|
||||
|
||||
return "viewName";
|
||||
}
|
||||
|
||||
|
||||
@ResponseStatus(value=HttpStatus.ACCEPTED)
|
||||
@ResponseBody
|
||||
public String handleRequestBody(@RequestBody byte[] bytes) throws Exception {
|
||||
|
|
@ -336,17 +365,29 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||
return "Handled requestBody=[" + requestBody + "]";
|
||||
}
|
||||
|
||||
@ResponseStatus(value=HttpStatus.ACCEPTED)
|
||||
@ResponseBody
|
||||
public String handleAndValidateRequestBody(@Valid TestBean modelAttr, Errors errors) throws Exception {
|
||||
return "Error count [" + errors.getErrorCount() + "]";
|
||||
}
|
||||
|
||||
public ResponseEntity<String> handleHttpEntity(HttpEntity<byte[]> httpEntity) throws Exception {
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.set("header", "headerValue");
|
||||
String responseBody = "Handled requestBody=[" + new String(httpEntity.getBody(), "UTF-8") + "]";
|
||||
return new ResponseEntity<String>(responseBody, responseHeaders, HttpStatus.ACCEPTED);
|
||||
}
|
||||
|
||||
|
||||
public void handleRequestPart(@RequestPart String requestPart, Model model) {
|
||||
model.addAttribute("requestPart", requestPart);
|
||||
}
|
||||
|
||||
|
||||
public void handleAndValidateRequestPart(@RequestPart @Valid String requestPart,
|
||||
Errors errors, Model model) throws Exception {
|
||||
|
||||
model.addAttribute("error count", errors.getErrorCount());
|
||||
}
|
||||
|
||||
public void handleAndCompleteSession(SessionStatus sessionStatus) {
|
||||
sessionStatus.setComplete();
|
||||
}
|
||||
|
|
@ -367,7 +408,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||
return new Color(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class User implements Principal {
|
||||
public String getName() {
|
||||
return "user";
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ public class RequestResponseBodyMethodProcessorTests {
|
|||
expect(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).andReturn(body);
|
||||
replay(messageConverter);
|
||||
|
||||
Object result = processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, null);
|
||||
Object result = processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, new ValidatingBinderFactory());
|
||||
|
||||
assertEquals("Invalid argument", body, result);
|
||||
assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled());
|
||||
|
|
@ -211,7 +211,7 @@ public class RequestResponseBodyMethodProcessorTests {
|
|||
|
||||
@Test
|
||||
public void resolveArgumentNotRequiredNoContent() throws Exception {
|
||||
assertNull(processor.resolveArgument(paramStringNotRequired, mavContainer, webRequest, null));
|
||||
assertNull(processor.resolveArgument(paramStringNotRequired, mavContainer, webRequest, new ValidatingBinderFactory()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -1584,13 +1584,13 @@ public void handle(@RequestBody String body, Writer writer) throws IOException {
|
|||
<para>An <classname>@RequestBody</classname> method parameter can be
|
||||
annotated with <classname>@Valid</classname>, in which case it will be
|
||||
validated using the configured <classname>Validator</classname>
|
||||
instance. When using the MVC namespace a JSR-303 validator is
|
||||
configured automatically assuming a JSR-303 implementation is
|
||||
instance. When using the MVC namespace or Java config, a JSR-303 validator
|
||||
is configured automatically assuming a JSR-303 implementation is
|
||||
available on the classpath.</para>
|
||||
<para>Unlike <classname>@ModelAttribute</classname> parameters, for which
|
||||
a <classname>BindingResult</classname> can be used to examine the errors,
|
||||
<classname>@RequestBody</classname> validation errors always result in a
|
||||
<classname>MethodArgumentNotValidException</classname> being raised.
|
||||
<para>Just like with <classname>@ModelAttribute</classname> parameters,
|
||||
an <classname>Errors</classname> argument can be used to examine the errors.
|
||||
If such an argument is not declared, a
|
||||
<classname>MethodArgumentNotValidException</classname> will be raised.
|
||||
The exception is handled in the
|
||||
<classname>DefaultHandlerExceptionResolver</classname>, which sends
|
||||
a <literal>400</literal> error back to the client.</para>
|
||||
|
|
|
|||
Loading…
Reference in New Issue