Skip to content

Commit 289f35d

Browse files
committed
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
1 parent 51e30dd commit 289f35d

File tree

5 files changed

+69
-27
lines changed

5 files changed

+69
-27
lines changed

spring-web/src/main/java/org/springframework/http/converter/AbstractGenericHttpMessageConverter.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,13 @@ protected AbstractGenericHttpMessageConverter(MediaType... supportedMediaTypes)
5858
}
5959

6060
@Override
61-
public boolean canWrite(Class<?> contextClass, MediaType mediaType) {
62-
return canWrite(null, contextClass, mediaType);
61+
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
62+
return canRead(contextClass, mediaType);
63+
}
64+
65+
@Override
66+
public boolean canWrite(Type type, Class<?> contextClass, MediaType mediaType) {
67+
return canWrite(contextClass, mediaType);
6368
}
6469

6570
/**

spring-web/src/main/java/org/springframework/http/converter/GenericHttpMessageConverter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ public interface GenericHttpMessageConverter<T> extends HttpMessageConverter<T>
3838

3939
/**
4040
* Indicates whether the given type can be read by this converter.
41+
* This method should perform the same checks than
42+
* {@link HttpMessageConverter#canRead(Class, MediaType)} with additional ones
43+
* related to the generic type.
4144
* @param type the type to test for readability
4245
* @param contextClass a context class for the target type, for example a class
4346
* in which the target type appears in a method signature (can be {@code null})
@@ -64,6 +67,9 @@ T read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
6467

6568
/**
6669
* Indicates whether the given class can be written by this converter.
70+
* This method should perform the same checks than
71+
* {@link HttpMessageConverter#canWrite(Class, MediaType)} with additional ones
72+
* related to the generic type.
6773
* @param type the type to test for writability, can be {@code null} if not specified.
6874
* @param contextClass the class to test for writability
6975
* @param mediaType the media type to write, can be {@code null} if not specified.

spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
160160
}
161161

162162
@Override
163-
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
163+
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
164164
if (!jackson23Available || !logger.isWarnEnabled()) {
165165
return (this.objectMapper.canSerialize(clazz) && canWrite(mediaType));
166166
}

spring-web/src/main/java/org/springframework/http/converter/json/GsonHttpMessageConverter.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,7 @@ public boolean canRead(Class<?> clazz, MediaType mediaType) {
120120
}
121121

122122
@Override
123-
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
124-
return canRead(mediaType);
125-
}
126-
127-
@Override
128-
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
123+
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
129124
return canWrite(mediaType);
130125
}
131126

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

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,10 @@ protected <T> void writeWithMessageConverters(T returnValue, MethodParameter ret
116116
throws IOException, HttpMediaTypeNotAcceptableException {
117117

118118
Class<?> returnValueClass = getReturnValueType(returnValue, returnType);
119+
Type returnValueType = getGenericType(returnType);
119120
HttpServletRequest servletRequest = inputMessage.getServletRequest();
120121
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
121-
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
122+
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass, returnValueType);
122123

123124
Assert.isTrue(returnValue == null || !producibleMediaTypes.isEmpty(),
124125
"No converter found for return value of type: " + returnValueClass);
@@ -156,25 +157,30 @@ else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICAT
156157
if (selectedMediaType != null) {
157158
selectedMediaType = selectedMediaType.removeQualityValue();
158159
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
159-
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
160+
if (messageConverter instanceof GenericHttpMessageConverter) {
161+
if (((GenericHttpMessageConverter<T>) messageConverter).canWrite(returnValueType,
162+
returnValueClass, selectedMediaType)) {
163+
returnValue = (T) getAdvice().beforeBodyWrite(returnValue, returnType, selectedMediaType,
164+
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
165+
inputMessage, outputMessage);
166+
if (returnValue != null) {
167+
((GenericHttpMessageConverter<T>) messageConverter).write(returnValue,
168+
returnValueType, selectedMediaType, outputMessage);
169+
if (logger.isDebugEnabled()) {
170+
logger.debug("Written [" + returnValue + "] as \"" +
171+
selectedMediaType + "\" using [" + messageConverter + "]");
172+
}
173+
}
174+
return;
175+
}
176+
}
177+
else if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
160178
returnValue = (T) getAdvice().beforeBodyWrite(returnValue, returnType, selectedMediaType,
161179
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
162180
inputMessage, outputMessage);
163181
if (returnValue != null) {
164-
if (messageConverter instanceof GenericHttpMessageConverter) {
165-
Type type;
166-
if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) {
167-
returnType.increaseNestingLevel();
168-
type = returnType.getNestedGenericParameterType();
169-
}
170-
else {
171-
type = returnType.getGenericParameterType();
172-
}
173-
((GenericHttpMessageConverter<T>) messageConverter).write(returnValue, type, selectedMediaType, outputMessage);
174-
}
175-
else {
176-
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
177-
}
182+
((HttpMessageConverter<T>) messageConverter).write(returnValue,
183+
selectedMediaType, outputMessage);
178184
if (logger.isDebugEnabled()) {
179185
logger.debug("Written [" + returnValue + "] as \"" +
180186
selectedMediaType + "\" using [" + messageConverter + "]");
@@ -200,24 +206,54 @@ protected Class<?> getReturnValueType(Object returnValue, MethodParameter return
200206
return (returnValue != null ? returnValue.getClass() : returnType.getParameterType());
201207
}
202208

209+
/**
210+
* Return the generic type of the {@code returnType} (or of the nested type if it is
211+
* a {@link HttpEntity}).
212+
*/
213+
private Type getGenericType(MethodParameter returnType) {
214+
Type type;
215+
if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) {
216+
returnType.increaseNestingLevel();
217+
type = returnType.getNestedGenericParameterType();
218+
}
219+
else {
220+
type = returnType.getGenericParameterType();
221+
}
222+
return type;
223+
}
224+
225+
/**
226+
* @see #getProducibleMediaTypes(HttpServletRequest, Class, Type)
227+
*/
228+
@SuppressWarnings("unchecked")
229+
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> returnValueClass) {
230+
return getProducibleMediaTypes(request, returnValueClass, null);
231+
}
232+
203233
/**
204234
* Returns the media types that can be produced:
205235
* <ul>
206236
* <li>The producible media types specified in the request mappings, or
207237
* <li>Media types of configured converters that can write the specific return value, or
208238
* <li>{@link MediaType#ALL}
209239
* </ul>
240+
* @since 4.2
210241
*/
211242
@SuppressWarnings("unchecked")
212-
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> returnValueClass) {
243+
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> returnValueClass, Type returnValueType) {
213244
Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
214245
if (!CollectionUtils.isEmpty(mediaTypes)) {
215246
return new ArrayList<MediaType>(mediaTypes);
216247
}
217248
else if (!this.allSupportedMediaTypes.isEmpty()) {
218249
List<MediaType> result = new ArrayList<MediaType>();
219250
for (HttpMessageConverter<?> converter : this.messageConverters) {
220-
if (converter.canWrite(returnValueClass, null)) {
251+
if (converter instanceof GenericHttpMessageConverter && returnValueType != null) {
252+
if (((GenericHttpMessageConverter<?>) converter).canWrite(returnValueType, returnValueClass, null)) {
253+
result.addAll(converter.getSupportedMediaTypes());
254+
}
255+
}
256+
else if (converter.canWrite(returnValueClass, null)) {
221257
result.addAll(converter.getSupportedMediaTypes());
222258
}
223259
}

0 commit comments

Comments
 (0)