diff --git a/core/src/main/java/feign/BaseBuilder.java b/core/src/main/java/feign/BaseBuilder.java index d05588b8d9..79818759fa 100644 --- a/core/src/main/java/feign/BaseBuilder.java +++ b/core/src/main/java/feign/BaseBuilder.java @@ -43,7 +43,7 @@ public abstract class BaseBuilder> { protected Encoder encoder = new Encoder.Default(); protected Decoder decoder = new Decoder.Default(); protected boolean closeAfterDecode = true; - protected QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder(); + protected QueryMapEncoder queryMapEncoder = QueryMap.MapEncoder.FIELD.instance(); protected ErrorDecoder errorDecoder = new ErrorDecoder.Default(); protected Options options = new Options(); protected InvocationHandlerFactory invocationHandlerFactory = diff --git a/core/src/main/java/feign/Contract.java b/core/src/main/java/feign/Contract.java index cf966a6f6d..2256646877 100644 --- a/core/src/main/java/feign/Contract.java +++ b/core/src/main/java/feign/Contract.java @@ -315,6 +315,7 @@ public Default() { checkState(data.queryMapIndex() == null, "QueryMap annotation was present on multiple parameters."); data.queryMapIndex(paramIndex); + data.queryMapEncoder(queryMap.mapEncoder().instance()); }); super.registerParameterAnnotation(HeaderMap.class, (queryMap, data, paramIndex) -> { checkState(data.headerMapIndex() == null, diff --git a/core/src/main/java/feign/MethodMetadata.java b/core/src/main/java/feign/MethodMetadata.java index 899f5571bf..ef002e6952 100644 --- a/core/src/main/java/feign/MethodMetadata.java +++ b/core/src/main/java/feign/MethodMetadata.java @@ -29,6 +29,7 @@ public final class MethodMetadata implements Serializable { private Integer bodyIndex; private Integer headerMapIndex; private Integer queryMapIndex; + private QueryMapEncoder queryMapEncoder; private boolean alwaysEncodeBody; private transient Type bodyType; private final RequestTemplate template = new RequestTemplate(); @@ -109,6 +110,15 @@ public MethodMetadata queryMapIndex(Integer queryMapIndex) { return this; } + public QueryMapEncoder queryMapEncoder() { + return queryMapEncoder; + } + + public MethodMetadata queryMapEncoder(QueryMapEncoder queryMapEncoder) { + this.queryMapEncoder = queryMapEncoder; + return this; + } + @Experimental public boolean alwaysEncodeBody() { return alwaysEncodeBody; diff --git a/core/src/main/java/feign/QueryMap.java b/core/src/main/java/feign/QueryMap.java index eef0a4320b..91b127eeed 100644 --- a/core/src/main/java/feign/QueryMap.java +++ b/core/src/main/java/feign/QueryMap.java @@ -16,6 +16,8 @@ import java.lang.annotation.Retention; import java.util.List; import java.util.Map; +import feign.querymap.BeanQueryMapEncoder; +import feign.querymap.FieldQueryMapEncoder; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -70,4 +72,26 @@ * @deprecated */ boolean encoded() default false; + + /** + * Specifies the QueryMapEncoder implementation to use to transform DTO into query map. + * + * @return the enum containing the instance of QueryMapEncoder + */ + MapEncoder mapEncoder() default MapEncoder.DEFAULT; + + public enum MapEncoder { + // the latter DEFAULT will use BaseBuilder instance + BEAN(new BeanQueryMapEncoder()), FIELD(new FieldQueryMapEncoder()), DEFAULT(null); + + private QueryMapEncoder mapEncoder; + + private MapEncoder(QueryMapEncoder mapEncoder) { + this.mapEncoder = mapEncoder; + } + + public QueryMapEncoder instance() { + return mapEncoder; + } + } } diff --git a/core/src/main/java/feign/RequestTemplateFactoryResolver.java b/core/src/main/java/feign/RequestTemplateFactoryResolver.java index 84743b04da..6dd69de891 100644 --- a/core/src/main/java/feign/RequestTemplateFactoryResolver.java +++ b/core/src/main/java/feign/RequestTemplateFactoryResolver.java @@ -108,26 +108,28 @@ public RequestTemplate create(Object[] argv) { // add query map parameters after initial resolve so that they take // precedence over any predefined values Object value = argv[metadata.queryMapIndex()]; - Map queryMap = toQueryMap(value); + Map queryMap = toQueryMap(value, metadata.queryMapEncoder()); template = addQueryMapQueryParameters(queryMap, template); } if (metadata.headerMapIndex() != null) { // add header map parameters for a resolution of the user pojo object Object value = argv[metadata.headerMapIndex()]; - Map headerMap = toQueryMap(value); + Map headerMap = toQueryMap(value, metadata.queryMapEncoder()); template = addHeaderMapHeaders(headerMap, template); } return template; } - private Map toQueryMap(Object value) { + private Map toQueryMap(Object value, QueryMapEncoder queryMapEncoder) { if (value instanceof Map) { return (Map) value; } try { - return queryMapEncoder.encode(value); + // encode with @QueryMap annotation if exists otherwise with the one from this resolver + return queryMapEncoder != null ? queryMapEncoder.encode(value) + : this.queryMapEncoder.encode(value); } catch (EncodeException e) { throw new IllegalStateException(e); } diff --git a/core/src/test/java/feign/ChildPojo.java b/core/src/test/java/feign/ChildPojo.java index a6bf2ea392..f620724e2f 100644 --- a/core/src/test/java/feign/ChildPojo.java +++ b/core/src/test/java/feign/ChildPojo.java @@ -16,6 +16,15 @@ class ParentPojo { public String parentPublicProperty; protected String parentProtectedProperty; + private String parentPrivatePropertyAlteredByGetter; + + public String getParentPrivatePropertyAlteredByGetter() { + return parentPrivatePropertyAlteredByGetter + "FromGetter"; + } + + public void setParentPrivatePropertyAlteredByGetter(String parentPrivatePropertyAlteredByGetter) { + this.parentPrivatePropertyAlteredByGetter = parentPrivatePropertyAlteredByGetter; + } public String getParentPublicProperty() { return parentPublicProperty; diff --git a/core/src/test/java/feign/FeignTest.java b/core/src/test/java/feign/FeignTest.java index a2ed6b0c9a..21fd153ed4 100644 --- a/core/src/test/java/feign/FeignTest.java +++ b/core/src/test/java/feign/FeignTest.java @@ -16,6 +16,7 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import feign.Feign.ResponseMappingDecoder; +import feign.QueryMap.MapEncoder; import feign.Request.HttpMethod; import feign.Target.HardCodedTarget; import feign.querymap.BeanQueryMapEncoder; @@ -916,6 +917,7 @@ public void queryMap_with_child_pojo() throws Exception { childPojo.setChildPrivateProperty("first"); childPojo.setParentProtectedProperty("second"); childPojo.setParentPublicProperty("third"); + childPojo.setParentPrivatePropertyAlteredByGetter("fourth"); server.enqueue(new MockResponse()); api.queryMapPropertyInheritence(childPojo); @@ -923,7 +925,31 @@ public void queryMap_with_child_pojo() throws Exception { .hasQueryParams( "parentPublicProperty=third", "parentProtectedProperty=second", - "childPrivateProperty=first"); + "childPrivateProperty=first", + "parentPrivatePropertyAlteredByGetter=fourth"); + } + + @Test + public void queryMap_with_child_pojo_altered_by_getter_while_using_overriding_encoder() + throws Exception { + TestInterface api = new TestInterfaceBuilder() + .queryMapEndcoder(new FieldQueryMapEncoder()) + .target("http://localhost:" + server.getPort()); + + ChildPojo childPojo = new ChildPojo(); + childPojo.setChildPrivateProperty("first"); + childPojo.setParentProtectedProperty("second"); + childPojo.setParentPublicProperty("third"); + childPojo.setParentPrivatePropertyAlteredByGetter("fourth"); + + server.enqueue(new MockResponse()); + api.queryMapPropertyInheritenceWithBeanMapEncoder(childPojo); + assertThat(server.takeRequest()) + .hasQueryParams( + "parentPublicProperty=third", + "parentProtectedProperty=second", + "childPrivateProperty=first", + "parentPrivatePropertyAlteredByGetter=fourthFromGetter"); } @Test @@ -1084,6 +1110,10 @@ void queryMapWithQueryParams(@Param("name") String name, @RequestLine("GET /") void queryMapPropertyPojo(@QueryMap PropertyPojo object); + @RequestLine("GET /") + void queryMapPropertyInheritenceWithBeanMapEncoder(@QueryMap( + mapEncoder = MapEncoder.BEAN) ChildPojo object); + @RequestLine("GET /") void queryMapPropertyInheritence(@QueryMap ChildPojo object);