Skip to content

Commit cd37873

Browse files
committed
Avoid stateful MethodParameter nesting level changes in MVC processing
(cherry picked from commit e7a53e3)
1 parent 4ddd957 commit cd37873

File tree

1 file changed

+57
-60
lines changed

1 file changed

+57
-60
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java

Lines changed: 57 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
3030
import javax.servlet.http.HttpServletResponse;
3131

3232
import org.springframework.core.MethodParameter;
33+
import org.springframework.core.ResolvableType;
3334
import org.springframework.http.HttpEntity;
3435
import org.springframework.http.HttpHeaders;
3536
import org.springframework.http.HttpOutputMessage;
@@ -62,17 +63,6 @@
6263
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
6364
implements HandlerMethodReturnValueHandler {
6465

65-
private static final MediaType MEDIA_TYPE_APPLICATION = new MediaType("application");
66-
67-
private static final UrlPathHelper RAW_URL_PATH_HELPER = new UrlPathHelper();
68-
69-
private static final UrlPathHelper DECODING_URL_PATH_HELPER = new UrlPathHelper();
70-
71-
static {
72-
RAW_URL_PATH_HELPER.setRemoveSemicolonContent(false);
73-
RAW_URL_PATH_HELPER.setUrlDecode(false);
74-
}
75-
7666
/* Extensions associated with the built-in message converters */
7767
private static final Set<String> WHITELISTED_EXTENSIONS = new HashSet<String>(Arrays.asList(
7868
"txt", "text", "yml", "properties", "csv",
@@ -82,6 +72,17 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
8272
private static final Set<String> WHITELISTED_MEDIA_BASE_TYPES = new HashSet<String>(
8373
Arrays.asList("audio", "image", "video"));
8474

75+
private static final MediaType MEDIA_TYPE_APPLICATION = new MediaType("application");
76+
77+
private static final UrlPathHelper DECODING_URL_PATH_HELPER = new UrlPathHelper();
78+
79+
private static final UrlPathHelper RAW_URL_PATH_HELPER = new UrlPathHelper();
80+
81+
static {
82+
RAW_URL_PATH_HELPER.setRemoveSemicolonContent(false);
83+
RAW_URL_PATH_HELPER.setUrlDecode(false);
84+
}
85+
8586

8687
private final ContentNegotiationManager contentNegotiationManager;
8788

@@ -145,17 +146,17 @@ protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequ
145146
* Writes the given return value to the given web request. Delegates to
146147
* {@link #writeWithMessageConverters(Object, MethodParameter, ServletServerHttpRequest, ServletServerHttpResponse)}
147148
*/
148-
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType, NativeWebRequest webRequest)
149+
protected <T> void writeWithMessageConverters(T value, MethodParameter returnType, NativeWebRequest webRequest)
149150
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
150151

151152
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
152153
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
153-
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
154+
writeWithMessageConverters(value, returnType, inputMessage, outputMessage);
154155
}
155156

156157
/**
157158
* Writes the given return type to the given output message.
158-
* @param returnValue the value to write to the output message
159+
* @param value the value to write to the output message
159160
* @param returnType the type of the value
160161
* @param inputMessage the input messages. Used to inspect the {@code Accept} header.
161162
* @param outputMessage the output message to write to
@@ -164,18 +165,18 @@ protected <T> void writeWithMessageConverters(T returnValue, MethodParameter ret
164165
* the request cannot be met by the message converters
165166
*/
166167
@SuppressWarnings("unchecked")
167-
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
168+
protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
168169
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
169170
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
170171

171-
Class<?> returnValueClass = getReturnValueType(returnValue, returnType);
172-
Type returnValueType = getGenericType(returnType);
173-
HttpServletRequest servletRequest = inputMessage.getServletRequest();
174-
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
175-
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass, returnValueType);
172+
Class<?> valueType = getReturnValueType(value, returnType);
173+
Type declaredType = getGenericType(returnType);
174+
HttpServletRequest request = inputMessage.getServletRequest();
175+
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
176+
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
176177

177-
if (returnValue != null && producibleMediaTypes.isEmpty()) {
178-
throw new IllegalArgumentException("No converter found for return value of type: " + returnValueClass);
178+
if (value != null && producibleMediaTypes.isEmpty()) {
179+
throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
179180
}
180181

181182
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
@@ -187,7 +188,7 @@ protected <T> void writeWithMessageConverters(T returnValue, MethodParameter ret
187188
}
188189
}
189190
if (compatibleMediaTypes.isEmpty()) {
190-
if (returnValue != null) {
191+
if (value != null) {
191192
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
192193
}
193194
return;
@@ -212,78 +213,74 @@ else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICAT
212213
selectedMediaType = selectedMediaType.removeQualityValue();
213214
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
214215
if (messageConverter instanceof GenericHttpMessageConverter) {
215-
if (((GenericHttpMessageConverter<T>) messageConverter).canWrite(returnValueType,
216-
returnValueClass, selectedMediaType)) {
217-
returnValue = (T) getAdvice().beforeBodyWrite(returnValue, returnType, selectedMediaType,
216+
if (((GenericHttpMessageConverter<T>) messageConverter).canWrite(
217+
declaredType, valueType, selectedMediaType)) {
218+
value = (T) getAdvice().beforeBodyWrite(value, returnType, selectedMediaType,
218219
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
219220
inputMessage, outputMessage);
220-
if (returnValue != null) {
221+
if (value != null) {
221222
addContentDispositionHeader(inputMessage, outputMessage);
222-
((GenericHttpMessageConverter<T>) messageConverter).write(returnValue,
223-
returnValueType, selectedMediaType, outputMessage);
223+
((GenericHttpMessageConverter<T>) messageConverter).write(
224+
value, declaredType, selectedMediaType, outputMessage);
224225
if (logger.isDebugEnabled()) {
225-
logger.debug("Written [" + returnValue + "] as \"" +
226-
selectedMediaType + "\" using [" + messageConverter + "]");
226+
logger.debug("Written [" + value + "] as \"" + selectedMediaType +
227+
"\" using [" + messageConverter + "]");
227228
}
228229
}
229230
return;
230231
}
231232
}
232-
else if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
233-
returnValue = (T) getAdvice().beforeBodyWrite(returnValue, returnType, selectedMediaType,
233+
else if (messageConverter.canWrite(valueType, selectedMediaType)) {
234+
value = (T) getAdvice().beforeBodyWrite(value, returnType, selectedMediaType,
234235
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
235236
inputMessage, outputMessage);
236-
if (returnValue != null) {
237+
if (value != null) {
237238
addContentDispositionHeader(inputMessage, outputMessage);
238-
((HttpMessageConverter<T>) messageConverter).write(returnValue,
239-
selectedMediaType, outputMessage);
239+
((HttpMessageConverter<T>) messageConverter).write(value, selectedMediaType, outputMessage);
240240
if (logger.isDebugEnabled()) {
241-
logger.debug("Written [" + returnValue + "] as \"" +
242-
selectedMediaType + "\" using [" + messageConverter + "]");
241+
logger.debug("Written [" + value + "] as \"" + selectedMediaType +
242+
"\" using [" + messageConverter + "]");
243243
}
244244
}
245245
return;
246246
}
247247
}
248248
}
249249

250-
if (returnValue != null) {
250+
if (value != null) {
251251
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
252252
}
253253
}
254254

255255
/**
256-
* Return the type of the value to be written to the response. Typically this
257-
* is a simple check via getClass on the returnValue but if the returnValue is
258-
* null, then the returnType needs to be examined possibly including generic
259-
* type determination (e.g. {@code ResponseEntity<T>}).
256+
* Return the type of the value to be written to the response. Typically this is
257+
* a simple check via getClass on the value but if the value is null, then the
258+
* return type needs to be examined possibly including generic type determination
259+
* (e.g. {@code ResponseEntity<T>}).
260260
*/
261-
protected Class<?> getReturnValueType(Object returnValue, MethodParameter returnType) {
262-
return (returnValue != null ? returnValue.getClass() : returnType.getParameterType());
261+
protected Class<?> getReturnValueType(Object value, MethodParameter returnType) {
262+
return (value != null ? value.getClass() : returnType.getParameterType());
263263
}
264264

265265
/**
266-
* Return the generic type of the {@code returnType} (or of the nested type if it is
267-
* a {@link HttpEntity}).
266+
* Return the generic type of the {@code returnType} (or of the nested type
267+
* if it is an {@link HttpEntity}).
268268
*/
269269
private Type getGenericType(MethodParameter returnType) {
270-
Type type;
271270
if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) {
272-
returnType.increaseNestingLevel();
273-
type = returnType.getNestedGenericParameterType();
271+
return ResolvableType.forType(returnType.getGenericParameterType()).getGeneric(0).getType();
274272
}
275273
else {
276-
type = returnType.getGenericParameterType();
274+
return returnType.getGenericParameterType();
277275
}
278-
return type;
279276
}
280277

281278
/**
282279
* @see #getProducibleMediaTypes(HttpServletRequest, Class, Type)
283280
*/
284281
@SuppressWarnings({"unchecked", "unused"})
285-
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> returnValueClass) {
286-
return getProducibleMediaTypes(request, returnValueClass, null);
282+
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass) {
283+
return getProducibleMediaTypes(request, valueClass, null);
287284
}
288285

289286
/**
@@ -296,20 +293,20 @@ protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Cl
296293
* @since 4.2
297294
*/
298295
@SuppressWarnings("unchecked")
299-
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> returnValueClass, Type returnValueType) {
296+
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, Type declaredType) {
300297
Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
301298
if (!CollectionUtils.isEmpty(mediaTypes)) {
302299
return new ArrayList<MediaType>(mediaTypes);
303300
}
304301
else if (!this.allSupportedMediaTypes.isEmpty()) {
305302
List<MediaType> result = new ArrayList<MediaType>();
306303
for (HttpMessageConverter<?> converter : this.messageConverters) {
307-
if (converter instanceof GenericHttpMessageConverter && returnValueType != null) {
308-
if (((GenericHttpMessageConverter<?>) converter).canWrite(returnValueType, returnValueClass, null)) {
304+
if (converter instanceof GenericHttpMessageConverter && declaredType != null) {
305+
if (((GenericHttpMessageConverter<?>) converter).canWrite(declaredType, valueClass, null)) {
309306
result.addAll(converter.getSupportedMediaTypes());
310307
}
311308
}
312-
else if (converter.canWrite(returnValueClass, null)) {
309+
else if (converter.canWrite(valueClass, null)) {
313310
result.addAll(converter.getSupportedMediaTypes());
314311
}
315312
}
@@ -412,7 +409,7 @@ private boolean safeMediaTypesForExtension(String extension) {
412409
try {
413410
mediaTypes = this.pathStrategy.resolveMediaTypeKey(null, extension);
414411
}
415-
catch (HttpMediaTypeNotAcceptableException e) {
412+
catch (HttpMediaTypeNotAcceptableException ex) {
416413
// Ignore
417414
}
418415
if (CollectionUtils.isEmpty(mediaTypes)) {

0 commit comments

Comments
 (0)