Call type aware canWrite() when using a GenericHttpMessageConverter

This commit introduces the following changes:
 - In AbstractMessageConverterMethodProcessor, the type aware variant of
   canWrite() is now called when the converter implements
   GenericHttpMessageConverter.
 - The Javadoc has been updated in GenericHttpMessageConverter to make it clear
   that the type aware canRead() and canWrite() methods should perform the same
   checks than non type aware ones.
  - AbstractGenericHttpMessageConverter now implements default type aware
    canRead() and canWrite() methods than just call the non type aware variants.
    Due to this, if subclasses just override the non type aware variants,
    they still have the right behavior.

Issue: SPR-13161
This commit is contained in:
Sebastien Deleuze
2015-06-25 15:02:33 +02:00
parent 51e30dd221
commit 289f35da3a
5 changed files with 69 additions and 27 deletions

View File

@@ -116,9 +116,10 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
throws IOException, HttpMediaTypeNotAcceptableException {
Class<?> returnValueClass = getReturnValueType(returnValue, returnType);
Type returnValueType = getGenericType(returnType);
HttpServletRequest servletRequest = inputMessage.getServletRequest();
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass, returnValueType);
Assert.isTrue(returnValue == null || !producibleMediaTypes.isEmpty(),
"No converter found for return value of type: " + returnValueClass);
@@ -156,25 +157,30 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
if (messageConverter instanceof GenericHttpMessageConverter) {
if (((GenericHttpMessageConverter<T>) messageConverter).canWrite(returnValueType,
returnValueClass, selectedMediaType)) {
returnValue = (T) getAdvice().beforeBodyWrite(returnValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
inputMessage, outputMessage);
if (returnValue != null) {
((GenericHttpMessageConverter<T>) messageConverter).write(returnValue,
returnValueType, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + returnValue + "] as \"" +
selectedMediaType + "\" using [" + messageConverter + "]");
}
}
return;
}
}
else if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
returnValue = (T) getAdvice().beforeBodyWrite(returnValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
inputMessage, outputMessage);
if (returnValue != null) {
if (messageConverter instanceof GenericHttpMessageConverter) {
Type type;
if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) {
returnType.increaseNestingLevel();
type = returnType.getNestedGenericParameterType();
}
else {
type = returnType.getGenericParameterType();
}
((GenericHttpMessageConverter<T>) messageConverter).write(returnValue, type, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
}
((HttpMessageConverter<T>) messageConverter).write(returnValue,
selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + returnValue + "] as \"" +
selectedMediaType + "\" using [" + messageConverter + "]");
@@ -200,6 +206,30 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
return (returnValue != null ? returnValue.getClass() : returnType.getParameterType());
}
/**
* Return the generic type of the {@code returnType} (or of the nested type if it is
* a {@link HttpEntity}).
*/
private Type getGenericType(MethodParameter returnType) {
Type type;
if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) {
returnType.increaseNestingLevel();
type = returnType.getNestedGenericParameterType();
}
else {
type = returnType.getGenericParameterType();
}
return type;
}
/**
* @see #getProducibleMediaTypes(HttpServletRequest, Class, Type)
*/
@SuppressWarnings("unchecked")
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> returnValueClass) {
return getProducibleMediaTypes(request, returnValueClass, null);
}
/**
* Returns the media types that can be produced:
* <ul>
@@ -207,9 +237,10 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
* <li>Media types of configured converters that can write the specific return value, or
* <li>{@link MediaType#ALL}
* </ul>
* @since 4.2
*/
@SuppressWarnings("unchecked")
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> returnValueClass) {
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> returnValueClass, Type returnValueType) {
Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<MediaType>(mediaTypes);
@@ -217,7 +248,12 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<MediaType>();
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter.canWrite(returnValueClass, null)) {
if (converter instanceof GenericHttpMessageConverter && returnValueType != null) {
if (((GenericHttpMessageConverter<?>) converter).canWrite(returnValueType, returnValueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
else if (converter.canWrite(returnValueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}