Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer;
import org.springframework.cloud.openfeign.support.AbstractFormWriter;
import org.springframework.cloud.openfeign.support.FeignEncoderProperties;
import org.springframework.cloud.openfeign.support.FeignHttpMessageConverters;
import org.springframework.cloud.openfeign.support.HttpMessageConverterCustomizer;
import org.springframework.cloud.openfeign.support.PageableSpringEncoder;
import org.springframework.cloud.openfeign.support.PageableSpringQueryMapEncoder;
Expand Down Expand Up @@ -98,16 +99,24 @@ public class FeignClientsConfiguration {

@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder(ObjectProvider<HttpMessageConverterCustomizer> customizers) {
return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(messageConverters, customizers)));
public FeignHttpMessageConverters feignHttpMessageConverters(
ObjectProvider<HttpMessageConverter<?>> messageConverters,
ObjectProvider<HttpMessageConverterCustomizer> customizers) {
return new FeignHttpMessageConverters(messageConverters, customizers);
}

@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder(ObjectProvider<FeignHttpMessageConverters> messageConverters) {
return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(messageConverters)));
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
public Encoder feignEncoder(ObjectProvider<AbstractFormWriter> formWriterProvider,
ObjectProvider<HttpMessageConverterCustomizer> customizers) {
return springEncoder(formWriterProvider, messageConverters, encoderProperties, customizers);
ObjectProvider<FeignHttpMessageConverters> feignHttpMessageConverters) {
return springEncoder(formWriterProvider, encoderProperties, feignHttpMessageConverters);
}

@Bean
Expand Down Expand Up @@ -145,16 +154,16 @@ public FeignClientConfigurer feignClientConfigurer() {
}

private static Encoder springEncoder(ObjectProvider<AbstractFormWriter> formWriterProvider,
ObjectProvider<HttpMessageConverter<?>> messageConverters, FeignEncoderProperties encoderProperties,
ObjectProvider<HttpMessageConverterCustomizer> customizers) {
FeignEncoderProperties encoderProperties,
ObjectProvider<FeignHttpMessageConverters> feignHttpMessageConverters) {
AbstractFormWriter formWriter = formWriterProvider.getIfAvailable();

if (formWriter != null) {
return new SpringEncoder(new SpringPojoFormEncoder(formWriter), messageConverters, encoderProperties,
customizers);
return new SpringEncoder(new SpringPojoFormEncoder(formWriter), encoderProperties,
feignHttpMessageConverters);
}
else {
return new SpringEncoder(new SpringFormEncoder(), messageConverters, encoderProperties, customizers);
return new SpringEncoder(new SpringFormEncoder(), encoderProperties, feignHttpMessageConverters);
}
}

Expand Down Expand Up @@ -182,11 +191,10 @@ static class SpringDataConfiguration {
@Bean
@ConditionalOnMissingBean
public Encoder feignEncoderPageable(ObjectProvider<AbstractFormWriter> formWriterProvider,
ObjectProvider<HttpMessageConverter<?>> messageConverters,
ObjectProvider<FeignEncoderProperties> encoderProperties,
ObjectProvider<HttpMessageConverterCustomizer> customizers) {
PageableSpringEncoder encoder = new PageableSpringEncoder(springEncoder(formWriterProvider,
messageConverters, encoderProperties.getIfAvailable(), customizers));
ObjectProvider<FeignHttpMessageConverters> feignHttpMessageConverters) {
PageableSpringEncoder encoder = new PageableSpringEncoder(
springEncoder(formWriterProvider, encoderProperties.getIfAvailable(), feignHttpMessageConverters));

if (dataWebProperties != null) {
encoder.setPageParameter(dataWebProperties.getPageable().getPageParameter());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright 2013-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.cloud.openfeign.support;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverters;
import org.springframework.http.converter.StringHttpMessageConverter;

/**
* Class that mimics {@link HttpMessageConverters} and the default implementation there.
* Applies the {@link HttpMessageConverterCustomizer}s and gathers all the converters into
* a {@link List}.
*/
public class FeignHttpMessageConverters {

private final ObjectProvider<HttpMessageConverter<?>> messageConverters;

private final ObjectProvider<HttpMessageConverterCustomizer> customizers;

private List<HttpMessageConverter<?>> converters;

public FeignHttpMessageConverters(ObjectProvider<HttpMessageConverter<?>> messageConverters,
ObjectProvider<HttpMessageConverterCustomizer> customizers) {
this.messageConverters = messageConverters;
this.customizers = customizers;
}

public List<HttpMessageConverter<?>> getConverters() {
initConvertersIfRequired();
return converters;
}

private void initConvertersIfRequired() {
if (this.converters == null) {
this.converters = new ArrayList<>();
HttpMessageConverters.ClientBuilder builder = HttpMessageConverters.forClient();
// TODO: allow disabling of registerDefaults
builder.registerDefaults();
// TODO: check if already added? Howto order?
this.messageConverters.forEach((converter) -> {
if (converter instanceof StringHttpMessageConverter) {
builder.withStringConverter(converter);
}
/*
* else if (converter instanceof
* KotlinSerializationJsonHttpMessageConverter) {
* builder.withKotlinSerializationJsonConverter(converter); }
*/
else if (supportsMediaType(converter, MediaType.APPLICATION_JSON)) {
builder.withJsonConverter(converter);
}
else if (supportsMediaType(converter, MediaType.APPLICATION_XML)) {
builder.withXmlConverter(converter);
}
else {
builder.addCustomConverter(converter);
}
});
HttpMessageConverters hmc = builder.build();
hmc.forEach(converter -> converters.add(converter));
customizers.forEach(customizer -> customizer.accept(this.converters));
}
}

private static boolean supportsMediaType(HttpMessageConverter<?> converter, MediaType mediaType) {
for (MediaType supportedMediaType : converter.getSupportedMediaTypes()) {
if (supportedMediaType.equalsTypeAndSubtype(mediaType)) {
return true;
}
}
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.List;

import feign.FeignException;
import feign.Response;
Expand All @@ -32,7 +31,6 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.client.HttpMessageConverterExtractor;

import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders;
Expand All @@ -44,27 +42,18 @@
*/
public class SpringDecoder implements Decoder {

private final ObjectProvider<HttpMessageConverter<?>> messageConverters;
private final ObjectProvider<FeignHttpMessageConverters> converters;

private final ObjectProvider<HttpMessageConverterCustomizer> customizers;

public SpringDecoder(ObjectProvider<HttpMessageConverter<?>> messageConverters) {
this(messageConverters, new EmptyObjectProvider<>());
}

public SpringDecoder(ObjectProvider<HttpMessageConverter<?>> messageConverters,
ObjectProvider<HttpMessageConverterCustomizer> customizers) {
this.messageConverters = messageConverters;
this.customizers = customizers;
public SpringDecoder(ObjectProvider<FeignHttpMessageConverters> converters) {
this.converters = converters;
}

@Override
public Object decode(final Response response, Type type) throws IOException, FeignException {
if (type instanceof Class || type instanceof ParameterizedType || type instanceof WildcardType) {
List<HttpMessageConverter<?>> converters = messageConverters.orderedStream().toList();
customizers.forEach(customizer -> customizer.accept(converters));
@SuppressWarnings({ "unchecked", "rawtypes" })
HttpMessageConverterExtractor<?> extractor = new HttpMessageConverterExtractor(type, converters);
HttpMessageConverterExtractor<?> extractor = new HttpMessageConverterExtractor(type,
converters.getObject().getConverters());

return extractor.extractData(new FeignResponseAdapter(response));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

Expand Down Expand Up @@ -72,29 +71,23 @@ public class SpringEncoder implements Encoder {

private final SpringFormEncoder springFormEncoder;

private final ObjectProvider<HttpMessageConverter<?>> messageConverters;
private final ObjectProvider<FeignHttpMessageConverters> converters;

private final FeignEncoderProperties encoderProperties;

private final ObjectProvider<HttpMessageConverterCustomizer> customizers;

private List<HttpMessageConverter<?>> converters;

public SpringEncoder(ObjectProvider<HttpMessageConverter<?>> messageConverters) {
this(new SpringFormEncoder(), messageConverters, new FeignEncoderProperties(), new EmptyObjectProvider<>());
public SpringEncoder(ObjectProvider<FeignHttpMessageConverters> converters) {
this(new SpringFormEncoder(), new FeignEncoderProperties(), converters);
}

public SpringEncoder(SpringFormEncoder springFormEncoder, ObjectProvider<HttpMessageConverter<?>> messageConverters,
FeignEncoderProperties encoderProperties, ObjectProvider<HttpMessageConverterCustomizer> customizers) {
public SpringEncoder(SpringFormEncoder springFormEncoder, FeignEncoderProperties encoderProperties,
ObjectProvider<FeignHttpMessageConverters> converters) {
this.springFormEncoder = springFormEncoder;
this.messageConverters = messageConverters;
this.encoderProperties = encoderProperties;
this.customizers = customizers;
this.converters = converters;
}

@Override
public void encode(Object requestBody, Type bodyType, RequestTemplate request) throws EncodeException {
// template.body(conversionService.convert(object, String.class));
if (requestBody != null) {
Collection<String> contentTypes = request.headers().get(HttpEncoding.CONTENT_TYPE);

Expand All @@ -120,8 +113,7 @@ public void encode(Object requestBody, Type bodyType, RequestTemplate request) t

private void encodeWithMessageConverter(Object requestBody, Type bodyType, RequestTemplate request,
MediaType requestContentType) {
initConvertersIfRequired();
for (HttpMessageConverter messageConverter : converters) {
for (HttpMessageConverter messageConverter : converters.getObject().getConverters()) {
FeignOutputMessage outputMessage;
try {
if (messageConverter instanceof GenericHttpMessageConverter) {
Expand Down Expand Up @@ -176,13 +168,6 @@ else if (shouldHaveNullCharset(messageConverter, outputMessage)) {
throw new EncodeException(message);
}

private void initConvertersIfRequired() {
if (converters == null) {
converters = messageConverters.orderedStream().toList();
customizers.forEach(customizer -> customizer.accept(converters));
}
}

private boolean shouldHaveNullCharset(HttpMessageConverter messageConverter, FeignOutputMessage outputMessage) {
return binaryContentType(outputMessage) || messageConverter instanceof ByteArrayHttpMessageConverter
|| messageConverter instanceof ProtobufHttpMessageConverter && ProtobufHttpMessageConverter.PROTOBUF
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters;
import org.springframework.cloud.loadbalancer.support.SimpleObjectProvider;
import org.springframework.cloud.openfeign.support.FeignHttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.http.converter.HttpMessageConverter;

import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
* Tests for {@link SpringDecoder}.
Expand All @@ -41,9 +40,9 @@ class SpringDecoderTests {

@BeforeEach
void setUp() {
ObjectProvider<HttpMessageConverter<?>> factory = mock();
when(factory.orderedStream()).thenReturn(new HttpMessageConverters().getConverters().stream());
decoder = new SpringDecoder(factory);
ObjectProvider<FeignHttpMessageConverters> converters = new SimpleObjectProvider<>(
new FeignHttpMessageConverters(mock(), mock()));
decoder = new SpringDecoder(converters);
}

// Issue: https://github.com/spring-cloud/spring-cloud-openfeign/issues/972
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.loadbalancer.support.SimpleObjectProvider;
import org.springframework.cloud.openfeign.support.FeignHttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.cloud.test.ClassPathExclusions;
import org.springframework.http.converter.HttpMessageConverter;
Expand All @@ -46,7 +48,8 @@ void testEncodeWhenProtobufNotInClasspath() {
when(factory.orderedStream()).thenReturn(protobufHttpMessageConverters.stream());
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.method(POST);
new SpringEncoder(factory).encode("a=b", String.class, requestTemplate);
new SpringEncoder(new SimpleObjectProvider<>(new FeignHttpMessageConverters(factory, mock()))).encode("a=b",
String.class, requestTemplate);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Stream;

import com.google.protobuf.InvalidProtocolBufferException;
import feign.RequestTemplate;
Expand All @@ -43,6 +44,8 @@
import org.mockito.stubbing.Answer;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.loadbalancer.support.SimpleObjectProvider;
import org.springframework.cloud.openfeign.support.FeignHttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;
Expand All @@ -51,7 +54,6 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
* Test {@link SpringEncoder} with {@link ProtobufHttpMessageConverter}
Expand Down Expand Up @@ -111,10 +113,14 @@ void testProtobufWithCharsetWillFail() throws IOException {
}

private SpringEncoder newEncoder() {
ObjectProvider<HttpMessageConverter<?>> factory = mock();
List<HttpMessageConverter<?>> protobufHttpMessageConverters = List.of(new ProtobufHttpMessageConverter());
when(factory.orderedStream()).thenReturn(protobufHttpMessageConverters.stream());
return new SpringEncoder(factory);
ObjectProvider<HttpMessageConverter<?>> factory = new SimpleObjectProvider<>(
new ProtobufHttpMessageConverter()) {
@Override
public Stream<HttpMessageConverter<?>> stream() {
return Stream.of(getObject());
}
};
return new SpringEncoder(new SimpleObjectProvider<>(new FeignHttpMessageConverters(factory, mock())));
}

private RequestTemplate newRequestTemplate() {
Expand Down
Loading