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:
parent
4b44a34692
commit
1af25e9cb1
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue