diff --git a/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverter.java index a0583b9c6d5..a6a80b38945 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -17,6 +17,7 @@ package org.springframework.http.converter; import java.io.IOException; +import java.util.Collections; import java.util.List; import org.springframework.http.HttpInputMessage; @@ -53,11 +54,30 @@ public interface HttpMessageConverter { boolean canWrite(Class clazz, @Nullable MediaType mediaType); /** - * Return the list of {@link MediaType} objects supported by this converter. - * @return the list of supported media types, potentially an immutable copy + * Return the list of media types supported by this converter. The list may + * not apply to every possible target element type and calls to this method + * should typically be guarded via {@link #canWrite(Class, MediaType) + * canWrite(clazz, null}. The list may also exclude MIME types supported + * only for a specific class. Alternatively, use + * {@link #getSupportedMediaTypes(Class)} for a more precise list. + * @return the list of supported media types */ List getSupportedMediaTypes(); + /** + * Return the list of media types supported by this converter for the given + * class. The list may differ from {@link #getSupportedMediaTypes()} if the + * converter doesn't support given Class or if it support it only for a + * subset of media types. + * @param clazz the type of class to check + * @return the list of media types supported for the given class + * @since 5.3.4 + */ + default List getSupportedMediaTypes(Class clazz) { + return (canRead(clazz, null) || canWrite(clazz, null) ? + getSupportedMediaTypes() : Collections.emptyList()); + } + /** * Read an object of the given type from the given input message, and returns it. * @param clazz the type of object to return. This type must have previously been passed to the diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java index ce3b5edb500..d6f5b49f6cf 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java @@ -23,6 +23,7 @@ import java.io.Reader; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; @@ -194,6 +195,18 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener return Collections.emptyMap(); } + @Override + public List getSupportedMediaTypes(Class clazz) { + List result = null; + for (Map.Entry, Map> entry : getObjectMapperRegistrations().entrySet()) { + if (entry.getKey().isAssignableFrom(clazz)) { + result = (result != null ? result : new ArrayList<>(entry.getValue().size())); + result.addAll(entry.getValue().keySet()); + } + } + return (CollectionUtils.isEmpty(result) ? getSupportedMediaTypes() : result); + } + private Map, Map> getObjectMapperRegistrations() { return (this.objectMapperRegistrations != null ? this.objectMapperRegistrations : Collections.emptyMap()); } diff --git a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java index 641f5231472..22662632475 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -17,6 +17,7 @@ package org.springframework.web.client; import java.io.IOException; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.net.URI; import java.util.ArrayList; @@ -885,7 +886,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat if (this.responseType != null) { List allSupportedMediaTypes = getMessageConverters().stream() .filter(converter -> canReadResponse(this.responseType, converter)) - .flatMap(this::getSupportedMediaTypes) + .flatMap((HttpMessageConverter converter) -> getSupportedMediaTypes(this.responseType, converter)) .distinct() .sorted(MediaType.SPECIFICITY_COMPARATOR) .collect(Collectors.toList()); @@ -908,8 +909,10 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat return false; } - private Stream getSupportedMediaTypes(HttpMessageConverter messageConverter) { - return messageConverter.getSupportedMediaTypes() + private Stream getSupportedMediaTypes(Type type, HttpMessageConverter converter) { + Type rawType = (type instanceof ParameterizedType ? ((ParameterizedType) type).getRawType() : type); + Class clazz = (rawType instanceof Class ? (Class) rawType : null); + return (clazz != null ? converter.getSupportedMediaTypes(clazz) : converter.getSupportedMediaTypes()) .stream() .map(mediaType -> { if (mediaType.getCharset() != null) { diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java index 76e9bfceebe..cea6e8c7cf7 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java @@ -109,6 +109,22 @@ public class MappingJackson2HttpMessageConverterTests { assertThat(converter.canWrite(MyBean.class, new MediaType("application", "vnd.test-micro-type+json"))).isTrue(); } + @Test + public void getSupportedMediaTypes() { + MediaType[] defaultMediaTypes = {MediaType.APPLICATION_JSON, MediaType.parseMediaType("application/*+json")}; + assertThat(converter.getSupportedMediaTypes()).containsExactly(defaultMediaTypes); + assertThat(converter.getSupportedMediaTypes(MyBean.class)).containsExactly(defaultMediaTypes); + + MediaType halJson = MediaType.parseMediaType("application/hal+json"); + converter.registerObjectMappersForType(MyBean.class, map -> { + map.put(halJson, new ObjectMapper()); + map.put(MediaType.APPLICATION_JSON, new ObjectMapper()); + }); + + assertThat(converter.getSupportedMediaTypes(MyBean.class)).containsExactly(halJson, MediaType.APPLICATION_JSON); + assertThat(converter.getSupportedMediaTypes(Map.class)).containsExactly(defaultMediaTypes); + } + @Test public void readTyped() throws IOException { String body = "{" + diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java index 2c727e67ad7..c95c60cb28f 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -232,12 +232,10 @@ class RestTemplateTests { void requestAvoidsDuplicateAcceptHeaderValues() throws Exception { HttpMessageConverter firstConverter = mock(HttpMessageConverter.class); given(firstConverter.canRead(any(), any())).willReturn(true); - given(firstConverter.getSupportedMediaTypes()) - .willReturn(Collections.singletonList(MediaType.TEXT_PLAIN)); + given(firstConverter.getSupportedMediaTypes(any())).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN)); HttpMessageConverter secondConverter = mock(HttpMessageConverter.class); given(secondConverter.canRead(any(), any())).willReturn(true); - given(secondConverter.getSupportedMediaTypes()) - .willReturn(Collections.singletonList(MediaType.TEXT_PLAIN)); + given(secondConverter.getSupportedMediaTypes(any())).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN)); HttpHeaders requestHeaders = new HttpHeaders(); mockSentRequest(GET, "https://example.com/", requestHeaders); @@ -651,7 +649,7 @@ class RestTemplateTests { template.setMessageConverters(Collections.>singletonList(converter)); ParameterizedTypeReference> intList = new ParameterizedTypeReference>() {}; given(converter.canRead(intList.getType(), null, null)).willReturn(true); - given(converter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN)); + given(converter.getSupportedMediaTypes(any())).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN)); given(converter.canWrite(String.class, String.class, null)).willReturn(true); HttpHeaders requestHeaders = new HttpHeaders(); @@ -774,8 +772,7 @@ class RestTemplateTests { private void mockHttpMessageConverter(MediaType mediaType, Class type) { given(converter.canRead(type, null)).willReturn(true); given(converter.canRead(type, mediaType)).willReturn(true); - given(converter.getSupportedMediaTypes()) - .willReturn(Collections.singletonList(mediaType)); + given(converter.getSupportedMediaTypes(type)).willReturn(Collections.singletonList(mediaType)); given(converter.canRead(type, mediaType)).willReturn(true); given(converter.canWrite(type, null)).willReturn(true); given(converter.canWrite(type, mediaType)).willReturn(true); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilder.java index 4dfad874a4e..44b721e72a2 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -339,7 +339,7 @@ final class DefaultEntityResponseBuilder implements EntityResponse.Builder return messageConverters.stream() .filter(messageConverter -> messageConverter.canWrite(entityClass, null)) - .flatMap(messageConverter -> messageConverter.getSupportedMediaTypes().stream()) + .flatMap(messageConverter -> messageConverter.getSupportedMediaTypes(entityClass).stream()) .collect(Collectors.toList()); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java index da9bd4b62e8..26f76e7ca0a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -81,8 +81,6 @@ class DefaultServerRequest implements ServerRequest { private final List> messageConverters; - private final List allSupportedMediaTypes; - private final MultiValueMap params; private final Map attributes; @@ -94,7 +92,6 @@ class DefaultServerRequest implements ServerRequest { public DefaultServerRequest(HttpServletRequest servletRequest, List> messageConverters) { this.serverHttpRequest = new ServletServerHttpRequest(servletRequest); this.messageConverters = Collections.unmodifiableList(new ArrayList<>(messageConverters)); - this.allSupportedMediaTypes = allSupportedMediaTypes(messageConverters); this.headers = new DefaultRequestHeaders(this.serverHttpRequest.getHeaders()); this.params = CollectionUtils.toMultiValueMap(new ServletParametersMap(servletRequest)); @@ -107,13 +104,6 @@ class DefaultServerRequest implements ServerRequest { ServletRequestPathUtils.parseAndCache(servletRequest)); } - private static List allSupportedMediaTypes(List> messageConverters) { - return messageConverters.stream() - .flatMap(converter -> converter.getSupportedMediaTypes().stream()) - .sorted(MediaType.SPECIFICITY_COMPARATOR) - .collect(Collectors.toList()); - } - @Override public String methodName() { @@ -211,7 +201,14 @@ class DefaultServerRequest implements ServerRequest { return theConverter.read(clazz, this.serverHttpRequest); } } - throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes); + throw new HttpMediaTypeNotSupportedException(contentType, getSupportedMediaTypes(bodyClass)); + } + + private List getSupportedMediaTypes(Class bodyClass) { + return this.messageConverters.stream() + .flatMap(converter -> converter.getSupportedMediaTypes(bodyClass).stream()) + .sorted(MediaType.SPECIFICITY_COMPARATOR) + .collect(Collectors.toList()); } @Override diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java index 3751a03094f..2d7c20172a1 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 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. @@ -23,7 +23,6 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.EnumSet; import java.util.LinkedHashSet; import java.util.List; @@ -80,8 +79,6 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements protected final List> messageConverters; - protected final List allSupportedMediaTypes; - private final RequestResponseBodyAdviceChain advice; @@ -101,26 +98,10 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements Assert.notEmpty(converters, "'messageConverters' must not be empty"); this.messageConverters = converters; - this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters); this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice); } - /** - * Return the media types supported by all provided message converters sorted - * by specificity via {@link MediaType#sortBySpecificity(List)}. - */ - private static List getAllSupportedMediaTypes(List> messageConverters) { - Set allSupportedMediaTypes = new LinkedHashSet<>(); - for (HttpMessageConverter messageConverter : messageConverters) { - allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); - } - List result = new ArrayList<>(allSupportedMediaTypes); - MediaType.sortBySpecificity(result); - return Collections.unmodifiableList(result); - } - - /** * Return the configured {@link RequestBodyAdvice} and * {@link RequestBodyAdvice} where each instance may be wrapped as a @@ -222,7 +203,7 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements (noContentType && !message.hasBody())) { return null; } - throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes); + throw new HttpMediaTypeNotSupportedException(contentType, getSupportedMediaTypes(targetClass)); } MediaType selectedContentType = contentType; @@ -283,6 +264,21 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements return !hasBindingResult; } + /** + * Return the media types supported by all provided message converters sorted + * by specificity via {@link MediaType#sortBySpecificity(List)}. + * @since 5.3.4 + */ + protected List getSupportedMediaTypes(Class clazz) { + Set mediaTypeSet = new LinkedHashSet<>(); + for (HttpMessageConverter converter : this.messageConverters) { + mediaTypeSet.addAll(converter.getSupportedMediaTypes(clazz)); + } + List result = new ArrayList<>(mediaTypeSet); + MediaType.sortBySpecificity(result); + return result; + } + /** * Adapt the given argument against the method parameter, if necessary. * @param arg the resolved argument 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 4f1a532b98c..94748eb7d92 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 @@ -299,7 +299,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe throw new HttpMessageNotWritableException( "No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'"); } - throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes); + throw new HttpMediaTypeNotAcceptableException(getSupportedMediaTypes(body.getClass())); } } @@ -361,23 +361,18 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe if (!CollectionUtils.isEmpty(mediaTypes)) { return new ArrayList<>(mediaTypes); } - else if (!this.allSupportedMediaTypes.isEmpty()) { - List result = new ArrayList<>(); - for (HttpMessageConverter converter : this.messageConverters) { - if (converter instanceof GenericHttpMessageConverter && targetType != null) { - if (((GenericHttpMessageConverter) converter).canWrite(targetType, valueClass, null)) { - result.addAll(converter.getSupportedMediaTypes()); - } - } - else if (converter.canWrite(valueClass, null)) { - result.addAll(converter.getSupportedMediaTypes()); + List result = new ArrayList<>(); + for (HttpMessageConverter converter : this.messageConverters) { + if (converter instanceof GenericHttpMessageConverter && targetType != null) { + if (((GenericHttpMessageConverter) converter).canWrite(targetType, valueClass, null)) { + result.addAll(converter.getSupportedMediaTypes(valueClass)); } } - return result; - } - else { - return Collections.singletonList(MediaType.ALL); + else if (converter.canWrite(valueClass, null)) { + result.addAll(converter.getSupportedMediaTypes(valueClass)); + } } + return (result.isEmpty() ? Collections.singletonList(MediaType.ALL) : result); } private List getAcceptableMediaTypes(HttpServletRequest request) diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java index 574536c781f..a46de7c7b80 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -138,16 +138,15 @@ public class HttpEntityMethodProcessorMockTests { public void setup() throws Exception { stringHttpMessageConverter = mock(HttpMessageConverter.class); - given(stringHttpMessageConverter.getSupportedMediaTypes()) - .willReturn(Collections.singletonList(TEXT_PLAIN)); + given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(TEXT_PLAIN)); resourceMessageConverter = mock(HttpMessageConverter.class); - given(resourceMessageConverter.getSupportedMediaTypes()) - .willReturn(Collections.singletonList(MediaType.ALL)); + given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL)); + given(resourceMessageConverter.getSupportedMediaTypes(any())).willReturn(Collections.singletonList(MediaType.ALL)); resourceRegionMessageConverter = mock(HttpMessageConverter.class); - given(resourceRegionMessageConverter.getSupportedMediaTypes()) - .willReturn(Collections.singletonList(MediaType.ALL)); + given(resourceRegionMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL)); + given(resourceRegionMessageConverter.getSupportedMediaTypes(any())).willReturn(Collections.singletonList(MediaType.ALL)); processor = new HttpEntityMethodProcessor(Arrays.asList( stringHttpMessageConverter, resourceMessageConverter, resourceRegionMessageConverter)); @@ -241,7 +240,7 @@ public class HttpEntityMethodProcessorMockTests { servletRequest.setMethod("POST"); servletRequest.addHeader("Content-Type", contentType.toString()); - given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(contentType)); + given(stringHttpMessageConverter.getSupportedMediaTypes(any())).willReturn(Collections.singletonList(contentType)); given(stringHttpMessageConverter.canRead(String.class, contentType)).willReturn(false); assertThatExceptionOfType(HttpMediaTypeNotSupportedException.class).isThrownBy(() -> @@ -314,7 +313,7 @@ public class HttpEntityMethodProcessorMockTests { servletRequest.addHeader("Accept", accepted.toString()); given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true); - given(stringHttpMessageConverter.getSupportedMediaTypes()) + given(stringHttpMessageConverter.getSupportedMediaTypes(any())) .willReturn(Collections.singletonList(TEXT_PLAIN)); assertThatExceptionOfType(HttpMediaTypeNotAcceptableException.class).isThrownBy(() -> @@ -328,7 +327,7 @@ public class HttpEntityMethodProcessorMockTests { .body(""); given(stringHttpMessageConverter.canWrite(String.class, TEXT_PLAIN)).willReturn(true); - given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(TEXT_PLAIN)); + given(stringHttpMessageConverter.getSupportedMediaTypes(any())).willReturn(Collections.singletonList(TEXT_PLAIN)); assertThatThrownBy(() -> processor.handleReturnValue( @@ -345,7 +344,7 @@ public class HttpEntityMethodProcessorMockTests { ResponseEntity returnValue = ResponseEntity.ok().body(""); given(stringHttpMessageConverter.canWrite(String.class, TEXT_PLAIN)).willReturn(true); - given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(TEXT_PLAIN)); + given(stringHttpMessageConverter.getSupportedMediaTypes(any())).willReturn(Collections.singletonList(TEXT_PLAIN)); assertThatThrownBy(() -> processor.handleReturnValue( @@ -362,7 +361,7 @@ public class HttpEntityMethodProcessorMockTests { servletRequest.addHeader("Accept", accepted.toString()); given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true); - given(stringHttpMessageConverter.getSupportedMediaTypes()) + given(stringHttpMessageConverter.getSupportedMediaTypes(any())) .willReturn(Collections.singletonList(TEXT_PLAIN)); given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(false); @@ -575,7 +574,7 @@ public class HttpEntityMethodProcessorMockTests { .ok(new ByteArrayResource("Content".getBytes(StandardCharsets.UTF_8))); given(resourceMessageConverter.canWrite(ByteArrayResource.class, null)).willReturn(true); - given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL)); + given(resourceMessageConverter.getSupportedMediaTypes(any())).willReturn(Collections.singletonList(MediaType.ALL)); given(resourceMessageConverter.canWrite(ByteArrayResource.class, APPLICATION_OCTET_STREAM)).willReturn(true); processor.handleReturnValue(returnValue, returnTypeResponseEntityResource, mavContainer, webRequest); @@ -735,6 +734,7 @@ public class HttpEntityMethodProcessorMockTests { private void initStringMessageConversion(MediaType accepted) { given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true); given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(TEXT_PLAIN)); + given(stringHttpMessageConverter.getSupportedMediaTypes(String.class)).willReturn(Collections.singletonList(TEXT_PLAIN)); given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(true); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorMockTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorMockTests.java index a35f4e2384d..b805e00a499 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorMockTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorMockTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -113,10 +113,13 @@ public class RequestResponseBodyMethodProcessorMockTests { public void setup() throws Exception { stringMessageConverter = mock(HttpMessageConverter.class); given(stringMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN)); + given(stringMessageConverter.getSupportedMediaTypes(any())).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN)); resourceMessageConverter = mock(HttpMessageConverter.class); given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL)); + given(resourceMessageConverter.getSupportedMediaTypes(any())).willReturn(Collections.singletonList(MediaType.ALL)); resourceRegionMessageConverter = mock(HttpMessageConverter.class); given(resourceRegionMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL)); + given(resourceRegionMessageConverter.getSupportedMediaTypes(any())).willReturn(Collections.singletonList(MediaType.ALL)); processor = new RequestResponseBodyMethodProcessor( Arrays.asList(stringMessageConverter, resourceMessageConverter, resourceRegionMessageConverter)); @@ -388,7 +391,7 @@ public class RequestResponseBodyMethodProcessorMockTests { servletRequest.addHeader("Accept", accepted); given(stringMessageConverter.canWrite(String.class, null)).willReturn(true); - given(stringMessageConverter.getSupportedMediaTypes()).willReturn(supported); + given(stringMessageConverter.getSupportedMediaTypes(any())).willReturn(supported); given(stringMessageConverter.canWrite(String.class, accepted)).willReturn(true); processor.handleReturnValue(body, returnTypeStringProduces, mavContainer, webRequest); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java index 09e546ad564..75d1217408a 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -29,6 +29,8 @@ import java.util.List; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonView; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -85,6 +87,9 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException @SuppressWarnings("unused") public class RequestResponseBodyMethodProcessorTests { + protected static final String NEWLINE_SYSTEM_PROPERTY = System.getProperty("line.separator"); + + private ModelAndViewContainer container; private MockHttpServletRequest servletRequest; @@ -358,9 +363,37 @@ public class RequestResponseBodyMethodProcessorTests { assertThat(this.servletResponse.getHeader("Content-Type")).isEqualTo("image/jpeg"); } - // SPR-13135 + @Test // gh-26212 + public void handleReturnValueWithObjectMapperByTypeRegistration() throws Exception { + MediaType halFormsMediaType = MediaType.parseMediaType("application/prs.hal-forms+json"); + MediaType halMediaType = MediaType.parseMediaType("application/hal+json"); - @Test + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true); + + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + converter.registerObjectMappersForType(SimpleBean.class, map -> map.put(halMediaType, objectMapper)); + + this.servletRequest.addHeader("Accept", halFormsMediaType + "," + halMediaType); + + SimpleBean simpleBean = new SimpleBean(); + simpleBean.setId(12L); + simpleBean.setName("Jason"); + + RequestResponseBodyMethodProcessor processor = + new RequestResponseBodyMethodProcessor(Collections.singletonList(converter)); + MethodParameter returnType = new MethodParameter(getClass().getDeclaredMethod("getSimpleBean"), -1); + processor.writeWithMessageConverters(simpleBean, returnType, this.request); + + assertThat(this.servletResponse.getHeader("Content-Type")).isEqualTo(halMediaType.toString()); + assertThat(this.servletResponse.getContentAsString()).isEqualTo( + "{" + NEWLINE_SYSTEM_PROPERTY + + " \"id\" : 12," + NEWLINE_SYSTEM_PROPERTY + + " \"name\" : \"Jason\"" + NEWLINE_SYSTEM_PROPERTY + + "}"); + } + + @Test // SPR-13135 public void handleReturnValueWithInvalidReturnType() throws Exception { Method method = getClass().getDeclaredMethod("handleAndReturnOutputStream"); MethodParameter returnType = new MethodParameter(method, -1); @@ -778,6 +811,10 @@ public class RequestResponseBodyMethodProcessorTests { return null; } + SimpleBean getSimpleBean() { + return null; + } + private static abstract class MyParameterizedController { diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java index 0f962a958b5..e6e6ceb3ca8 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java @@ -92,7 +92,6 @@ import org.springframework.http.HttpOutputMessage; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; @@ -961,7 +960,9 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl void unsupportedRequestBody(boolean usePathPatterns) throws Exception { initDispatcherServlet(RequestResponseBodyController.class, usePathPatterns, wac -> { RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); - adapterDef.getPropertyValues().add("messageConverters", new ByteArrayHttpMessageConverter()); + StringHttpMessageConverter converter = new StringHttpMessageConverter(); + converter.setSupportedMediaTypes(Collections.singletonList(MediaType.TEXT_PLAIN)); + adapterDef.getPropertyValues().add("messageConverters", converter); wac.registerBeanDefinition("handlerAdapter", adapterDef); }); @@ -972,7 +973,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl MockHttpServletResponse response = new MockHttpServletResponse(); getServlet().service(request, response); assertThat(response.getStatus()).isEqualTo(415); - assertThat(response.getHeader("Accept")).as("No Accept response header set").isNotNull(); + assertThat(response.getHeader("Accept")).isEqualTo("text/plain"); } @PathPatternsParameterizedTest