From 642d3e69611568d3ba9295e97607d6db26d41158 Mon Sep 17 00:00:00 2001 From: charliemordant Date: Sat, 29 Dec 2018 15:10:38 +0100 Subject: [PATCH] Spring data pageable support --- spring-cloud-openfeign-core/pom.xml | 35 +++++ .../openfeign/FeignAutoConfiguration.java | 8 + .../openfeign/FeignClientsConfiguration.java | 11 ++ .../openfeign/support/PageJacksonModule.java | 148 ++++++++++++++++++ .../support/PageableSpringEncoder.java | 72 +++++++++ .../EnableFeignClientsSpringDataTests.java | 56 +++++++ .../openfeign/EnableFeignClientsTests.java | 3 + .../FeignClientOverrideDefaultsTests.java | 4 +- .../cloud/openfeign/NonSpringDataTest.java | 4 + .../encoding/FeignContentEncodingTests.java | 8 + .../encoding/FeignPageableEncodingTests.java | 107 +++++++++++++ .../encoding/app/client/InvoiceClient.java | 4 + .../app/resource/InvoiceResource.java | 8 + .../support/PageableEncoderTests.java | 106 +++++++++++++ .../openfeign/support/SpringEncoderTests.java | 9 +- 15 files changed, 577 insertions(+), 6 deletions(-) create mode 100644 spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageJacksonModule.java create mode 100644 spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageableSpringEncoder.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EnableFeignClientsSpringDataTests.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/NonSpringDataTest.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageableEncoderTests.java diff --git a/spring-cloud-openfeign-core/pom.xml b/spring-cloud-openfeign-core/pom.xml index 3a7128b3..f78f2560 100644 --- a/spring-cloud-openfeign-core/pom.xml +++ b/spring-cloud-openfeign-core/pom.xml @@ -187,6 +187,11 @@ spring-cloud-test-support test + + org.springframework.data + spring-data-commons + true + io.projectreactor reactor-test @@ -215,6 +220,36 @@ test + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + org.springframework.cloud.openfeign.NonSpringDataTest + + + + Non-Spring-Data + + org.springframework.cloud.openfeign.NonSpringDataTest + + org.springframework.data:spring-data-commons + + + + + + + + java8plus diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java index 8b510e95..cdf6b06c 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java @@ -22,6 +22,7 @@ import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; +import com.fasterxml.jackson.databind.Module; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.config.RegistryBuilder; @@ -39,8 +40,10 @@ import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory; import org.springframework.cloud.commons.httpclient.OkHttpClientFactory; import org.springframework.cloud.openfeign.support.FeignHttpClientProperties; +import org.springframework.cloud.openfeign.support.PageJacksonModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.domain.Page; import feign.Client; import feign.Feign; @@ -207,4 +210,9 @@ public class FeignAutoConfiguration { } } + @Bean + @ConditionalOnClass(Page.class) + public Module pageJacksonModule(){ + return new PageJacksonModule(); + } } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java index 090bf3f4..c7118d2e 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java @@ -24,8 +24,10 @@ import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.cloud.openfeign.support.PageableSpringEncoder; import org.springframework.cloud.openfeign.support.ResponseEntityDecoder; import org.springframework.cloud.openfeign.support.SpringDecoder; import org.springframework.cloud.openfeign.support.SpringEncoder; @@ -34,6 +36,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.core.convert.ConversionService; +import org.springframework.data.domain.Pageable; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.format.support.FormattingConversionService; @@ -75,10 +78,18 @@ public class FeignClientsConfiguration { @Bean @ConditionalOnMissingBean + @ConditionalOnMissingClass("org.springframework.data.domain.Pageable") public Encoder feignEncoder() { return new SpringEncoder(this.messageConverters); } + @Bean + @ConditionalOnClass(Pageable.class) + @ConditionalOnMissingBean + public Encoder feignEncoderPageable() { + return new PageableSpringEncoder(new SpringEncoder(this.messageConverters)); + } + @Bean @ConditionalOnMissingBean public Contract feignContract(ConversionService feignConversionService) { diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageJacksonModule.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageJacksonModule.java new file mode 100644 index 00000000..1d3a2fe9 --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageJacksonModule.java @@ -0,0 +1,148 @@ +package org.springframework.cloud.openfeign.support; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.springframework.data.domain.*; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +/** + * This jackson module provides support to deserialize spring {Page} + * objects. + * + * @author Pascal Büttiker + */ +public class PageJacksonModule extends Module { + + @Override + public String getModuleName() { + return "PageJacksonModule"; + } + + @Override + public Version version() { + return new Version(0,1,0, "", null,null); + } + + @Override + public void setupModule(SetupContext context) { + context.setMixInAnnotations(Page.class, PageMixIn.class); + } + + @JsonDeserialize(as = SimplePageImpl.class) + private interface PageMixIn{ } + + + static class SimplePageImpl implements Page { + + private final Page delegate; + + public SimplePageImpl( + @JsonProperty("content") List content, + @JsonProperty("page")int number, + @JsonProperty("size") int size, + @JsonProperty("totalElements") long totalElements){ + delegate = new PageImpl<>(content, PageRequest.of(number, size), totalElements); + } + + + @JsonProperty + @Override + public int getTotalPages() { + return delegate.getTotalPages(); + } + + @JsonProperty + @Override + public long getTotalElements() { + return delegate.getTotalElements(); + } + + @JsonProperty("page") + @Override + public int getNumber() { + return delegate.getNumber(); + } + + @JsonProperty + @Override + public int getSize() { + return delegate.getSize(); + } + + @JsonProperty + @Override + public int getNumberOfElements() { + return delegate.getNumberOfElements(); + } + + @JsonProperty + @Override + public List getContent() { + return delegate.getContent(); + } + + @JsonProperty + @Override + public boolean hasContent() { + return delegate.hasContent(); + } + + @JsonIgnore + @Override + public Sort getSort() { + return delegate.getSort(); + } + + @JsonProperty + @Override + public boolean isFirst() { + return delegate.isFirst(); + } + + @JsonProperty + @Override + public boolean isLast() { + return delegate.isLast(); + } + + @JsonIgnore + @Override + public boolean hasNext() { + return delegate.hasNext(); + } + + @JsonIgnore + @Override + public boolean hasPrevious() { + return delegate.hasPrevious(); + } + + @JsonIgnore + @Override + public Pageable nextPageable() { + return delegate.nextPageable(); + } + @JsonIgnore + @Override + public Pageable previousPageable() { + return delegate.previousPageable(); + } + @JsonIgnore + @Override + public Page map(Function converter) { + return delegate.map(converter); + } + + @JsonIgnore + @Override + public Iterator iterator() { + return delegate.iterator(); + } + } +} \ No newline at end of file diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageableSpringEncoder.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageableSpringEncoder.java new file mode 100644 index 00000000..f2ab8a7f --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageableSpringEncoder.java @@ -0,0 +1,72 @@ +package org.springframework.cloud.openfeign.support; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import feign.RequestTemplate; +import feign.codec.EncodeException; +import feign.codec.Encoder; + +/** + * Provides support for encoding spring Pageable via composition. + * + * @author Pascal Büttiker + */ +public class PageableSpringEncoder implements Encoder { + + private final Encoder delegate; + + /** + * Creates a new PageableSpringEncoder with the given delegate for fallback. + * If no delegate is provided and this encoder cant handle the request, + * an EncodeException is thrown. + * @param delegate The optional delegate. + */ + public PageableSpringEncoder(Encoder delegate){ + this.delegate = delegate; + } + + @Override + public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { + + if (supports(object)) { + if (object instanceof Pageable) { + Pageable pageable = (Pageable) object; + template.query("page", pageable.getPageNumber() + ""); + template.query("size", pageable.getPageSize() + ""); + if (pageable.getSort() != null) { + applySort(template, pageable.getSort()); + } + } else if (object instanceof Sort) { + Sort sort = (Sort)object; + applySort(template, sort); + } + } else { + if (delegate != null) { + delegate.encode(object, bodyType, template); + } else { + throw new EncodeException("PageableSpringEncoder does not support the given object " + object.getClass() + " and no delegate was provided for fallback!"); + } + } + } + + private void applySort(RequestTemplate template, Sort sort) { + Collection existingSorts = template.queries().get("sort"); + List sortQueries = existingSorts != null ? new ArrayList<>(existingSorts) : new ArrayList<>(); + for (Sort.Order order : sort) { + sortQueries.add(order.getProperty() + "," + order.getDirection()); + } + if(!sortQueries.isEmpty()) { + template.query("sort", sortQueries); + } + } + + protected boolean supports(Object object) { + return object instanceof Pageable || object instanceof Sort; + } +} \ No newline at end of file diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EnableFeignClientsSpringDataTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EnableFeignClientsSpringDataTests.java new file mode 100644 index 00000000..e408c15f --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EnableFeignClientsSpringDataTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2013-2017 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 + * + * http://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; + +import feign.codec.Encoder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration; +import org.springframework.cloud.openfeign.support.PageableSpringEncoder; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Spencer Gibb + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = EnableFeignClientsSpringDataTests.PlainConfiguration.class) +@DirtiesContext +public class EnableFeignClientsSpringDataTests { + + @Autowired + private FeignContext feignContext; + + @Test + public void encoderDefaultCorrect() { + + PageableSpringEncoder.class.cast(this.feignContext.getInstance("foo", Encoder.class)); + } + + @Configuration + @Import({ PropertyPlaceholderAutoConfiguration.class, ArchaiusAutoConfiguration.class, + FeignAutoConfiguration.class }) + protected static class PlainConfiguration { + } + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EnableFeignClientsTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EnableFeignClientsTests.java index 75cfeee3..af6b9a51 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EnableFeignClientsTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EnableFeignClientsTests.java @@ -18,6 +18,7 @@ package org.springframework.cloud.openfeign; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; @@ -44,6 +45,7 @@ import feign.slf4j.Slf4jLogger; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = EnableFeignClientsTests.PlainConfiguration.class) @DirtiesContext +@Category({NonSpringDataTest.class}) public class EnableFeignClientsTests { @Autowired @@ -57,6 +59,7 @@ public class EnableFeignClientsTests { @Test public void encoderDefaultCorrect() { + SpringEncoder.class.cast(this.feignContext.getInstance("foo", Encoder.class)); } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java index 844bda57..83b2c8e5 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java @@ -23,7 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration; -import org.springframework.cloud.openfeign.support.SpringEncoder; +import org.springframework.cloud.openfeign.support.PageableSpringEncoder; import org.springframework.cloud.openfeign.support.SpringMvcContract; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -85,7 +85,7 @@ public class FeignClientOverrideDefaultsTests { @Test public void overrideEncoder() { Encoder.Default.class.cast(this.context.getInstance("foo", Encoder.class)); - SpringEncoder.class.cast(this.context.getInstance("bar", Encoder.class)); + PageableSpringEncoder.class.cast(this.context.getInstance("bar", Encoder.class)); } @Test diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/NonSpringDataTest.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/NonSpringDataTest.java new file mode 100644 index 00000000..b2af3bfb --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/NonSpringDataTest.java @@ -0,0 +1,4 @@ +package org.springframework.cloud.openfeign; + +public interface NonSpringDataTest { +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignContentEncodingTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignContentEncodingTests.java index 748b0e6e..c85fad45 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignContentEncodingTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignContentEncodingTests.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -30,6 +31,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.cloud.openfeign.NonSpringDataTest; import org.springframework.cloud.openfeign.encoding.app.client.InvoiceClient; import org.springframework.cloud.openfeign.encoding.app.domain.Invoice; import org.springframework.cloud.netflix.ribbon.RibbonClient; @@ -37,6 +39,11 @@ import org.springframework.cloud.openfeign.test.NoSecurityConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.config.EnableSpringDataWebSupport; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -55,6 +62,7 @@ import com.netflix.loadbalancer.Server; "hystrix.command.default.execution.isolation.strategy=SEMAPHORE", "ribbon.OkToRetryOnAllOperations=false" }) @RunWith(SpringJUnit4ClassRunner.class) +@Category({NonSpringDataTest.class}) public class FeignContentEncodingTests { @Autowired diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java new file mode 100644 index 00000000..c5dbffcc --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java @@ -0,0 +1,107 @@ +/* + * Copyright 2013-2015 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 + * + * http://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.encoding; + +import com.netflix.loadbalancer.BaseLoadBalancer; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.cloud.netflix.ribbon.RibbonClient; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.cloud.openfeign.encoding.app.client.InvoiceClient; +import org.springframework.cloud.openfeign.encoding.app.domain.Invoice; +import org.springframework.cloud.openfeign.test.NoSecurityConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.config.EnableSpringDataWebSupport; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.Collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Tests the pagination encoding. + * + * @author Charlie Mordant. + */ +@SpringBootTest(classes = FeignPageableEncodingTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = { + "feign.compression.request.enabled=true", + "hystrix.command.default.execution.isolation.strategy=SEMAPHORE", + "ribbon.OkToRetryOnAllOperations=false" }) +@RunWith(SpringJUnit4ClassRunner.class) +public class FeignPageableEncodingTests { + + @Autowired + private InvoiceClient invoiceClient; + + @Test + public void testPageable() { + + // given + Pageable pageable = PageRequest.of(0,10, Sort.Direction.ASC, "sortProperty"); + + // when + final ResponseEntity> response = this.invoiceClient + .getInvoicesPaged(pageable); + + // then + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(pageable.getPageSize(), response.getBody().getSize()); + + } + + @EnableFeignClients(clients = InvoiceClient.class) + @RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class) + @SpringBootApplication(scanBasePackages = "org.springframework.cloud.openfeign.encoding.app") + @EnableSpringDataWebSupport + @Import(NoSecurityConfiguration.class) + public static class Application { + } + + @Configuration + static class LocalRibbonClientConfiguration { + + @Value("${local.server.port}") + private int port = 0; + + @Bean + public ILoadBalancer ribbonLoadBalancer() { + BaseLoadBalancer balancer = new BaseLoadBalancer(); + balancer.setServersList( + Collections.singletonList(new Server("localhost", this.port))); + return balancer; + } + } + +} \ No newline at end of file diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/app/client/InvoiceClient.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/app/client/InvoiceClient.java index 466d2669..2357edfa 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/app/client/InvoiceClient.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/app/client/InvoiceClient.java @@ -20,6 +20,7 @@ import java.util.List; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.encoding.app.domain.Invoice; +import org.springframework.data.domain.Page; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; @@ -33,6 +34,9 @@ import org.springframework.web.bind.annotation.RequestMethod; @FeignClient("local") public interface InvoiceClient { + @RequestMapping(value = "invoicesPaged", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + ResponseEntity> getInvoicesPaged(org.springframework.data.domain.Pageable pageable); + @RequestMapping(value = "invoices", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) ResponseEntity> getInvoices(); diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/app/resource/InvoiceResource.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/app/resource/InvoiceResource.java index d6030d9b..2ea31412 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/app/resource/InvoiceResource.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/app/resource/InvoiceResource.java @@ -17,6 +17,8 @@ package org.springframework.cloud.openfeign.encoding.app.resource; import org.springframework.cloud.openfeign.encoding.app.domain.Invoice; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; @@ -50,6 +52,12 @@ public class InvoiceResource { return ResponseEntity.ok(invoices); } + @RequestMapping(value = "invoicesPaged", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity> getInvoicesPaged(org.springframework.data.domain.Pageable pageable) { + Page page = new PageImpl<>(createInvoiceList(pageable.getPageSize()), pageable, 100); + return ResponseEntity.ok(page); + } + private List createInvoiceList(int count) { final List invoices = new ArrayList<>(); for (int ind = 0; ind < count; ind++) { diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageableEncoderTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageableEncoderTests.java new file mode 100644 index 00000000..7a0861b1 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageableEncoderTests.java @@ -0,0 +1,106 @@ +package org.springframework.cloud.openfeign.support; + +import feign.RequestTemplate; +import feign.codec.Encoder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.openfeign.FeignContext; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; + +/** + * Tests the pagination encoding and sorting. + * + * @author Charlie Mordant. + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = SpringEncoderTests.Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = { + "spring.application.name=springencodertest", "spring.jmx.enabled=false" }) +@DirtiesContext +public class PageableEncoderTests { + + public static final int PAGE = 1; + public static final int SIZE = 10; + public static final String SORT_2 = "sort2"; + public static final String SORT_1 = "sort1"; + @Autowired + private FeignContext context; + + @Test + public void testPaginationAndSortingRequest() { + Encoder encoder = this.context.getInstance("foo", Encoder.class); + assertThat(encoder, is(notNullValue())); + RequestTemplate request = new RequestTemplate(); + + encoder.encode(createPageAndSortRequest(), null, request); + + assertThat("Request queries shall contain three entries", + request.queries().size(), + equalTo(3)); + assertThat("Request page shall contain page", + request.queries().get("page"), + hasItem(String.valueOf(PAGE))); + assertThat("Request size shall contain size", + request.queries().get("size"), + hasItem(String.valueOf(SIZE))); + assertThat("Request sort size shall contain sort entries", + request.queries().get("sort").size(), + equalTo(2)); + } + + private Pageable createPageAndSortRequest() { + return PageRequest.of(PAGE, SIZE, Sort.Direction.ASC, SORT_1, SORT_2); + } + + @Test + public void testPaginationRequest() { + Encoder encoder = this.context.getInstance("foo", Encoder.class); + assertThat(encoder, is(notNullValue())); + RequestTemplate request = new RequestTemplate(); + encoder.encode(createPageAndRequest(), null, request); + assertThat("Request queries shall contain three entries", + request.queries().size(), + equalTo(2)); + assertThat("Request page shall contain page", + request.queries().get("page"), + hasItem(String.valueOf(PAGE))); + assertThat("Request size shall contain size", + request.queries().get("size"), + hasItem(String.valueOf(SIZE))); + assertThat("Request sort size shall contain sort entries", + request.queries().containsKey("sort"), + equalTo(false)); + } + + private Pageable createPageAndRequest() { + return PageRequest.of(PAGE, SIZE); + } + + @Test + public void testSortingRequest() { + Encoder encoder = this.context.getInstance("foo", Encoder.class); + assertThat(encoder, is(notNullValue())); + RequestTemplate request = new RequestTemplate(); + + encoder.encode(createSort(), null, request); + + assertThat("Request queries shall contain three entries", + request.queries().size(), + equalTo(1)); + assertThat("Request sort size shall contain sort entries", + request.queries().get("sort").size(), + equalTo(2)); + } + + private Sort createSort() { + return Sort.by(SORT_1, SORT_2).ascending(); + } +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java index 72a595a4..21f8356e 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java @@ -25,6 +25,7 @@ import java.util.List; import feign.RequestTemplate; import feign.codec.EncodeException; +import feign.codec.Encoder; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; @@ -78,7 +79,7 @@ public class SpringEncoderTests { @Test public void testCustomHttpMessageConverter() { - SpringEncoder encoder = this.context.getInstance("foo", SpringEncoder.class); + Encoder encoder = this.context.getInstance("foo", Encoder.class); assertThat(encoder, is(notNullValue())); RequestTemplate request = new RequestTemplate(); @@ -97,7 +98,7 @@ public class SpringEncoderTests { @Test public void testBinaryData() { - SpringEncoder encoder = this.context.getInstance("foo", SpringEncoder.class); + Encoder encoder = this.context.getInstance("foo", Encoder.class); assertThat(encoder, is(notNullValue())); RequestTemplate request = new RequestTemplate(); @@ -110,7 +111,7 @@ public class SpringEncoderTests { @Test(expected = EncodeException.class) public void testMultipartFile1() { - SpringEncoder encoder = this.context.getInstance("foo", SpringEncoder.class); + Encoder encoder = this.context.getInstance("foo", Encoder.class); assertThat(encoder, is(notNullValue())); RequestTemplate request = new RequestTemplate(); @@ -122,7 +123,7 @@ public class SpringEncoderTests { @Test public void testMultipartFile2() { - SpringEncoder encoder = this.context.getInstance("foo", SpringEncoder.class); + Encoder encoder = this.context.getInstance("foo", Encoder.class); assertThat(encoder, is(notNullValue())); RequestTemplate request = new RequestTemplate(); request = request.header("Content-Type", MediaType.MULTIPART_FORM_DATA_VALUE);