Avoid stateful MethodParameter nesting level changes in MVC processing
(cherry picked from commit e7a53e3)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
@@ -30,6 +30,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
@@ -62,17 +63,6 @@ import org.springframework.web.util.UrlPathHelper;
|
||||
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
|
||||
implements HandlerMethodReturnValueHandler {
|
||||
|
||||
private static final MediaType MEDIA_TYPE_APPLICATION = new MediaType("application");
|
||||
|
||||
private static final UrlPathHelper RAW_URL_PATH_HELPER = new UrlPathHelper();
|
||||
|
||||
private static final UrlPathHelper DECODING_URL_PATH_HELPER = new UrlPathHelper();
|
||||
|
||||
static {
|
||||
RAW_URL_PATH_HELPER.setRemoveSemicolonContent(false);
|
||||
RAW_URL_PATH_HELPER.setUrlDecode(false);
|
||||
}
|
||||
|
||||
/* Extensions associated with the built-in message converters */
|
||||
private static final Set<String> WHITELISTED_EXTENSIONS = new HashSet<String>(Arrays.asList(
|
||||
"txt", "text", "yml", "properties", "csv",
|
||||
@@ -82,6 +72,17 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
private static final Set<String> WHITELISTED_MEDIA_BASE_TYPES = new HashSet<String>(
|
||||
Arrays.asList("audio", "image", "video"));
|
||||
|
||||
private static final MediaType MEDIA_TYPE_APPLICATION = new MediaType("application");
|
||||
|
||||
private static final UrlPathHelper DECODING_URL_PATH_HELPER = new UrlPathHelper();
|
||||
|
||||
private static final UrlPathHelper RAW_URL_PATH_HELPER = new UrlPathHelper();
|
||||
|
||||
static {
|
||||
RAW_URL_PATH_HELPER.setRemoveSemicolonContent(false);
|
||||
RAW_URL_PATH_HELPER.setUrlDecode(false);
|
||||
}
|
||||
|
||||
|
||||
private final ContentNegotiationManager contentNegotiationManager;
|
||||
|
||||
@@ -145,17 +146,17 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
* Writes the given return value to the given web request. Delegates to
|
||||
* {@link #writeWithMessageConverters(Object, MethodParameter, ServletServerHttpRequest, ServletServerHttpResponse)}
|
||||
*/
|
||||
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType, NativeWebRequest webRequest)
|
||||
protected <T> void writeWithMessageConverters(T value, MethodParameter returnType, NativeWebRequest webRequest)
|
||||
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
|
||||
|
||||
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
|
||||
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
|
||||
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
|
||||
writeWithMessageConverters(value, returnType, inputMessage, outputMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given return type to the given output message.
|
||||
* @param returnValue the value to write to the output message
|
||||
* @param value the value to write to the output message
|
||||
* @param returnType the type of the value
|
||||
* @param inputMessage the input messages. Used to inspect the {@code Accept} header.
|
||||
* @param outputMessage the output message to write to
|
||||
@@ -164,18 +165,18 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
* the request cannot be met by the message converters
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
|
||||
protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
|
||||
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
|
||||
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
|
||||
|
||||
Class<?> returnValueClass = getReturnValueType(returnValue, returnType);
|
||||
Type returnValueType = getGenericType(returnType);
|
||||
HttpServletRequest servletRequest = inputMessage.getServletRequest();
|
||||
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
|
||||
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass, returnValueType);
|
||||
Class<?> valueType = getReturnValueType(value, returnType);
|
||||
Type declaredType = getGenericType(returnType);
|
||||
HttpServletRequest request = inputMessage.getServletRequest();
|
||||
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
|
||||
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
|
||||
|
||||
if (returnValue != null && producibleMediaTypes.isEmpty()) {
|
||||
throw new IllegalArgumentException("No converter found for return value of type: " + returnValueClass);
|
||||
if (value != null && producibleMediaTypes.isEmpty()) {
|
||||
throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
|
||||
}
|
||||
|
||||
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
|
||||
@@ -187,7 +188,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
}
|
||||
}
|
||||
if (compatibleMediaTypes.isEmpty()) {
|
||||
if (returnValue != null) {
|
||||
if (value != null) {
|
||||
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
|
||||
}
|
||||
return;
|
||||
@@ -212,34 +213,33 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
selectedMediaType = selectedMediaType.removeQualityValue();
|
||||
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
|
||||
if (messageConverter instanceof GenericHttpMessageConverter) {
|
||||
if (((GenericHttpMessageConverter<T>) messageConverter).canWrite(returnValueType,
|
||||
returnValueClass, selectedMediaType)) {
|
||||
returnValue = (T) getAdvice().beforeBodyWrite(returnValue, returnType, selectedMediaType,
|
||||
if (((GenericHttpMessageConverter<T>) messageConverter).canWrite(
|
||||
declaredType, valueType, selectedMediaType)) {
|
||||
value = (T) getAdvice().beforeBodyWrite(value, returnType, selectedMediaType,
|
||||
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
|
||||
inputMessage, outputMessage);
|
||||
if (returnValue != null) {
|
||||
if (value != null) {
|
||||
addContentDispositionHeader(inputMessage, outputMessage);
|
||||
((GenericHttpMessageConverter<T>) messageConverter).write(returnValue,
|
||||
returnValueType, selectedMediaType, outputMessage);
|
||||
((GenericHttpMessageConverter<T>) messageConverter).write(
|
||||
value, declaredType, selectedMediaType, outputMessage);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Written [" + returnValue + "] as \"" +
|
||||
selectedMediaType + "\" using [" + messageConverter + "]");
|
||||
logger.debug("Written [" + value + "] as \"" + selectedMediaType +
|
||||
"\" using [" + messageConverter + "]");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
|
||||
returnValue = (T) getAdvice().beforeBodyWrite(returnValue, returnType, selectedMediaType,
|
||||
else if (messageConverter.canWrite(valueType, selectedMediaType)) {
|
||||
value = (T) getAdvice().beforeBodyWrite(value, returnType, selectedMediaType,
|
||||
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
|
||||
inputMessage, outputMessage);
|
||||
if (returnValue != null) {
|
||||
if (value != null) {
|
||||
addContentDispositionHeader(inputMessage, outputMessage);
|
||||
((HttpMessageConverter<T>) messageConverter).write(returnValue,
|
||||
selectedMediaType, outputMessage);
|
||||
((HttpMessageConverter<T>) messageConverter).write(value, selectedMediaType, outputMessage);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Written [" + returnValue + "] as \"" +
|
||||
selectedMediaType + "\" using [" + messageConverter + "]");
|
||||
logger.debug("Written [" + value + "] as \"" + selectedMediaType +
|
||||
"\" using [" + messageConverter + "]");
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -247,43 +247,40 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
}
|
||||
}
|
||||
|
||||
if (returnValue != null) {
|
||||
if (value != null) {
|
||||
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type of the value to be written to the response. Typically this
|
||||
* is a simple check via getClass on the returnValue but if the returnValue is
|
||||
* null, then the returnType needs to be examined possibly including generic
|
||||
* type determination (e.g. {@code ResponseEntity<T>}).
|
||||
* Return the type of the value to be written to the response. Typically this is
|
||||
* a simple check via getClass on the value but if the value is null, then the
|
||||
* return type needs to be examined possibly including generic type determination
|
||||
* (e.g. {@code ResponseEntity<T>}).
|
||||
*/
|
||||
protected Class<?> getReturnValueType(Object returnValue, MethodParameter returnType) {
|
||||
return (returnValue != null ? returnValue.getClass() : returnType.getParameterType());
|
||||
protected Class<?> getReturnValueType(Object value, MethodParameter returnType) {
|
||||
return (value != null ? value.getClass() : returnType.getParameterType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the generic type of the {@code returnType} (or of the nested type if it is
|
||||
* a {@link HttpEntity}).
|
||||
* Return the generic type of the {@code returnType} (or of the nested type
|
||||
* if it is an {@link HttpEntity}).
|
||||
*/
|
||||
private Type getGenericType(MethodParameter returnType) {
|
||||
Type type;
|
||||
if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) {
|
||||
returnType.increaseNestingLevel();
|
||||
type = returnType.getNestedGenericParameterType();
|
||||
return ResolvableType.forType(returnType.getGenericParameterType()).getGeneric(0).getType();
|
||||
}
|
||||
else {
|
||||
type = returnType.getGenericParameterType();
|
||||
return returnType.getGenericParameterType();
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getProducibleMediaTypes(HttpServletRequest, Class, Type)
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "unused"})
|
||||
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> returnValueClass) {
|
||||
return getProducibleMediaTypes(request, returnValueClass, null);
|
||||
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass) {
|
||||
return getProducibleMediaTypes(request, valueClass, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,7 +293,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
* @since 4.2
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> returnValueClass, Type returnValueType) {
|
||||
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, Type declaredType) {
|
||||
Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
|
||||
if (!CollectionUtils.isEmpty(mediaTypes)) {
|
||||
return new ArrayList<MediaType>(mediaTypes);
|
||||
@@ -304,12 +301,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 instanceof GenericHttpMessageConverter && returnValueType != null) {
|
||||
if (((GenericHttpMessageConverter<?>) converter).canWrite(returnValueType, returnValueClass, null)) {
|
||||
if (converter instanceof GenericHttpMessageConverter && declaredType != null) {
|
||||
if (((GenericHttpMessageConverter<?>) converter).canWrite(declaredType, valueClass, null)) {
|
||||
result.addAll(converter.getSupportedMediaTypes());
|
||||
}
|
||||
}
|
||||
else if (converter.canWrite(returnValueClass, null)) {
|
||||
else if (converter.canWrite(valueClass, null)) {
|
||||
result.addAll(converter.getSupportedMediaTypes());
|
||||
}
|
||||
}
|
||||
@@ -412,7 +409,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
try {
|
||||
mediaTypes = this.pathStrategy.resolveMediaTypeKey(null, extension);
|
||||
}
|
||||
catch (HttpMediaTypeNotAcceptableException e) {
|
||||
catch (HttpMediaTypeNotAcceptableException ex) {
|
||||
// Ignore
|
||||
}
|
||||
if (CollectionUtils.isEmpty(mediaTypes)) {
|
||||
|
||||
Reference in New Issue
Block a user