From cd3787397112e061c584c6046b9507ccaf2a0c86 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sat, 2 Jul 2016 13:02:22 +0200 Subject: [PATCH] Avoid stateful MethodParameter nesting level changes in MVC processing (cherry picked from commit e7a53e3) --- ...stractMessageConverterMethodProcessor.java | 117 +++++++++--------- 1 file changed, 57 insertions(+), 60 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java index f40349950411..296caaf8ce83 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java @@ -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.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 @@ 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 WHITELISTED_EXTENSIONS = new HashSet(Arrays.asList( "txt", "text", "yml", "properties", "csv", @@ -82,6 +72,17 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe private static final Set WHITELISTED_MEDIA_BASE_TYPES = new HashSet( 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 @@ protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequ * Writes the given return value to the given web request. Delegates to * {@link #writeWithMessageConverters(Object, MethodParameter, ServletServerHttpRequest, ServletServerHttpResponse)} */ - protected void writeWithMessageConverters(T returnValue, MethodParameter returnType, NativeWebRequest webRequest) + protected 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 @@ protected void writeWithMessageConverters(T returnValue, MethodParameter ret * the request cannot be met by the message converters */ @SuppressWarnings("unchecked") - protected void writeWithMessageConverters(T returnValue, MethodParameter returnType, + protected 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 requestedMediaTypes = getAcceptableMediaTypes(servletRequest); - List producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass, returnValueType); + Class valueType = getReturnValueType(value, returnType); + Type declaredType = getGenericType(returnType); + HttpServletRequest request = inputMessage.getServletRequest(); + List requestedMediaTypes = getAcceptableMediaTypes(request); + List 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 compatibleMediaTypes = new LinkedHashSet(); @@ -187,7 +188,7 @@ protected void writeWithMessageConverters(T returnValue, MethodParameter ret } } if (compatibleMediaTypes.isEmpty()) { - if (returnValue != null) { + if (value != null) { throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes); } return; @@ -212,34 +213,33 @@ else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICAT selectedMediaType = selectedMediaType.removeQualityValue(); for (HttpMessageConverter messageConverter : this.messageConverters) { if (messageConverter instanceof GenericHttpMessageConverter) { - if (((GenericHttpMessageConverter) messageConverter).canWrite(returnValueType, - returnValueClass, selectedMediaType)) { - returnValue = (T) getAdvice().beforeBodyWrite(returnValue, returnType, selectedMediaType, + if (((GenericHttpMessageConverter) messageConverter).canWrite( + declaredType, valueType, selectedMediaType)) { + value = (T) getAdvice().beforeBodyWrite(value, returnType, selectedMediaType, (Class>) messageConverter.getClass(), inputMessage, outputMessage); - if (returnValue != null) { + if (value != null) { addContentDispositionHeader(inputMessage, outputMessage); - ((GenericHttpMessageConverter) messageConverter).write(returnValue, - returnValueType, selectedMediaType, outputMessage); + ((GenericHttpMessageConverter) 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>) messageConverter.getClass(), inputMessage, outputMessage); - if (returnValue != null) { + if (value != null) { addContentDispositionHeader(inputMessage, outputMessage); - ((HttpMessageConverter) messageConverter).write(returnValue, - selectedMediaType, outputMessage); + ((HttpMessageConverter) 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 @@ else if (messageConverter.canWrite(returnValueClass, selectedMediaType)) { } } - 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}). + * 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}). */ - 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 getProducibleMediaTypes(HttpServletRequest request, Class returnValueClass) { - return getProducibleMediaTypes(request, returnValueClass, null); + protected List getProducibleMediaTypes(HttpServletRequest request, Class valueClass) { + return getProducibleMediaTypes(request, valueClass, null); } /** @@ -296,7 +293,7 @@ protected List getProducibleMediaTypes(HttpServletRequest request, Cl * @since 4.2 */ @SuppressWarnings("unchecked") - protected List getProducibleMediaTypes(HttpServletRequest request, Class returnValueClass, Type returnValueType) { + protected List getProducibleMediaTypes(HttpServletRequest request, Class valueClass, Type declaredType) { Set mediaTypes = (Set) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); if (!CollectionUtils.isEmpty(mediaTypes)) { return new ArrayList(mediaTypes); @@ -304,12 +301,12 @@ protected List getProducibleMediaTypes(HttpServletRequest request, Cl else if (!this.allSupportedMediaTypes.isEmpty()) { List result = new ArrayList(); 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 @@ private boolean safeMediaTypesForExtension(String extension) { try { mediaTypes = this.pathStrategy.resolveMediaTypeKey(null, extension); } - catch (HttpMediaTypeNotAcceptableException e) { + catch (HttpMediaTypeNotAcceptableException ex) { // Ignore } if (CollectionUtils.isEmpty(mediaTypes)) {