Fix issue with @RequestBody args that are type vars

The change to support generic @RequestBody arguments introduced in
3.2 M2 also introduced a regression in reading arguments that are
type variables. This change fixes the issue.

Issue: SPR-9964
This commit is contained in:
Rossen Stoyanchev
2012-11-21 10:40:28 -05:00
parent 4181397c31
commit b7f7fae78a
14 changed files with 202 additions and 105 deletions

View File

@@ -27,6 +27,7 @@ import org.springframework.http.MediaType;
* request into a target object of a specified generic type.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.2
*
* @see org.springframework.core.ParameterizedTypeReference
@@ -36,11 +37,13 @@ public interface GenericHttpMessageConverter<T> extends HttpMessageConverter<T>
/**
* Indicates whether the given type can be read by this converter.
* @param type the type to test for readability
* @param contextClass a context class for the target type, for example a class
* in which the target type appears in a method signature, can be {@code null}
* @param mediaType the media type to read, can be {@code null} if not specified.
* Typically the value of a {@code Content-Type} header.
* @return {@code true} if readable; {@code false} otherwise
*/
boolean canRead(Type type, MediaType mediaType);
boolean canRead(Type type, Class<?> contextClass, MediaType mediaType);
/**
* Read an object of the given type form the given input message, and returns it.
@@ -48,12 +51,14 @@ public interface GenericHttpMessageConverter<T> extends HttpMessageConverter<T>
* been passed to the {@link #canRead canRead} method of this interface,
* which must have returned {@code true}.
* @param type the type of the target object
* @param contextClass a context class for the target type, for example a class
* in which the target type appears in a method signature, can be {@code null}
* @param inputMessage the HTTP input message to read from
* @return the converted object
* @throws IOException in case of I/O errors
* @throws HttpMessageNotReadableException in case of conversion errors
*/
T read(Type type, HttpInputMessage inputMessage)
T read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
}

View File

@@ -128,11 +128,11 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return canRead((Type) clazz, mediaType);
return canRead((Type) clazz, null, mediaType);
}
public boolean canRead(Type type, MediaType mediaType) {
JavaType javaType = getJavaType(type);
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
JavaType javaType = getJavaType(type, contextClass);
return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType));
}
@@ -151,14 +151,14 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
JavaType javaType = getJavaType(clazz);
JavaType javaType = getJavaType(clazz, null);
return readJavaType(javaType, inputMessage);
}
public Object read(Type type, HttpInputMessage inputMessage)
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
JavaType javaType = getJavaType(type);
JavaType javaType = getJavaType(type, contextClass);
return readJavaType(javaType, inputMessage);
}
@@ -197,10 +197,10 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
}
}
/**
* Return the Jackson {@link JavaType} for the specified type.
* <p>The default implementation returns {@link ObjectMapper#constructType(java.lang.reflect.Type)},
* Return the Jackson {@link JavaType} for the specified type and context class.
* <p>The default implementation returns {@link ObjectMapper#constructType(java.lang.reflect.Type)}
* or {@code ObjectMapper.getTypeFactory().constructType(type, contextClass)},
* but this can be overridden in subclasses, to allow for custom generic collection handling.
* For instance:
* <pre class="code">
@@ -213,10 +213,15 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
* }
* </pre>
* @param type the type to return the java type for
* @param contextClass a context class for the target type, for example a class
* in which the target type appears in a method signature, can be {@code null}
* signature, can be {@code null}
* @return the java type
*/
protected JavaType getJavaType(Type type) {
return this.objectMapper.constructType(type);
protected JavaType getJavaType(Type type, Class<?> contextClass) {
return (contextClass != null) ?
this.objectMapper.getTypeFactory().constructType(type, contextClass) :
this.objectMapper.constructType(type);
}
/**

View File

@@ -128,11 +128,11 @@ public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConve
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return canRead((Type) clazz, mediaType);
return canRead((Type) clazz, null, mediaType);
}
public boolean canRead(Type type, MediaType mediaType) {
JavaType javaType = getJavaType(type);
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
JavaType javaType = getJavaType(type, contextClass);
return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType));
}
@@ -151,14 +151,14 @@ public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConve
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
JavaType javaType = getJavaType(clazz);
JavaType javaType = getJavaType(clazz, null);
return readJavaType(javaType, inputMessage);
}
public Object read(Type type, HttpInputMessage inputMessage)
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
JavaType javaType = getJavaType(type);
JavaType javaType = getJavaType(type, contextClass);
return readJavaType(javaType, inputMessage);
}
@@ -196,10 +196,10 @@ public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConve
}
}
/**
* Return the Jackson {@link JavaType} for the specified type.
* <p>The default implementation returns {@link TypeFactory#type(java.lang.reflect.Type)},
* Return the Jackson {@link JavaType} for the specified type and context class.
* <p>The default implementation returns {@link TypeFactory#type(java.lang.reflect.Type)}
* or {@code TypeFactory.type(type, TypeFactory.type(contextClass))},
* but this can be overridden in subclasses, to allow for custom generic collection handling.
* For instance:
* <pre class="code">
@@ -212,10 +212,14 @@ public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConve
* }
* </pre>
* @param type the type to return the java type for
* @param contextClass a context class for the target type, for example a class
* in which the target type appears in a method signature, can be {@code null}
* @return the java type
*/
protected JavaType getJavaType(Type type) {
return TypeFactory.type(type);
protected JavaType getJavaType(Type type, Class<?> contextClass) {
return (contextClass != null) ?
TypeFactory.type(type, TypeFactory.type(contextClass)) :
TypeFactory.type(type);
}
/**

View File

@@ -74,7 +74,7 @@ public class Jaxb2CollectionHttpMessageConverter<T extends Collection>
* {@link Collection} where the generic type is a JAXB type annotated with
* {@link XmlRootElement} or {@link XmlType}.
*/
public boolean canRead(Type type, MediaType mediaType) {
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
if (!(type instanceof ParameterizedType)) {
return false;
}
@@ -119,7 +119,9 @@ public class Jaxb2CollectionHttpMessageConverter<T extends Collection>
throw new UnsupportedOperationException();
}
public T read(Type type, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
public T read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
ParameterizedType parameterizedType = (ParameterizedType) type;
T result = createCollection((Class<?>) parameterizedType.getRawType());
Class<?> elementClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];

View File

@@ -86,12 +86,12 @@ public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
for (HttpMessageConverter messageConverter : this.messageConverters) {
if (messageConverter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter genericMessageConverter = (GenericHttpMessageConverter) messageConverter;
if (genericMessageConverter.canRead(this.responseType, contentType)) {
if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading [" + this.responseType + "] as \"" +
contentType + "\" using [" + messageConverter + "]");
}
return (T) genericMessageConverter.read(this.responseType, response);
return (T) genericMessageConverter.read(this.responseType, null, response);
}
}
if (this.responseClass != null) {

View File

@@ -553,20 +553,17 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
}
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
for (HttpMessageConverter<?> converter : getMessageConverters()) {
if (responseClass != null) {
if (messageConverter.canRead(responseClass, null)) {
allSupportedMediaTypes
.addAll(getSupportedMediaTypes(messageConverter));
if (converter.canRead(responseClass, null)) {
allSupportedMediaTypes.addAll(getSupportedMediaTypes(converter));
}
}
else if (messageConverter instanceof GenericHttpMessageConverter) {
else if (converter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter genericMessageConverter =
(GenericHttpMessageConverter) messageConverter;
if (genericMessageConverter.canRead(responseType, null)) {
allSupportedMediaTypes
.addAll(getSupportedMediaTypes(messageConverter));
GenericHttpMessageConverter genericConverter = (GenericHttpMessageConverter) converter;
if (genericConverter.canRead(responseType, null, null)) {
allSupportedMediaTypes.addAll(getSupportedMediaTypes(converter));
}
}