Resolve target type for GenericHttpMessageConverter.canWrite/write

Issue: SPR-16877
This commit is contained in:
Juergen Hoeller 2018-07-16 19:45:35 +02:00
parent cacd14c805
commit b915e42c38
2 changed files with 37 additions and 30 deletions

View File

@ -28,6 +28,7 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
@ -59,12 +60,13 @@ import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.UrlPathHelper;
/** /**
* Extends {@link AbstractMessageConverterMethodArgumentResolver} with the ability to handle * Extends {@link AbstractMessageConverterMethodArgumentResolver} with the ability to handle method
* method return values by writing to the response with {@link HttpMessageConverter HttpMessageConverters}. * return values by writing to the response with {@link HttpMessageConverter HttpMessageConverters}.
* *
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Brian Clozel * @author Brian Clozel
* @author Juergen Hoeller
* @since 3.1 * @since 3.1
*/ */
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
@ -180,30 +182,30 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
Object body; Object body;
Class<?> valueType; Class<?> valueType;
Type declaredType; Type targetType;
if (value instanceof CharSequence) { if (value instanceof CharSequence) {
body = value.toString(); body = value.toString();
valueType = String.class; valueType = String.class;
declaredType = String.class; targetType = String.class;
} }
else { else {
body = value; body = value;
valueType = getReturnValueType(body, returnType); valueType = getReturnValueType(body, returnType);
declaredType = getGenericType(returnType); targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
} }
if (isResourceType(value, returnType)) { if (isResourceType(value, returnType)) {
outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes"); outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
&& outputMessage.getServletResponse().getStatus() == 200) { outputMessage.getServletResponse().getStatus() == 200) {
Resource resource = (Resource) value; Resource resource = (Resource) value;
try { try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange(); List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value()); outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
body = HttpRange.toResourceRegions(httpRanges, resource); body = HttpRange.toResourceRegions(httpRanges, resource);
valueType = body.getClass(); valueType = body.getClass();
declaredType = RESOURCE_REGION_LIST_TYPE; targetType = RESOURCE_REGION_LIST_TYPE;
} }
catch (IllegalArgumentException ex) { catch (IllegalArgumentException ex) {
outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength()); outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
@ -225,7 +227,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
else { else {
HttpServletRequest request = inputMessage.getServletRequest(); HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request); List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType); List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, targetType);
if (body != null && producibleMediaTypes.isEmpty()) { if (body != null && producibleMediaTypes.isEmpty()) {
throw new HttpMessageNotWritableException( throw new HttpMessageNotWritableException(
@ -270,22 +272,22 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
if (selectedMediaType != null) { if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue(); selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> converter : this.messageConverters) { for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null); (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ? if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(declaredType, valueType, selectedMediaType) : ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) { converter.canWrite(valueType, selectedMediaType)) {
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(), (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage); inputMessage, outputMessage);
if (body != null) { if (body != null) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
Object formatted = body instanceof CharSequence ? "\"" + body + "\"" : body; Object formatted = (body instanceof CharSequence ? "\"" + body + "\"" : body);
logger.debug("Writing [" + formatted + "]"); logger.debug("Writing [" + formatted + "]");
} }
addContentDispositionHeader(inputMessage, outputMessage); addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) { if (genericConverter != null) {
genericConverter.write(body, declaredType, selectedMediaType, outputMessage); genericConverter.write(body, targetType, selectedMediaType, outputMessage);
} }
else { else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage); ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
@ -356,18 +358,19 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
* @since 4.2 * @since 4.2
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, protected List<MediaType> getProducibleMediaTypes(
@Nullable Type declaredType) { HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); Set<MediaType> mediaTypes =
(Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) { if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<>(mediaTypes); return new ArrayList<>(mediaTypes);
} }
else if (!this.allSupportedMediaTypes.isEmpty()) { else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<>(); List<MediaType> result = new ArrayList<>();
for (HttpMessageConverter<?> converter : this.messageConverters) { for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter && declaredType != null) { if (converter instanceof GenericHttpMessageConverter && targetType != null) {
if (((GenericHttpMessageConverter<?>) converter).canWrite(declaredType, valueClass, null)) { if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes()); result.addAll(converter.getSupportedMediaTypes());
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -932,7 +932,20 @@ public class RequestResponseBodyMethodProcessorTests {
} }
private static class JacksonController { private static class BaseController<T> {
@RequestMapping
@ResponseBody
public List<T> handleTypeInfoList() {
List<T> list = new ArrayList<>();
list.add((T) new Foo("foo"));
list.add((T) new Bar("bar"));
return list;
}
}
private static class JacksonController extends BaseController<ParentClass> {
@RequestMapping @RequestMapping
@ResponseBody @ResponseBody
@ -969,15 +982,6 @@ public class RequestResponseBodyMethodProcessorTests {
return entity.getBody(); return entity.getBody();
} }
@RequestMapping
@ResponseBody
public List<ParentClass> handleTypeInfoList() {
List<ParentClass> list = new ArrayList<>();
list.add(new Foo("foo"));
list.add(new Bar("bar"));
return list;
}
@RequestMapping @RequestMapping
@ResponseBody @ResponseBody
public Identifiable handleSubType() { public Identifiable handleSubType() {