Add HttpMessageConverters

Prior to this commit, Spring Web would configure
`HttpMessageConverter<?>` collections on clients like `RestTemplate` and
on the server infrastructure, in `WebMvcConfigurationSupport`.

This commit introduces a high-level construct for building and
configuring ordered collections of converters.

This includes:
* configuration of well-known converters with classpath detection
* configuration of shared converters, or client/server specific
* configuration of custom converters

See gh-33894
This commit is contained in:
Brian Clozel 2025-06-26 17:18:23 +02:00
parent 4b44a34692
commit 1af25e9cb1
3 changed files with 1237 additions and 0 deletions

View File

@ -0,0 +1,604 @@
/*
* Copyright 2002-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.converter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
import org.springframework.http.MediaType;
import org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter;
import org.springframework.http.converter.cbor.KotlinSerializationCborHttpMessageConverter;
import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.protobuf.KotlinSerializationProtobufHttpMessageConverter;
import org.springframework.http.converter.smile.JacksonSmileHttpMessageConverter;
import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.http.converter.yaml.JacksonYamlHttpMessageConverter;
import org.springframework.http.converter.yaml.MappingJackson2YamlHttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Default implementation for {@link HttpMessageConverters}.
*
* @author Brian Clozel
*/
@SuppressWarnings("removal")
class DefaultHttpMessageConverters implements HttpMessageConverters {
private final List<HttpMessageConverter<?>> clientMessageConverters;
private final List<HttpMessageConverter<?>> serverMessageConverters;
DefaultHttpMessageConverters(List<HttpMessageConverter<?>> clientMessageConverters, List<HttpMessageConverter<?>> serverMessageConverters) {
this.clientMessageConverters = clientMessageConverters;
this.serverMessageConverters = serverMessageConverters;
}
@Override
public Iterable<HttpMessageConverter<?>> forClient() {
return this.clientMessageConverters;
}
@Override
public Iterable<HttpMessageConverter<?>> forServer() {
return this.serverMessageConverters;
}
static class DefaultBuilder implements HttpMessageConverters.Builder {
private final DefaultMessageConverterConfigurer commonMessageConverters;
private final DefaultClientMessageConverterConfigurer clientMessageConverterConfigurer;
private final DefaultServerMessageConverterConfigurer serverMessageConverterConfigurer;
DefaultBuilder(boolean registerDefaults) {
this(registerDefaults, DefaultHttpMessageConverters.class.getClassLoader());
}
DefaultBuilder(boolean registerDefaults, ClassLoader classLoader) {
this.commonMessageConverters = new DefaultMessageConverterConfigurer();
this.clientMessageConverterConfigurer = new DefaultClientMessageConverterConfigurer(this.commonMessageConverters);
this.serverMessageConverterConfigurer = new DefaultServerMessageConverterConfigurer(this.commonMessageConverters);
if (registerDefaults) {
this.commonMessageConverters.registerDefaults(classLoader);
this.clientMessageConverterConfigurer.registerDefaults(classLoader);
this.serverMessageConverterConfigurer.registerDefaults(classLoader);
}
}
@Override
public Builder configureClient(Consumer<ClientMessageConverterConfigurer> consumer) {
consumer.accept(this.clientMessageConverterConfigurer);
return this;
}
@Override
public Builder configureServer(Consumer<ServerMessageConverterConfigurer> consumer) {
consumer.accept(this.serverMessageConverterConfigurer);
return this;
}
@Override
public Builder stringMessageConverter(HttpMessageConverter<?> stringMessageConverter) {
this.commonMessageConverters.setStringMessageConverter(stringMessageConverter);
return this;
}
@Override
public DefaultBuilder jsonMessageConverter(HttpMessageConverter<?> jsonMessageConverter) {
this.commonMessageConverters.setJsonMessageConverter(jsonMessageConverter);
return this;
}
@Override
public DefaultBuilder xmlMessageConverter(HttpMessageConverter<?> xmlMessageConverter) {
this.commonMessageConverters.setXmlMessageConverter(xmlMessageConverter);
return this;
}
@Override
public DefaultBuilder smileMessageConverter(HttpMessageConverter<?> smileMessageConverter) {
this.commonMessageConverters.setSmileMessageConverter(smileMessageConverter);
return this;
}
@Override
public Builder cborMessageConverter(HttpMessageConverter<?> cborMessageConverter) {
this.commonMessageConverters.setCborMessageConverter(cborMessageConverter);
return this;
}
@Override
public Builder yamlMessageConverter(HttpMessageConverter<?> yamlMessageConverter) {
this.commonMessageConverters.setYamlMessageConverter(yamlMessageConverter);
return this;
}
@Override
public DefaultBuilder additionalMessageConverter(HttpMessageConverter<?> customConverter) {
Assert.notNull(customConverter, "'customConverter' must not be null");
this.commonMessageConverters.additionalMessageConverters.add(customConverter);
return this;
}
@Override
public DefaultHttpMessageConverters build() {
return new DefaultHttpMessageConverters(this.clientMessageConverterConfigurer.getMessageConverters(),
this.serverMessageConverterConfigurer.getMessageConverters());
}
}
static class DefaultMessageConverterConfigurer {
private final @Nullable DefaultMessageConverterConfigurer inheritedMessageConverters;
private @Nullable ByteArrayHttpMessageConverter byteArrayMessageConverter;
private @Nullable HttpMessageConverter<?> stringMessageConverter;
List<HttpMessageConverter<?>> resourceMessageConverters = Collections.emptyList();
private @Nullable HttpMessageConverter<?> jsonMessageConverter;
private @Nullable HttpMessageConverter<?> xmlMessageConverter;
private @Nullable HttpMessageConverter<?> smileMessageConverter;
private @Nullable HttpMessageConverter<?> cborMessageConverter;
private @Nullable HttpMessageConverter<?> yamlMessageConverter;
private final List<HttpMessageConverter<?>> additionalMessageConverters = new ArrayList<>();
DefaultMessageConverterConfigurer() {
this(null);
}
DefaultMessageConverterConfigurer(@Nullable DefaultMessageConverterConfigurer inheritedMessageConverters) {
this.inheritedMessageConverters = inheritedMessageConverters;
}
void setStringMessageConverter(HttpMessageConverter<?> stringMessageConverter) {
Assert.isTrue(stringMessageConverter.getSupportedMediaTypes().contains(MediaType.TEXT_PLAIN),
"stringMessageConverter should support 'text/plain'");
this.stringMessageConverter = stringMessageConverter;
}
void setJsonMessageConverter(HttpMessageConverter<?> jsonMessageConverter) {
Assert.isTrue(jsonMessageConverter.getSupportedMediaTypes().contains(MediaType.APPLICATION_JSON),
"jsonMessageConverter should support 'application/json'");
this.jsonMessageConverter = jsonMessageConverter;
}
void setXmlMessageConverter(HttpMessageConverter<?> xmlMessageConverter) {
Assert.isTrue(xmlMessageConverter.getSupportedMediaTypes().contains(MediaType.TEXT_XML),
"xmlMessageConverter should support 'text/xml'");
this.xmlMessageConverter = xmlMessageConverter;
}
void setSmileMessageConverter(HttpMessageConverter<?> smileMessageConverter) {
Assert.isTrue(smileMessageConverter.getSupportedMediaTypes().contains(new MediaType("application", "x-jackson-smile")),
"smileMessageConverter should support 'application/x-jackson-smile'");
this.smileMessageConverter = smileMessageConverter;
}
void setCborMessageConverter(HttpMessageConverter<?> cborMessageConverter) {
Assert.isTrue(cborMessageConverter.getSupportedMediaTypes().contains(MediaType.APPLICATION_CBOR),
"cborMessageConverter should support 'application/cbor'");
this.cborMessageConverter = cborMessageConverter;
}
void setYamlMessageConverter(HttpMessageConverter<?> yamlMessageConverter) {
Assert.isTrue(yamlMessageConverter.getSupportedMediaTypes().contains(MediaType.APPLICATION_YAML),
"yamlMessageConverter should support 'application/yaml'");
this.yamlMessageConverter = yamlMessageConverter;
}
List<HttpMessageConverter<?>> getBaseConverters() {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
if (this.byteArrayMessageConverter != null) {
converters.add(this.byteArrayMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.byteArrayMessageConverter != null) {
converters.add(this.inheritedMessageConverters.byteArrayMessageConverter);
}
if (this.stringMessageConverter != null) {
converters.add(this.stringMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.stringMessageConverter != null) {
converters.add(this.inheritedMessageConverters.stringMessageConverter);
}
return converters;
}
List<HttpMessageConverter<?>> getCoreConverters() {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
if (this.jsonMessageConverter != null) {
converters.add(this.jsonMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.jsonMessageConverter != null) {
converters.add(this.inheritedMessageConverters.jsonMessageConverter);
}
if (this.smileMessageConverter != null) {
converters.add(this.smileMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.smileMessageConverter != null) {
converters.add(this.inheritedMessageConverters.smileMessageConverter);
}
if (this.cborMessageConverter!= null) {
converters.add(this.cborMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.cborMessageConverter != null) {
converters.add(this.inheritedMessageConverters.cborMessageConverter);
}
if (this.yamlMessageConverter!= null) {
converters.add(this.yamlMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.yamlMessageConverter != null) {
converters.add(this.inheritedMessageConverters.yamlMessageConverter);
}
if (this.xmlMessageConverter!= null) {
converters.add(this.xmlMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.xmlMessageConverter != null) {
converters.add(this.inheritedMessageConverters.xmlMessageConverter);
}
return converters;
}
List<HttpMessageConverter<?>> getCustomConverters() {
List<HttpMessageConverter<?>> result = new ArrayList<>(this.additionalMessageConverters);
if (this.inheritedMessageConverters != null) {
result.addAll(this.inheritedMessageConverters.additionalMessageConverters);
}
return result;
}
void registerDefaults(ClassLoader classLoader) {
this.byteArrayMessageConverter = new ByteArrayHttpMessageConverter();
this.stringMessageConverter = new StringHttpMessageConverter();
if (isJacksonPresent(classLoader)) {
this.jsonMessageConverter = new JacksonJsonHttpMessageConverter();
}
else if (isJackson2Present(classLoader)) {
this.jsonMessageConverter = new MappingJackson2HttpMessageConverter();
}
else if (isGsonPresent(classLoader)) {
this.jsonMessageConverter = new GsonHttpMessageConverter();
}
else if (isJsonbPresent(classLoader)) {
this.jsonMessageConverter = new JsonbHttpMessageConverter();
}
else if (isKotlinSerializationJsonPresent(classLoader)) {
this.jsonMessageConverter = new KotlinSerializationJsonHttpMessageConverter();
}
if (isJacksonXmlPresent(classLoader)) {
this.xmlMessageConverter = new JacksonXmlHttpMessageConverter();
}
else if (isJackson2XmlPresent(classLoader)) {
this.xmlMessageConverter = new MappingJackson2XmlHttpMessageConverter();
}
else if (isJaxb2Present(classLoader)) {
this.xmlMessageConverter = new Jaxb2RootElementHttpMessageConverter();
}
if (isJacksonSmilePresent(classLoader)) {
this.smileMessageConverter = new JacksonSmileHttpMessageConverter();
}
else if (isJackson2SmilePresent(classLoader)) {
this.smileMessageConverter = new MappingJackson2SmileHttpMessageConverter();
}
if (isJacksonCborPresent(classLoader)) {
this.cborMessageConverter = new JacksonCborHttpMessageConverter();
}
else if (isJackson2CborPresent(classLoader)) {
this.cborMessageConverter = new MappingJackson2CborHttpMessageConverter();
}
else if (isKotlinSerializationCborPresent(classLoader)) {
this.cborMessageConverter = new KotlinSerializationCborHttpMessageConverter();
}
if (isJacksonYamlPresent(classLoader)) {
this.yamlMessageConverter = new JacksonYamlHttpMessageConverter();
}
else if (isJackson2YamlPresent(classLoader)) {
this.yamlMessageConverter = new MappingJackson2YamlHttpMessageConverter();
}
if (isKotlinSerializationProtobufPresent(classLoader)) {
this.additionalMessageConverters.add(new KotlinSerializationProtobufHttpMessageConverter());
}
if (isRomePresent(classLoader)) {
this.additionalMessageConverters.add(new AtomFeedHttpMessageConverter());
this.additionalMessageConverters.add(new RssChannelHttpMessageConverter());
}
}
private static boolean isRomePresent(ClassLoader classLoader) {
return ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
}
private static boolean isJacksonPresent(ClassLoader classLoader) {
return ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader);
}
private static boolean isJackson2Present(ClassLoader classLoader) {
return ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
}
private static boolean isGsonPresent(ClassLoader classLoader) {
return ClassUtils.isPresent("com.google.gson.Gson", classLoader);
}
private static boolean isJsonbPresent(ClassLoader classLoader) {
return ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader);
}
private static boolean isKotlinSerializationJsonPresent(ClassLoader classLoader) {
return ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader);
}
private static boolean isJacksonSmilePresent(ClassLoader classLoader) {
return isJacksonPresent(classLoader) && ClassUtils.isPresent("tools.jackson.dataformat.smile.SmileMapper", classLoader);
}
private static boolean isJackson2SmilePresent(ClassLoader classLoader) {
return ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
}
private static boolean isJaxb2Present(ClassLoader classLoader) {
return ClassUtils.isPresent("jakarta.xml.bind.Binder", classLoader);
}
private static boolean isJacksonXmlPresent(ClassLoader classLoader) {
return isJacksonPresent(classLoader) && ClassUtils.isPresent("tools.jackson.dataformat.xml.XmlMapper", classLoader);
}
private static boolean isJackson2XmlPresent(ClassLoader classLoader) {
return ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
}
private static boolean isJacksonCborPresent(ClassLoader classLoader) {
return isJacksonPresent(classLoader) && ClassUtils.isPresent("tools.jackson.dataformat.cbor.CBORMapper", classLoader);
}
private static boolean isJackson2CborPresent(ClassLoader classLoader) {
return ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
}
private static boolean isJacksonYamlPresent(ClassLoader classLoader) {
return isJacksonPresent(classLoader) && ClassUtils.isPresent("tools.jackson.dataformat.yaml.YAMLMapper", classLoader);
}
private static boolean isJackson2YamlPresent(ClassLoader classLoader) {
return ClassUtils.isPresent("com.fasterxml.jackson.dataformat.yaml.YAMLFactory", classLoader);
}
private static boolean isKotlinSerializationCborPresent(ClassLoader classLoader) {
return ClassUtils.isPresent("kotlinx.serialization.cbor.Cbor", classLoader);
}
private static boolean isKotlinSerializationProtobufPresent(ClassLoader classLoader) {
return ClassUtils.isPresent("kotlinx.serialization.protobuf.ProtoBuf", classLoader);
}
}
static class DefaultClientMessageConverterConfigurer extends DefaultMessageConverterConfigurer implements ClientMessageConverterConfigurer {
private @Nullable Consumer<HttpMessageConverter<?>> configurer;
private final DefaultMessageConverterConfigurer clientMessageConverters;
public DefaultClientMessageConverterConfigurer(DefaultMessageConverterConfigurer parentMessageConverters) {
this.clientMessageConverters = new DefaultMessageConverterConfigurer(parentMessageConverters);
}
@Override
public ClientMessageConverterConfigurer stringMessageConverter(HttpMessageConverter<?> stringMessageConverter) {
this.clientMessageConverters.setStringMessageConverter(stringMessageConverter);
return this;
}
@Override
public ClientMessageConverterConfigurer jsonMessageConverter(HttpMessageConverter<?> jsonMessageConverter) {
this.clientMessageConverters.setJsonMessageConverter(jsonMessageConverter);
return this;
}
@Override
public ClientMessageConverterConfigurer xmlMessageConverter(HttpMessageConverter<?> xmlMessageConverter) {
this.clientMessageConverters.setXmlMessageConverter(xmlMessageConverter);
return this;
}
@Override
public ClientMessageConverterConfigurer smileMessageConverter(HttpMessageConverter<?> smileMessageConverter) {
this.clientMessageConverters.setSmileMessageConverter(smileMessageConverter);
return this;
}
@Override
public ClientMessageConverterConfigurer cborMessageConverter(HttpMessageConverter<?> cborMessageConverter) {
this.clientMessageConverters.setCborMessageConverter(cborMessageConverter);
return this;
}
@Override
public ClientMessageConverterConfigurer yamlMessageConverter(HttpMessageConverter<?> yamlMessageConverter) {
this.clientMessageConverters.setYamlMessageConverter(yamlMessageConverter);
return this;
}
@Override
public ClientMessageConverterConfigurer additionalMessageConverter(HttpMessageConverter<?> customConverter) {
Assert.notNull(customConverter, "'customConverter' must not be null");
this.clientMessageConverters.additionalMessageConverters.add(customConverter);
return this;
}
@Override
public ClientMessageConverterConfigurer configureClientMessageConverters(Consumer<HttpMessageConverter<?>> configurer) {
this.configurer = (this.configurer != null) ? configurer.andThen(this.configurer) : configurer;
return this;
}
@Override
void registerDefaults(ClassLoader classLoader) {
this.resourceMessageConverters = Collections.singletonList(new ResourceHttpMessageConverter(false));
}
List<HttpMessageConverter<?>> getMessageConverters() {
List<HttpMessageConverter<?>> allConverters = new ArrayList<>();
List<HttpMessageConverter<?>> partConverters = new ArrayList<>();
partConverters.addAll(this.clientMessageConverters.getCoreConverters());
partConverters.addAll(this.clientMessageConverters.getCustomConverters());
allConverters.addAll(this.clientMessageConverters.getBaseConverters());
allConverters.addAll(this.resourceMessageConverters);
if (!partConverters.isEmpty()) {
allConverters.add(new AllEncompassingFormHttpMessageConverter(partConverters));
}
allConverters.addAll(this.clientMessageConverters.getCoreConverters());
allConverters.addAll(this.clientMessageConverters.getCustomConverters());
if (this.configurer != null) {
allConverters.forEach(this.configurer);
}
return allConverters;
}
}
static class DefaultServerMessageConverterConfigurer extends DefaultMessageConverterConfigurer implements ServerMessageConverterConfigurer {
private @Nullable Consumer<HttpMessageConverter<?>> configurer;
private final DefaultMessageConverterConfigurer serverMessageConverters;
DefaultServerMessageConverterConfigurer(DefaultMessageConverterConfigurer commonMessageConverters) {
this.serverMessageConverters = new DefaultMessageConverterConfigurer(commonMessageConverters);
}
@Override
public ServerMessageConverterConfigurer stringMessageConverter(HttpMessageConverter<?> stringMessageConverter) {
this.serverMessageConverters.setStringMessageConverter(stringMessageConverter);
return this;
}
@Override
public ServerMessageConverterConfigurer jsonMessageConverter(HttpMessageConverter<?> jsonMessageConverter) {
this.serverMessageConverters.setJsonMessageConverter(jsonMessageConverter);
return this;
}
@Override
public ServerMessageConverterConfigurer xmlMessageConverter(HttpMessageConverter<?> xmlMessageConverter) {
this.serverMessageConverters.setXmlMessageConverter(xmlMessageConverter);
return this;
}
@Override
public ServerMessageConverterConfigurer smileMessageConverter(HttpMessageConverter<?> smileMessageConverter) {
this.serverMessageConverters.setSmileMessageConverter(smileMessageConverter);
return this;
}
@Override
public ServerMessageConverterConfigurer cborMessageConverter(HttpMessageConverter<?> cborMessageConverter) {
this.serverMessageConverters.setCborMessageConverter(cborMessageConverter);
return this;
}
@Override
public ServerMessageConverterConfigurer yamlMessageConverter(HttpMessageConverter<?> yamlMessageConverter) {
this.serverMessageConverters.setYamlMessageConverter(yamlMessageConverter);
return this;
}
@Override
public ServerMessageConverterConfigurer additionalMessageConverter(HttpMessageConverter<?> customConverter) {
Assert.notNull(customConverter, "'customConverter' must not be null");
this.serverMessageConverters.additionalMessageConverters.add(customConverter);
return this;
}
@Override
public ServerMessageConverterConfigurer configureServerMessageConverters(Consumer<HttpMessageConverter<?>> configurer) {
this.configurer = (this.configurer != null) ? configurer.andThen(this.configurer) : configurer;
return this;
}
@Override
void registerDefaults(ClassLoader classLoader) {
this.resourceMessageConverters = Arrays.asList(new ResourceHttpMessageConverter(), new ResourceRegionHttpMessageConverter());
}
List<HttpMessageConverter<?>> getMessageConverters() {
List<HttpMessageConverter<?>> allConverters = new ArrayList<>();
List<HttpMessageConverter<?>> partConverters = new ArrayList<>();
partConverters.addAll(this.serverMessageConverters.getCoreConverters());
partConverters.addAll(this.serverMessageConverters.getCustomConverters());
allConverters.addAll(this.serverMessageConverters.getBaseConverters());
allConverters.addAll(this.resourceMessageConverters);
allConverters.addAll(this.serverMessageConverters.getCoreConverters());
allConverters.addAll(this.serverMessageConverters.getCustomConverters());
if (!partConverters.isEmpty()) {
allConverters.add(new AllEncompassingFormHttpMessageConverter(partConverters));
}
if (this.configurer != null) {
allConverters.forEach(this.configurer);
}
return allConverters;
}
}
}

View File

@ -0,0 +1,199 @@
/*
* Copyright 2002-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.converter;
import java.util.function.Consumer;
/**
* Utility for building and configuring an immutable collection of {@link HttpMessageConverter}
* instances for client and server usage. You can {@link #create() create}
* a new empty instance or ask to {@link #withDefaults() register default converters},
* if available in your classpath.
*
* <p>This class offers a flexible arrangement for {@link HttpMessageConverters.Builder configuring message converters shared between}
* client and server, or {@link HttpMessageConverters.Builder#configureClient(Consumer) configuring client-specific}
* and {@link HttpMessageConverters.Builder#configureServer(Consumer) server-specific} converters.
*
* <p>The following HTTP message converters will be detected and registered if available, in order.
* For {@link #forClient() client side converters}:
* <ol>
* <li>{@link ByteArrayHttpMessageConverter}
* <li>{@link StringHttpMessageConverter} with the {@link java.nio.charset.StandardCharsets#ISO_8859_1} charset
* <li>{@link ResourceHttpMessageConverter}, with resource streaming support disabled
* <li>a Multipart converter, using all detected and custom converters for part conversion
* <li>A JSON converter
* <li>A Smile converter
* <li>A CBOR converter
* <li>A YAML converter
* <li>An XML converter
* <li>An ProtoBuf converter
* <li>ATOM and RSS converters
* <li>All custom message converters configured with the builder
* </ol>
*
* For {@link #forClient() client side converters}:
* <ol>
* <li>{@link ByteArrayHttpMessageConverter}
* <li>{@link StringHttpMessageConverter} with the {@link java.nio.charset.StandardCharsets#ISO_8859_1} charset
* <li>{@link ResourceHttpMessageConverter}
* <li>{@link ResourceRegionHttpMessageConverter}
* <li>A JSON converter
* <li>A Smile converter
* <li>A CBOR converter
* <li>A YAML converter
* <li>An XML converter
* <li>An ProtoBuf converter
* <li>ATOM and RSS converters
* <li>All custom message converters configured with the builder
* <li>a Multipart converter, using all detected and custom converters for part conversion
* </ol>
*
* @author Brian Clozel
* @since 7.0
*/
public interface HttpMessageConverters {
/**
* Return the list of configured message converters, tailored for HTTP client usage.
*/
Iterable<HttpMessageConverter<?>> forClient();
/**
* Return the list of configured message converters, tailored for HTTP server usage.
*/
Iterable<HttpMessageConverter<?>> forServer();
/**
* Create a builder instance, without any message converter pre-configured.
*/
static Builder create() {
return new DefaultHttpMessageConverters.DefaultBuilder(false);
}
/**
* Create a builder instance with default message converters pre-configured.
*/
static Builder withDefaults() {
return new DefaultHttpMessageConverters.DefaultBuilder(true);
}
interface MessageConverterConfigurer<T extends MessageConverterConfigurer<T>> {
/**
* Override the default String {@code HttpMessageConverter}
* with any converter supporting String conversion.
* @param stringMessageConverter the converter instance to use
* @see StringHttpMessageConverter
*/
T stringMessageConverter(HttpMessageConverter<?> stringMessageConverter);
/**
* Override the default Jackson 3.x JSON {@code HttpMessageConverter}
* with any converter supporting the JSON format.
* @param jsonMessageConverter the converter instance to use
* @see org.springframework.http.converter.json.JacksonJsonHttpMessageConverter
*/
T jsonMessageConverter(HttpMessageConverter<?> jsonMessageConverter);
/**
* Override the default Jackson 3.x XML {@code HttpMessageConverter}
* with any converter supporting the XML format.
* @param xmlMessageConverter the converter instance to use
* @see org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter
*/
T xmlMessageConverter(HttpMessageConverter<?> xmlMessageConverter);
/**
* Override the default Jackson 3.x Smile {@code HttpMessageConverter}
* with any converter supporting the Smile format.
* @param smileMessageConverter the converter instance to use
* @see org.springframework.http.converter.smile.JacksonSmileHttpMessageConverter
*/
T smileMessageConverter(HttpMessageConverter<?> smileMessageConverter);
/**
* Override the default Jackson 3.x CBOR {@code HttpMessageConverter}
* with any converter supporting the CBOR format.
* @param cborMessageConverter the converter instance to use
* @see org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter
*/
T cborMessageConverter(HttpMessageConverter<?> cborMessageConverter);
/**
* Override the default Jackson 3.x Yaml {@code HttpMessageConverter}
* with any converter supporting the Yaml format.
* @param yamlMessageConverter the converter instance to use
* @see org.springframework.http.converter.yaml.JacksonYamlHttpMessageConverter
*/
T yamlMessageConverter(HttpMessageConverter<?> yamlMessageConverter);
/**
* Add a custom {@code HttpMessageConverter} to the list of converters.
* @param customConverter the converter instance to add
*/
T additionalMessageConverter(HttpMessageConverter<?> customConverter);
}
/**
* Builder for an {@link HttpMessageConverters}.
* This builder manages the configuration of common and client/server-specific message converters.
*/
interface Builder extends MessageConverterConfigurer<Builder> {
/**
* Configure client-specific message converters.
* If no opinion is provided here, message converters defined in this builder will be used.
*/
Builder configureClient(Consumer<ClientMessageConverterConfigurer> consumer);
/**
* Configure server-specific message converters.
* If no opinion is provided here, message converters defined in this builder will be used.
*/
Builder configureServer(Consumer<ServerMessageConverterConfigurer> consumer);
/**
* Build and return the {@link HttpMessageConverters} instance configured by this builder.
*/
HttpMessageConverters build();
}
interface ClientMessageConverterConfigurer extends MessageConverterConfigurer<ClientMessageConverterConfigurer> {
/**
* Register a consumer to apply to configured converter instances.
* This can be used to configure rather than replace one or more specific converters.
* @param configurer the consumer to apply
*/
ClientMessageConverterConfigurer configureClientMessageConverters(Consumer<HttpMessageConverter<?>> configurer);
}
interface ServerMessageConverterConfigurer extends MessageConverterConfigurer<ServerMessageConverterConfigurer> {
/**
* Register a consumer to apply to configured converter instances.
* This can be used to configure rather than replace one or more specific converters.
* @param configurer the consumer to apply
*/
ServerMessageConverterConfigurer configureServerMessageConverters(Consumer<HttpMessageConverter<?>> configurer);
}
}

View File

@ -0,0 +1,434 @@
/*
* Copyright 2002-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.converter;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import com.google.gson.Gson;
import com.rometools.rome.feed.WireFeed;
import jakarta.json.bind.Jsonb;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.dataformat.cbor.CBORMapper;
import tools.jackson.dataformat.smile.SmileMapper;
import tools.jackson.dataformat.xml.XmlMapper;
import tools.jackson.dataformat.yaml.YAMLMapper;
import org.springframework.core.SmartClassLoader;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter;
import org.springframework.http.converter.cbor.KotlinSerializationCborHttpMessageConverter;
import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.protobuf.KotlinSerializationProtobufHttpMessageConverter;
import org.springframework.http.converter.smile.JacksonSmileHttpMessageConverter;
import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.http.converter.yaml.JacksonYamlHttpMessageConverter;
import org.springframework.http.converter.yaml.MappingJackson2YamlHttpMessageConverter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link DefaultHttpMessageConverters}.
*/
@SuppressWarnings("removal")
class DefaultHttpMessageConvertersTests {
@ParameterizedTest
@MethodSource("emptyMessageConverters")
void emptyConverters(Iterable<HttpMessageConverter<?>> converters) {
assertThat(converters).isEmpty();
}
static Stream<Arguments> emptyMessageConverters() {
return Stream.of(
Arguments.of(HttpMessageConverters.create().build().forClient()),
Arguments.of(HttpMessageConverters.create().build().forServer())
);
}
@Test
void clientAndServerConvertersAreShared() {
var converters = HttpMessageConverters.withDefaults().build();
Set<HttpMessageConverter<?>> allConverters = new HashSet<>();
converters.forClient().forEach(allConverters::add);
converters.forServer().forEach(allConverters::add);
assertThat(allConverters).hasSize(15);
}
@Test
void failsWhenStringConverterDoesNotSupportMediaType() {
assertThatIllegalArgumentException().isThrownBy(() ->
HttpMessageConverters.create().stringMessageConverter(new CustomHttpMessageConverter()).build())
.withMessage("stringMessageConverter should support 'text/plain'");
}
@Test
void failsWhenJsonConverterDoesNotSupportMediaType() {
assertThatIllegalArgumentException().isThrownBy(() ->
HttpMessageConverters.create().jsonMessageConverter(new CustomHttpMessageConverter()).build())
.withMessage("jsonMessageConverter should support 'application/json'");
}
@Test
void failsWhenXmlConverterDoesNotSupportMediaType() {
assertThatIllegalArgumentException().isThrownBy(() ->
HttpMessageConverters.create().xmlMessageConverter(new CustomHttpMessageConverter()).build())
.withMessage("xmlMessageConverter should support 'text/xml'");
}
@Test
void failsWhenSmileConverterDoesNotSupportMediaType() {
assertThatIllegalArgumentException().isThrownBy(() ->
HttpMessageConverters.create().smileMessageConverter(new CustomHttpMessageConverter()).build())
.withMessage("smileMessageConverter should support 'application/x-jackson-smile'");
}
@Test
void failsWhenCborConverterDoesNotSupportMediaType() {
assertThatIllegalArgumentException().isThrownBy(() ->
HttpMessageConverters.create().cborMessageConverter(new CustomHttpMessageConverter()).build())
.withMessage("cborMessageConverter should support 'application/cbor'");
}
@Test
void failsWhenYamlConverterDoesNotSupportMediaType() {
assertThatIllegalArgumentException().isThrownBy(() ->
HttpMessageConverters.create().yamlMessageConverter(new CustomHttpMessageConverter()).build())
.withMessage("yamlMessageConverter should support 'application/yaml'");
}
@Nested
class ClientConvertersTests {
@Test
void defaultConverters() {
var converters = HttpMessageConverters.withDefaults().build();
assertThat(converters.forClient()).hasExactlyElementsOfTypes(ByteArrayHttpMessageConverter.class,
StringHttpMessageConverter.class, ResourceHttpMessageConverter.class,
AllEncompassingFormHttpMessageConverter.class,
JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class,
JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class,
JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class,
AtomFeedHttpMessageConverter.class, RssChannelHttpMessageConverter.class);
}
@Test
void multipartConverterContainsOtherConverters() {
var converters = HttpMessageConverters.withDefaults().build();
AllEncompassingFormHttpMessageConverter multipartConverter = findMessageConverter(AllEncompassingFormHttpMessageConverter.class, converters.forClient());
assertThat(multipartConverter.getPartConverters()).hasExactlyElementsOfTypes(
ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class,
ResourceHttpMessageConverter.class, JacksonJsonHttpMessageConverter.class,
JacksonSmileHttpMessageConverter.class, JacksonCborHttpMessageConverter.class,
JacksonYamlHttpMessageConverter.class, JacksonXmlHttpMessageConverter.class,
KotlinSerializationProtobufHttpMessageConverter.class, AtomFeedHttpMessageConverter.class,
RssChannelHttpMessageConverter.class);
}
@Test
void registerCustomMessageConverter() {
var converters = HttpMessageConverters.create()
.additionalMessageConverter(new CustomHttpMessageConverter()).build();
assertThat(converters.forClient()).hasExactlyElementsOfTypes(AllEncompassingFormHttpMessageConverter.class, CustomHttpMessageConverter.class);
}
@Test
void registerCustomConverterInMultipartConverter() {
var converters = HttpMessageConverters.withDefaults()
.additionalMessageConverter(new CustomHttpMessageConverter()).build();
AllEncompassingFormHttpMessageConverter multipartConverter = findMessageConverter(AllEncompassingFormHttpMessageConverter.class, converters.forClient());
assertThat(multipartConverter.getPartConverters()).hasAtLeastOneElementOfType(CustomHttpMessageConverter.class);
}
@Test
void shouldUseServerSpecificConverter() {
JacksonJsonHttpMessageConverter jacksonConverter = new JacksonJsonHttpMessageConverter();
var converters = HttpMessageConverters.withDefaults()
.configureClient(configurer -> configurer.jsonMessageConverter(jacksonConverter)).build();
JacksonJsonHttpMessageConverter customConverter = findMessageConverter(JacksonJsonHttpMessageConverter.class, converters.forClient());
assertThat(customConverter).isEqualTo(jacksonConverter);
}
@Test
void shouldConfigureConverter() {
CustomHttpMessageConverter customConverter = new CustomHttpMessageConverter();
var converters = HttpMessageConverters.withDefaults()
.additionalMessageConverter(customConverter)
.configureClient(configurer -> {
configurer.configureClientMessageConverters(converter -> {
if (converter instanceof CustomHttpMessageConverter custom) {
custom.processed = true;
}
});
}).build();
assertThat(customConverter.processed).isTrue();
}
}
@Nested
class ServerConvertersTests {
@Test
void defaultConverters() {
var converters = HttpMessageConverters.withDefaults().build();
assertThat(converters.forServer()).hasExactlyElementsOfTypes(
ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class,
ResourceHttpMessageConverter.class, ResourceRegionHttpMessageConverter.class,
JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class,
JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class,
JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class,
AtomFeedHttpMessageConverter.class, RssChannelHttpMessageConverter.class,
AllEncompassingFormHttpMessageConverter.class);
}
@Test
void multipartConverterContainsOtherConverters() {
var converters = HttpMessageConverters.withDefaults().build();
AllEncompassingFormHttpMessageConverter multipartConverter = findMessageConverter(AllEncompassingFormHttpMessageConverter.class, converters.forServer());
assertThat(multipartConverter.getPartConverters()).hasExactlyElementsOfTypes(
ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class,
ResourceHttpMessageConverter.class, JacksonJsonHttpMessageConverter.class,
JacksonSmileHttpMessageConverter.class, JacksonCborHttpMessageConverter.class,
JacksonYamlHttpMessageConverter.class, JacksonXmlHttpMessageConverter.class,
KotlinSerializationProtobufHttpMessageConverter.class, AtomFeedHttpMessageConverter.class,
RssChannelHttpMessageConverter.class);
}
@Test
void registerCustomMessageConverter() {
var converters = HttpMessageConverters.create()
.additionalMessageConverter(new CustomHttpMessageConverter()).build();
assertThat(converters.forServer()).hasExactlyElementsOfTypes(CustomHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class);
}
@Test
void registerCustomConverterInMultipartConverter() {
var converters = HttpMessageConverters.withDefaults()
.additionalMessageConverter(new CustomHttpMessageConverter()).build();
AllEncompassingFormHttpMessageConverter multipartConverter = findMessageConverter(AllEncompassingFormHttpMessageConverter.class, converters.forServer());
assertThat(multipartConverter.getPartConverters()).hasAtLeastOneElementOfType(CustomHttpMessageConverter.class);
}
@Test
void shouldUseServerSpecificConverter() {
JacksonJsonHttpMessageConverter jacksonConverter = new JacksonJsonHttpMessageConverter();
var converters = HttpMessageConverters.withDefaults()
.configureServer(configurer -> configurer.jsonMessageConverter(jacksonConverter)).build();
JacksonJsonHttpMessageConverter customConverter = findMessageConverter(JacksonJsonHttpMessageConverter.class, converters.forServer());
assertThat(customConverter).isEqualTo(jacksonConverter);
}
@Test
void shouldConfigureConverter() {
CustomHttpMessageConverter customConverter = new CustomHttpMessageConverter();
var converters = HttpMessageConverters.withDefaults()
.additionalMessageConverter(customConverter)
.configureServer(configurer -> {
configurer.configureServerMessageConverters(converter -> {
if (converter instanceof CustomHttpMessageConverter custom) {
custom.processed = true;
}
});
}).build();
assertThat(customConverter.processed).isTrue();
}
}
@Nested
class ClasspathDetectionTests {
@Test
void jsonUsesJackson2WhenJacksonNotPresent() {
var classLoader = new FilteredClassLoader(ObjectMapper.class);
var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build();
assertThat(converters.forServer()).hasAtLeastOneElementOfType(MappingJackson2HttpMessageConverter.class)
.doesNotHaveAnyElementsOfTypes(JacksonJsonHttpMessageConverter.class);
}
@Test
void jsonUsesGsonWhenJacksonNotPresent() {
var classLoader = new FilteredClassLoader(ObjectMapper.class, com.fasterxml.jackson.databind.ObjectMapper.class);
var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build();
assertThat(converters.forServer()).hasAtLeastOneElementOfType(GsonHttpMessageConverter.class)
.doesNotHaveAnyElementsOfTypes(JacksonJsonHttpMessageConverter.class, MappingJackson2HttpMessageConverter.class);
}
@Test
void jsonUsesJsonbWhenJacksonAndGsonNotPresent() {
var classLoader = new FilteredClassLoader(ObjectMapper.class, com.fasterxml.jackson.databind.ObjectMapper.class, Gson.class);
var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build();
assertThat(converters.forServer()).hasAtLeastOneElementOfType(JsonbHttpMessageConverter.class)
.doesNotHaveAnyElementsOfTypes(JacksonJsonHttpMessageConverter.class, MappingJackson2HttpMessageConverter.class,
GsonHttpMessageConverter.class);
}
@Test
void jsonUsesKotlinWhenOthersNotPresent() {
var classLoader = new FilteredClassLoader(ObjectMapper.class, com.fasterxml.jackson.databind.ObjectMapper.class, Gson.class, Jsonb.class);
var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build();
assertThat(converters.forServer()).hasAtLeastOneElementOfType(KotlinSerializationJsonHttpMessageConverter.class)
.doesNotHaveAnyElementsOfTypes(JacksonJsonHttpMessageConverter.class, MappingJackson2HttpMessageConverter.class,
GsonHttpMessageConverter.class, JsonbHttpMessageConverter.class);
}
@Test
void xmlUsesJackson2WhenJacksonNotPresent() {
var classLoader = new FilteredClassLoader(XmlMapper.class);
var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build();
assertThat(converters.forServer()).hasAtLeastOneElementOfType(MappingJackson2XmlHttpMessageConverter.class)
.doesNotHaveAnyElementsOfTypes(JacksonXmlHttpMessageConverter.class);
}
@Test
void xmlUsesJaxbWhenJacksonNotPresent() {
var classLoader = new FilteredClassLoader(XmlMapper.class, com.fasterxml.jackson.dataformat.xml.XmlMapper.class);
var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build();
assertThat(converters.forServer()).hasAtLeastOneElementOfType(Jaxb2RootElementHttpMessageConverter.class)
.doesNotHaveAnyElementsOfTypes(JacksonXmlHttpMessageConverter.class, MappingJackson2XmlHttpMessageConverter.class);
}
@Test
void smileUsesJackson2WhenJacksonNotPresent() {
var classLoader = new FilteredClassLoader(SmileMapper.class);
var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build();
assertThat(converters.forServer()).hasAtLeastOneElementOfType(MappingJackson2SmileHttpMessageConverter.class)
.doesNotHaveAnyElementsOfTypes(JacksonSmileHttpMessageConverter.class);
}
@Test
void cborUsesJackson2WhenJacksonNotPresent() {
var classLoader = new FilteredClassLoader(CBORMapper.class);
var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build();
assertThat(converters.forServer()).hasAtLeastOneElementOfType(MappingJackson2CborHttpMessageConverter.class)
.doesNotHaveAnyElementsOfTypes(JacksonCborHttpMessageConverter.class);
}
@Test
void cborUsesKotlinWhenJacksonNotPresent() {
var classLoader = new FilteredClassLoader(CBORMapper.class, com.fasterxml.jackson.dataformat.cbor.CBORFactory.class);
var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build();
assertThat(converters.forServer()).hasAtLeastOneElementOfType(KotlinSerializationCborHttpMessageConverter.class)
.doesNotHaveAnyElementsOfTypes(JacksonCborHttpMessageConverter.class, MappingJackson2CborHttpMessageConverter.class);
}
@Test
void yamlUsesJackson2WhenJacksonNotPresent() {
var classLoader = new FilteredClassLoader(YAMLMapper.class);
var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build();
assertThat(converters.forServer()).hasAtLeastOneElementOfType(MappingJackson2YamlHttpMessageConverter.class)
.doesNotHaveAnyElementsOfTypes(JacksonYamlHttpMessageConverter.class);
}
@Test
void atomAndRssNotConfiguredWhenRomeNotPresent() {
var classLoader = new FilteredClassLoader(WireFeed.class);
var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build();
assertThat(converters.forServer()).doesNotHaveAnyElementsOfTypes(AtomFeedHttpMessageConverter.class, RssChannelHttpMessageConverter.class);
}
}
@SuppressWarnings("unchecked")
private <T> T findMessageConverter(Class<T> converterType, Iterable<HttpMessageConverter<?>> converters) {
return (T) StreamSupport
.stream(converters.spliterator(), false)
.filter(converter -> converter.getClass().equals(converterType))
.findFirst().orElseThrow();
}
static class FilteredClassLoader extends URLClassLoader implements SmartClassLoader {
private final Collection<Class<?>> hiddenClasses;
public FilteredClassLoader(Class<?>... hiddenClasses) {
this(java.util.Arrays.asList(hiddenClasses));
}
FilteredClassLoader(Collection<Class<?>> hiddenClasses) {
super(new URL[0], FilteredClassLoader.class.getClassLoader());
this.hiddenClasses = hiddenClasses;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
for (Class<?> hiddenClass : this.hiddenClasses) {
if (hiddenClass.getName().equals(name)) {
throw new ClassNotFoundException();
}
}
return super.loadClass(name, resolve);
}
}
static class CustomHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
boolean processed = false;
@Override
protected boolean supports(Class<?> clazz) {
return false;
}
@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
}
}
}