Browse Source

Improve empty request body handling

The check for an empty request body InputStream is now in the base
class AbstractMessageConverterMethodArgumentResolver shared for
all arguments that involve reading with an HttpMessageConverter --
@RequestBody, @RequestPart, and HttpEntity.

When an empty body is detected any configured RequestBodyAdvice is
given a chance to select a default value or leave it as null.

Issue: SPR-12778, SPR-12860, SPR-12861
pull/566/merge
Rossen Stoyanchev 11 years ago
parent
commit
36ed4df59d
  1. 89
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java
  2. 7
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java
  3. 41
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java
  4. 40
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java
  5. 35
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java
  6. 32
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartIntegrationTests.java
  7. 26
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolverTests.java
  8. 22
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorMockTests.java
  9. 71
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java

89
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java

@ -17,6 +17,8 @@ @@ -17,6 +17,8 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
@ -33,6 +35,7 @@ import org.apache.commons.logging.LogFactory; @@ -33,6 +35,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
@ -57,6 +60,9 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -57,6 +60,9 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
*/
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
private static final Object NO_VALUE = new Object();
protected final Log logger = LogFactory.getLog(getClass());
protected final List<HttpMessageConverter<?>> messageConverters;
@ -135,9 +141,8 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements @@ -135,9 +141,8 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
* @param <T> the expected type of the argument value to be created
* @param inputMessage the HTTP input message representing the current request
* @param param the method parameter descriptor (may be {@code null})
* @param targetType the type of object to create, not necessarily the same as
* the method parameter type (e.g. for {@code HttpEntity<String>} method
* parameter the target type is String)
* @param targetType the target type, not necessarily the same as the method
* parameter type, e.g. for {@code HttpEntity<String>}.
* @return the created method argument value
* @throws IOException if the reading from the request fails
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found
@ -165,6 +170,9 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements @@ -165,6 +170,9 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
targetClass = (Class<T>) resolvableType.resolve();
}
inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage);
Object body = NO_VALUE;
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
if (converter instanceof GenericHttpMessageConverter) {
@ -173,9 +181,16 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements @@ -173,9 +181,16 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
if (logger.isDebugEnabled()) {
logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
}
inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType);
T body = (T) genericConverter.read(targetType, contextClass, inputMessage);
return getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType);
if (inputMessage.getBody() != null) {
inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType);
body = genericConverter.read(targetType, contextClass, inputMessage);
body = getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType);
}
else {
body = null;
body = getAdvice().handleEmptyBody(body, inputMessage, param, targetType, converterType);
}
break;
}
}
else if (targetClass != null) {
@ -183,14 +198,25 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements @@ -183,14 +198,25 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
if (logger.isDebugEnabled()) {
logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
}
inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType);
T body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
return getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType);
if (inputMessage.getBody() != null) {
inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType);
body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
body = getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType);
}
else {
body = null;
body = getAdvice().handleEmptyBody(body, inputMessage, param, targetType, converterType);
}
break;
}
}
}
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
if (body == NO_VALUE) {
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}
return body;
}
/**
@ -240,4 +266,47 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements @@ -240,4 +266,47 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
return !hasBindingResult;
}
private static class EmptyBodyCheckingHttpInputMessage implements HttpInputMessage {
private final HttpHeaders headers;
private final InputStream body;
public EmptyBodyCheckingHttpInputMessage(HttpInputMessage inputMessage) throws IOException {
this.headers = inputMessage.getHeaders();
InputStream inputStream = inputMessage.getBody();
if (inputStream == null) {
this.body = null;
}
else if (inputStream.markSupported()) {
inputStream.mark(1);
this.body = (inputStream.read() != -1 ? inputStream : null);
inputStream.reset();
}
else {
PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
int b = pushbackInputStream.read();
if (b == -1) {
this.body = null;
}
else {
this.body = pushbackInputStream;
pushbackInputStream.unread(b);
}
}
}
@Override
public HttpHeaders getHeaders() {
return this.headers;
}
@Override
public InputStream getBody() throws IOException {
return this.body;
}
}
}

7
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java

@ -19,12 +19,14 @@ package org.springframework.web.servlet.mvc.method.annotation; @@ -19,12 +19,14 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.List;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
@ -120,8 +122,9 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro @@ -120,8 +122,9 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
Object body = readWithMessageConverters(webRequest, parameter, paramType);
if (RequestEntity.class.equals(parameter.getParameterType())) {
return new RequestEntity<Object>(body, inputMessage.getHeaders(),
inputMessage.getMethod(), inputMessage.getURI());
URI url = inputMessage.getURI();
HttpMethod httpMethod = inputMessage.getMethod();
return new RequestEntity<Object>(body, inputMessage.getHeaders(), httpMethod, url);
}
else {
return new HttpEntity<Object>(body, inputMessage.getHeaders());

41
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java

@ -17,8 +17,6 @@ @@ -17,8 +17,6 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.lang.reflect.Type;
import java.util.List;
@ -146,43 +144,14 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter @@ -146,43 +144,14 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
HttpInputMessage inputMessage = new ServletServerHttpRequest(servletRequest);
InputStream inputStream = inputMessage.getBody();
if (inputStream == null) {
return handleEmptyBody(methodParam);
}
else if (inputStream.markSupported()) {
inputStream.mark(1);
if (inputStream.read() == -1) {
return handleEmptyBody(methodParam);
}
inputStream.reset();
}
else {
final PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
int b = pushbackInputStream.read();
if (b == -1) {
return handleEmptyBody(methodParam);
}
else {
pushbackInputStream.unread(b);
Object arg = readWithMessageConverters(inputMessage, methodParam, paramType);
if (arg == null) {
if (methodParam.getParameterAnnotation(RequestBody.class).required()) {
throw new HttpMessageNotReadableException("Required request body is missing: " + methodParam);
}
inputMessage = new ServletServerHttpRequest(servletRequest) {
@Override
public InputStream getBody() {
// Form POST should not get here
return pushbackInputStream;
}
};
}
return super.readWithMessageConverters(inputMessage, methodParam, paramType);
}
private Object handleEmptyBody(MethodParameter param) {
if (param.getParameterAnnotation(RequestBody.class).required()) {
throw new HttpMessageNotReadableException("Required request body content is missing: " + param);
}
return null;
return arg;
}
@Override

40
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java

@ -16,14 +16,19 @@ @@ -16,14 +16,19 @@
package org.springframework.web.servlet.mvc.method.annotation;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.any;
import static org.mockito.BDDMockito.*;
import static org.mockito.BDDMockito.isA;
import static org.mockito.Matchers.eq;
import static org.springframework.web.servlet.HandlerMapping.*;
import java.lang.reflect.Method;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import org.junit.Before;
import org.junit.Test;
@ -48,13 +53,6 @@ import org.springframework.web.bind.annotation.RequestMapping; @@ -48,13 +53,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.any;
import static org.mockito.BDDMockito.*;
import static org.mockito.BDDMockito.isA;
import static org.mockito.Matchers.eq;
import static org.springframework.web.servlet.HandlerMapping.*;
/**
* Test fixture for {@link HttpEntityMethodProcessor} delegating to a mock
* {@link HttpMessageConverter}.
@ -136,10 +134,12 @@ public class HttpEntityMethodProcessorMockTests { @@ -136,10 +134,12 @@ public class HttpEntityMethodProcessorMockTests {
@Test
public void resolveArgument() throws Exception {
String body = "Foo";
MediaType contentType = MediaType.TEXT_PLAIN;
servletRequest.addHeader("Content-Type", contentType.toString());
servletRequest.setContent(body.getBytes(Charset.forName("UTF-8")));
String body = "Foo";
given(messageConverter.canRead(String.class, contentType)).willReturn(true);
given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
@ -152,14 +152,16 @@ public class HttpEntityMethodProcessorMockTests { @@ -152,14 +152,16 @@ public class HttpEntityMethodProcessorMockTests {
@Test
public void resolveArgumentRequestEntity() throws Exception {
String body = "Foo";
MediaType contentType = MediaType.TEXT_PLAIN;
servletRequest.addHeader("Content-Type", contentType.toString());
servletRequest.setMethod("GET");
servletRequest.setServerName("www.example.com");
servletRequest.setServerPort(80);
servletRequest.setRequestURI("/path");
servletRequest.setContent(body.getBytes(Charset.forName("UTF-8")));
String body = "Foo";
given(messageConverter.canRead(String.class, contentType)).willReturn(true);
given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
@ -226,6 +228,7 @@ public class HttpEntityMethodProcessorMockTests { @@ -226,6 +228,7 @@ public class HttpEntityMethodProcessorMockTests {
verify(messageConverter).write(eq(body), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
}
@SuppressWarnings("unchecked")
@Test
public void handleReturnValueWithResponseBodyAdvice() throws Exception {
ResponseEntity<String> returnValue = new ResponseEntity<>(HttpStatus.OK);
@ -416,28 +419,35 @@ public class HttpEntityMethodProcessorMockTests { @@ -416,28 +419,35 @@ public class HttpEntityMethodProcessorMockTests {
assertEquals(0, servletResponse.getContentAsByteArray().length);
}
public ResponseEntity<String> handle1(HttpEntity<String> httpEntity, ResponseEntity<String> responseEntity, int i, RequestEntity<String> requestEntity) {
return responseEntity;
@SuppressWarnings("unused")
public ResponseEntity<String> handle1(HttpEntity<String> httpEntity, ResponseEntity<String> entity,
int i, RequestEntity<String> requestEntity) {
return entity;
}
@SuppressWarnings("unused")
public HttpEntity<?> handle2(HttpEntity<?> entity) {
return entity;
}
@SuppressWarnings("unused")
public CustomHttpEntity handle2x(HttpEntity<?> entity) {
return new CustomHttpEntity();
}
@SuppressWarnings("unused")
public int handle3() {
return 42;
}
@SuppressWarnings("unused")
@RequestMapping(produces = {"text/html", "application/xhtml+xml"})
public ResponseEntity<String> handle4() {
return null;
}
@SuppressWarnings("unused")
public static class CustomHttpEntity extends HttpEntity<Object> {
}

35
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@ -16,9 +16,12 @@ @@ -16,9 +16,12 @@
package org.springframework.web.servlet.mvc.method.annotation;
import static org.junit.Assert.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
@ -39,8 +42,6 @@ import org.springframework.web.context.request.ServletWebRequest; @@ -39,8 +42,6 @@ import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
import static org.junit.Assert.*;
/**
* Test fixture with {@link HttpEntityMethodProcessor} delegating to
* actual {@link HttpMessageConverter} instances.
@ -61,8 +62,6 @@ public class HttpEntityMethodProcessorTests { @@ -61,8 +62,6 @@ public class HttpEntityMethodProcessorTests {
private MockHttpServletRequest servletRequest;
private MockHttpServletResponse servletResponse;
private ServletWebRequest webRequest;
@ -75,8 +74,7 @@ public class HttpEntityMethodProcessorTests { @@ -75,8 +74,7 @@ public class HttpEntityMethodProcessorTests {
mavContainer = new ModelAndViewContainer();
binderFactory = new ValidatingBinderFactory();
servletRequest = new MockHttpServletRequest();
servletResponse = new MockHttpServletResponse();
webRequest = new ServletWebRequest(servletRequest, servletResponse);
webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse());
}
@Test
@ -97,6 +95,23 @@ public class HttpEntityMethodProcessorTests { @@ -97,6 +95,23 @@ public class HttpEntityMethodProcessorTests {
assertEquals("Jad", result.getBody().getName());
}
// SPR-12861
@Test
public void resolveArgumentWithEmptyBody() throws Exception {
this.servletRequest.setContent(new byte[0]);
this.servletRequest.setContentType("application/json");
List<HttpMessageConverter<?>> converters = Arrays.asList(new MappingJackson2HttpMessageConverter());
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(converters);
HttpEntity<?> result = (HttpEntity<?>) processor.resolveArgument(this.paramSimpleBean,
this.mavContainer, this.webRequest, this.binderFactory);
assertNotNull(result);
assertNull(result.getBody());
}
@Test
public void resolveGenericArgument() throws Exception {
String content = "[{\"name\" : \"Jad\"}, {\"name\" : \"Robert\"}]";
@ -139,21 +154,21 @@ public class HttpEntityMethodProcessorTests { @@ -139,21 +154,21 @@ public class HttpEntityMethodProcessorTests {
}
@SuppressWarnings("unused")
public void handle(HttpEntity<List<SimpleBean>> arg1, HttpEntity<SimpleBean> arg2) {
}
@SuppressWarnings("unused")
private static abstract class MyParameterizedController<DTO extends Identifiable> {
public void handleDto(HttpEntity<DTO> dto) {
}
}
@SuppressWarnings("unused")
private static class MySimpleParameterizedController extends MyParameterizedController<SimpleBean> {
}
private interface Identifiable extends Serializable {
public Long getId();

32
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartIntegrationTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@ -16,8 +16,13 @@ @@ -16,8 +16,13 @@
package org.springframework.web.servlet.mvc.method.annotation;
import static org.junit.Assert.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.servlet.MultipartConfigElement;
import org.eclipse.jetty.server.Server;
@ -34,8 +39,10 @@ import org.springframework.core.io.ClassPathResource; @@ -34,8 +39,10 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@ -57,8 +64,6 @@ import org.springframework.web.servlet.DispatcherServlet; @@ -57,8 +64,6 @@ import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import static org.junit.Assert.*;
/**
* Test access to parts of a multipart request with {@link RequestPart}.
*
@ -102,9 +107,16 @@ public class RequestPartIntegrationTests { @@ -102,9 +107,16 @@ public class RequestPartIntegrationTests {
@Before
public void setUp() {
ByteArrayHttpMessageConverter emptyBodyConverter = new ByteArrayHttpMessageConverter();
emptyBodyConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON));
List<HttpMessageConverter<?>> converters = new ArrayList<>(3);
converters.add(emptyBodyConverter);
converters.add(new ResourceHttpMessageConverter());
converters.add(new MappingJackson2HttpMessageConverter());
AllEncompassingFormHttpMessageConverter converter = new AllEncompassingFormHttpMessageConverter();
converter.setPartConverters(Arrays.<HttpMessageConverter<?>>asList(
new ResourceHttpMessageConverter(), new MappingJackson2HttpMessageConverter()));
converter.setPartConverters(converters);
restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
restTemplate.setMessageConverters(Arrays.<HttpMessageConverter<?>>asList(converter));
@ -130,9 +142,9 @@ public class RequestPartIntegrationTests { @@ -130,9 +142,9 @@ public class RequestPartIntegrationTests {
private void testCreate(String url) {
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
HttpEntity<TestData> jsonEntity = new HttpEntity<TestData>(new TestData("Jason"));
parts.add("json-data", jsonEntity);
parts.add("json-data", new HttpEntity<TestData>(new TestData("Jason")));
parts.add("file-data", new ClassPathResource("logo.jpg", this.getClass()));
parts.add("empty-data", new HttpEntity<byte[]>(new byte[0])); // SPR-12860
URI location = restTemplate.postForLocation(url, parts);
assertEquals("http://localhost:8080/test/Jason/logo.jpg", location.toString());
@ -167,11 +179,15 @@ public class RequestPartIntegrationTests { @@ -167,11 +179,15 @@ public class RequestPartIntegrationTests {
}
}
@SuppressWarnings("unused")
@Controller
private static class RequestPartTestController {
@RequestMapping(value = "/test", method = RequestMethod.POST, consumes = { "multipart/mixed", "multipart/form-data" })
public ResponseEntity<Object> create(@RequestPart("json-data") TestData testData, @RequestPart("file-data") MultipartFile file) {
public ResponseEntity<Object> create(@RequestPart("json-data") TestData testData,
@RequestPart("file-data") MultipartFile file,
@RequestPart(value = "empty-data", required = false) TestData emptyData) {
String url = "http://localhost:8080/test/" + testData.getName() + "/" + file.getOriginalFilename();
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create(url));

26
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolverTests.java

@ -16,11 +16,16 @@ @@ -16,11 +16,16 @@
package org.springframework.web.servlet.mvc.method.annotation;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javax.servlet.http.Part;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@ -31,6 +36,7 @@ import org.junit.Test; @@ -31,6 +36,7 @@ import org.junit.Test;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.mock.web.test.MockHttpServletRequest;
@ -52,9 +58,6 @@ import org.springframework.web.multipart.MultipartFile; @@ -52,9 +58,6 @@ import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.multipart.support.RequestPartServletServerHttpRequest;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
/**
* Test fixture with {@link RequestPartMethodArgumentResolver} and mock {@link HttpMessageConverter}.
*
@ -90,8 +93,6 @@ public class RequestPartMethodArgumentResolverTests { @@ -90,8 +93,6 @@ public class RequestPartMethodArgumentResolverTests {
private MockMultipartHttpServletRequest multipartRequest;
private MockHttpServletResponse servletResponse;
@SuppressWarnings("unchecked")
@Before
@ -128,13 +129,14 @@ public class RequestPartMethodArgumentResolverTests { @@ -128,13 +129,14 @@ public class RequestPartMethodArgumentResolverTests {
resolver = new RequestPartMethodArgumentResolver(Collections.<HttpMessageConverter<?>>singletonList(messageConverter));
reset(messageConverter);
multipartFile1 = new MockMultipartFile("requestPart", "", "text/plain", (byte[]) null);
multipartFile2 = new MockMultipartFile("requestPart", "", "text/plain", (byte[]) null);
byte[] content = "doesn't matter as long as not empty".getBytes(Charset.forName("UTF-8"));
multipartFile1 = new MockMultipartFile("requestPart", "", "text/plain", content);
multipartFile2 = new MockMultipartFile("requestPart", "", "text/plain", content);
multipartRequest = new MockMultipartHttpServletRequest();
multipartRequest.addFile(multipartFile1);
multipartRequest.addFile(multipartFile2);
servletResponse = new MockHttpServletResponse();
webRequest = new ServletWebRequest(multipartRequest, servletResponse);
webRequest = new ServletWebRequest(multipartRequest, new MockHttpServletResponse());
}
@ -361,7 +363,7 @@ public class RequestPartMethodArgumentResolverTests { @@ -361,7 +363,7 @@ public class RequestPartMethodArgumentResolverTests {
SimpleBean simpleBean = new SimpleBean("foo");
given(messageConverter.canRead(SimpleBean.class, MediaType.TEXT_PLAIN)).willReturn(true);
given(messageConverter.read(eq(SimpleBean.class), isA(RequestPartServletServerHttpRequest.class))).willReturn(simpleBean);
given(messageConverter.read(eq(SimpleBean.class), isA(HttpInputMessage.class))).willReturn(simpleBean);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
Object actualValue = resolver.resolveArgument(optionalRequestPart, mavContainer, webRequest, new ValidatingBinderFactory());
@ -385,7 +387,7 @@ public class RequestPartMethodArgumentResolverTests { @@ -385,7 +387,7 @@ public class RequestPartMethodArgumentResolverTests {
private void testResolveArgument(SimpleBean argValue, MethodParameter parameter) throws Exception {
given(messageConverter.canRead(SimpleBean.class, MediaType.TEXT_PLAIN)).willReturn(true);
given(messageConverter.read(eq(SimpleBean.class), isA(RequestPartServletServerHttpRequest.class))).willReturn(argValue);
given(messageConverter.read(eq(SimpleBean.class), isA(HttpInputMessage.class))).willReturn(argValue);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
Object actualValue = resolver.resolveArgument(parameter, mavContainer, webRequest, new ValidatingBinderFactory());
@ -423,7 +425,7 @@ public class RequestPartMethodArgumentResolverTests { @@ -423,7 +425,7 @@ public class RequestPartMethodArgumentResolverTests {
}
}
@SuppressWarnings("unused")
public void handle(@RequestPart SimpleBean requestPart,
@RequestPart(value="requestPart", required=false) SimpleBean namedRequestPart,
@Valid @RequestPart("requestPart") SimpleBean validRequestPart,

22
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorMockTests.java

@ -16,12 +16,15 @@ @@ -16,12 +16,15 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@ -49,9 +52,6 @@ import org.springframework.web.context.request.ServletWebRequest; @@ -49,9 +52,6 @@ import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerMapping;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
/**
* Test fixture for {@link RequestResponseBodyMethodProcessor} delegating to a
* mock HttpMessageConverter.
@ -81,7 +81,6 @@ public class RequestResponseBodyMethodProcessorMockTests { @@ -81,7 +81,6 @@ public class RequestResponseBodyMethodProcessorMockTests {
private MockHttpServletRequest servletRequest;
private MockHttpServletResponse servletResponse;
@SuppressWarnings("unchecked")
@Before
@ -103,8 +102,7 @@ public class RequestResponseBodyMethodProcessorMockTests { @@ -103,8 +102,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
mavContainer = new ModelAndViewContainer();
servletRequest = new MockHttpServletRequest();
servletResponse = new MockHttpServletResponse();
webRequest = new ServletWebRequest(servletRequest, servletResponse);
webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse());
}
@Test
@ -154,7 +152,7 @@ public class RequestResponseBodyMethodProcessorMockTests { @@ -154,7 +152,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
testResolveArgumentWithValidation(new SimpleBean("name"));
}
private void testResolveArgumentWithValidation(SimpleBean simpleBean) throws IOException, Exception {
private void testResolveArgumentWithValidation(SimpleBean simpleBean) throws Exception {
MediaType contentType = MediaType.TEXT_PLAIN;
servletRequest.addHeader("Content-Type", contentType.toString());
servletRequest.setContent("payload".getBytes(Charset.forName("UTF-8")));
@ -189,6 +187,7 @@ public class RequestResponseBodyMethodProcessorMockTests { @@ -189,6 +187,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
fail("Expected exception");
}
catch (HttpMediaTypeNotSupportedException ex) {
// expected
}
}
@ -212,7 +211,9 @@ public class RequestResponseBodyMethodProcessorMockTests { @@ -212,7 +211,9 @@ public class RequestResponseBodyMethodProcessorMockTests {
@Test
public void resolveArgumentNotRequiredNoContent() throws Exception {
servletRequest.setContentType("text/plain");
servletRequest.setContent(new byte[0]);
given(messageConverter.canRead(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
assertNull(processor.resolveArgument(paramStringNotRequired, mavContainer, webRequest, new ValidatingBinderFactory()));
}
@ -293,23 +294,28 @@ public class RequestResponseBodyMethodProcessorMockTests { @@ -293,23 +294,28 @@ public class RequestResponseBodyMethodProcessorMockTests {
}
@SuppressWarnings("unused")
@ResponseBody
public String handle1(@RequestBody String s, int i) {
return s;
}
@SuppressWarnings("unused")
public int handle2() {
return 42;
}
@SuppressWarnings("unused")
@ResponseBody
public String handle3() {
return null;
}
@SuppressWarnings("unused")
public void handle4(@Valid @RequestBody SimpleBean b) {
}
@SuppressWarnings("unused")
public void handle5(@RequestBody(required=false) String s) {
}

71
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java

@ -18,6 +18,7 @@ package org.springframework.web.servlet.mvc.method.annotation; @@ -18,6 +18,7 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -29,6 +30,7 @@ import org.junit.Test; @@ -29,6 +30,7 @@ import org.junit.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@ -177,7 +179,9 @@ public class RequestResponseBodyMethodProcessorTests { @@ -177,7 +179,9 @@ public class RequestResponseBodyMethodProcessorTests {
assertEquals("foobarbaz", result);
}
@Test(expected = HttpMessageNotReadableException.class) // SPR-9942
// SPR-9942
@Test(expected = HttpMessageNotReadableException.class)
public void resolveArgumentRequiredNoContent() throws Exception {
this.servletRequest.setContent(new byte[0]);
this.servletRequest.setContentType("text/plain");
@ -187,7 +191,23 @@ public class RequestResponseBodyMethodProcessorTests { @@ -187,7 +191,23 @@ public class RequestResponseBodyMethodProcessorTests {
processor.resolveArgument(paramString, mavContainer, webRequest, binderFactory);
}
@Test // SPR-9964
// SPR-12778
@Test
public void resolveArgumentRequiredNoContentDefaultValue() throws Exception {
this.servletRequest.setContent(new byte[0]);
this.servletRequest.setContentType("text/plain");
List<HttpMessageConverter<?>> converters = Arrays.asList(new StringHttpMessageConverter());
List<Object> advice = Arrays.asList(new EmptyRequestBodyAdvice());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters, advice);
String arg = (String) processor.resolveArgument(paramString, mavContainer, webRequest, binderFactory);
assertNotNull(arg);
assertEquals("default value for empty body", arg);
}
// SPR-9964
@Test
public void resolveArgumentTypeVariable() throws Exception {
Method method = MyParameterizedController.class.getMethod("handleDto", Identifiable.class);
HandlerMethod handlerMethod = new HandlerMethod(new MySimpleParameterizedController(), method);
@ -207,7 +227,9 @@ public class RequestResponseBodyMethodProcessorTests { @@ -207,7 +227,9 @@ public class RequestResponseBodyMethodProcessorTests {
assertEquals("Jad", result.getName());
}
@Test // SPR-11225
// SPR-11225
@Test
public void resolveArgumentTypeVariableWithNonGenericConverter() throws Exception {
Method method = MyParameterizedController.class.getMethod("handleDto", Identifiable.class);
HandlerMethod handlerMethod = new HandlerMethod(new MySimpleParameterizedController(), method);
@ -229,7 +251,9 @@ public class RequestResponseBodyMethodProcessorTests { @@ -229,7 +251,9 @@ public class RequestResponseBodyMethodProcessorTests {
assertEquals("Jad", result.getName());
}
@Test // SPR-9160
// SPR-9160
@Test
public void handleReturnValueSortByQuality() throws Exception {
this.servletRequest.addHeader("Accept", "text/plain; q=0.5, application/json");
@ -383,6 +407,7 @@ public class RequestResponseBodyMethodProcessorTests { @@ -383,6 +407,7 @@ public class RequestResponseBodyMethodProcessorTests {
}
@SuppressWarnings("unused")
public String handle(
@RequestBody List<SimpleBean> list,
@RequestBody SimpleBean simpleBean,
@ -393,6 +418,7 @@ public class RequestResponseBodyMethodProcessorTests { @@ -393,6 +418,7 @@ public class RequestResponseBodyMethodProcessorTests {
}
@SuppressWarnings("unused")
private static abstract class MyParameterizedController<DTO extends Identifiable> {
@SuppressWarnings("unused")
@ -400,6 +426,7 @@ public class RequestResponseBodyMethodProcessorTests { @@ -400,6 +426,7 @@ public class RequestResponseBodyMethodProcessorTests {
}
@SuppressWarnings("unused")
private static class MySimpleParameterizedController extends MyParameterizedController<SimpleBean> {
}
@ -472,9 +499,10 @@ public class RequestResponseBodyMethodProcessorTests { @@ -472,9 +499,10 @@ public class RequestResponseBodyMethodProcessorTests {
}
}
private interface MyJacksonView1 {};
private interface MyJacksonView2 {};
private interface MyJacksonView1 {}
private interface MyJacksonView2 {}
@SuppressWarnings("unused")
private static class JacksonViewBean {
@JsonView(MyJacksonView1.class)
@ -537,4 +565,35 @@ public class RequestResponseBodyMethodProcessorTests { @@ -537,4 +565,35 @@ public class RequestResponseBodyMethodProcessorTests {
}
private static class EmptyRequestBodyAdvice implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return StringHttpMessageConverter.class.equals(converterType);
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return "default value for empty body";
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return inputMessage;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
}

Loading…
Cancel
Save