diff --git a/spring-cloud-openfeign-core/pom.xml b/spring-cloud-openfeign-core/pom.xml index 2d10c85d..1f590c46 100644 --- a/spring-cloud-openfeign-core/pom.xml +++ b/spring-cloud-openfeign-core/pom.xml @@ -188,6 +188,11 @@ spring-cloud-test-support test + + org.springframework.data + spring-data-commons + true + io.projectreactor reactor-test diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/EnableFeignClients.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/EnableFeignClients.java index 0c86415b..bd6c790b 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/EnableFeignClients.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/EnableFeignClients.java @@ -25,10 +25,11 @@ import java.lang.annotation.Target; import org.springframework.context.annotation.Import; /** - * Scans for interfaces that declare they are feign clients (via {@link FeignClient - * @FeignClient}). Configures component scanning directives for use with - * {@link org.springframework.context.annotation.Configuration - * @Configuration} classes. + * Scans for interfaces that declare they are feign clients (via + * {@link org.springframework.cloud.openfeign.FeignClient} @FeignClient). + * Configures component scanning directives for use with + * {@link org.springframework.context.annotation.Configuration} + * @Configuration classes. * * @author Spencer Gibb * @author Dave Syer 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 ce0e91dc..430dbd20 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 @@ -19,6 +19,7 @@ package org.springframework.cloud.openfeign; import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.databind.Module; import com.netflix.hystrix.HystrixCommand; import feign.Contract; import feign.Feign; @@ -33,8 +34,11 @@ 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.PageJacksonModule; +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; @@ -43,6 +47,8 @@ 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.Page; +import org.springframework.data.domain.Pageable; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.format.support.FormattingConversionService; @@ -74,10 +80,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) { @@ -112,6 +126,12 @@ public class FeignClientsConfiguration { return new DefaultFeignLoggerFactory(this.logger); } + @Bean + @ConditionalOnClass(Page.class) + public Module pageJacksonModule() { + return new PageJacksonModule(); + } + @Configuration @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class }) protected static class HystrixFeignConfiguration { 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..166b6103 --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageJacksonModule.java @@ -0,0 +1,171 @@ +/* + * Copyright 2013-2018 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.support; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +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.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +/** + * This jackson module provides support to deserialize spring {@link 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; + + 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(); + } + + } + +} 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..ead1816b --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageableSpringEncoder.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013-2018 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.support; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import feign.RequestTemplate; +import feign.codec.EncodeException; +import feign.codec.Encoder; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +/** + * 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; + } + +} 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..d749c069 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EnableFeignClientsSpringDataTests.java @@ -0,0 +1,58 @@ +/* + * 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; + +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 77f5dbb8..1164aaf7 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2019 the original author or authors. + * 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. @@ -23,61 +23,72 @@ import feign.codec.Decoder; import feign.codec.Encoder; import feign.optionals.OptionalDecoder; import feign.slf4j.Slf4jLogger; +import org.junit.After; +import org.junit.Before; 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.boot.WebApplicationType; +import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration; import org.springframework.cloud.openfeign.support.SpringEncoder; import org.springframework.cloud.openfeign.support.SpringMvcContract; +import org.springframework.cloud.test.ClassPathExclusions; +import org.springframework.cloud.test.ModifiedClassPathRunner; +import org.springframework.context.ConfigurableApplicationContext; 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 = EnableFeignClientsTests.PlainConfiguration.class) -@DirtiesContext +@RunWith(ModifiedClassPathRunner.class) +@ClassPathExclusions({ "spring-data-commons-*.jar" }) public class EnableFeignClientsTests { - @Autowired - private FeignContext feignContext; + private ConfigurableApplicationContext context; + + @Before + public void setUp() { + context = new SpringApplicationBuilder().web(WebApplicationType.NONE) + .properties("debug=true", "feign.httpclient.enabled=false") + .sources(EnableFeignClientsTests.PlainConfiguration.class).run(); + } + + @After + public void tearDown() { + if (context != null) { + context.close(); + } + } @Test public void decoderDefaultCorrect() { - OptionalDecoder.class.cast(this.feignContext.getInstance("foo", Decoder.class)); + OptionalDecoder.class.cast(this.context.getBeansOfType(Decoder.class).get(0)); } @Test public void encoderDefaultCorrect() { - SpringEncoder.class.cast(this.feignContext.getInstance("foo", Encoder.class)); + SpringEncoder.class.cast(this.context.getBeansOfType(Encoder.class).get(0)); } @Test public void loggerDefaultCorrect() { - Slf4jLogger.class.cast(this.feignContext.getInstance("foo", Logger.class)); + Slf4jLogger.class.cast(this.context.getBeansOfType(Logger.class).get(0)); } @Test public void contractDefaultCorrect() { - SpringMvcContract.class - .cast(this.feignContext.getInstance("foo", Contract.class)); + SpringMvcContract.class.cast(this.context.getBeansOfType(Contract.class).get(0)); } @Test public void builderDefaultCorrect() { - Feign.Builder.class - .cast(this.feignContext.getInstance("foo", Feign.Builder.class)); + Feign.Builder.class.cast(this.context.getBeansOfType(Feign.Builder.class).get(0)); } @Configuration - @Import({ PropertyPlaceholderAutoConfiguration.class, ArchaiusAutoConfiguration.class, - FeignAutoConfiguration.class }) + @Import({ ArchaiusAutoConfiguration.class, FeignAutoConfiguration.class }) protected static class PlainConfiguration { } 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 aa3a86e4..455379eb 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 @@ -38,7 +38,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; @@ -82,7 +82,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/encoding/FeignPageableEncodingTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java new file mode 100644 index 00000000..424fb1ad --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java @@ -0,0 +1,110 @@ +/* + * 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 java.util.Collections; + +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.cloud.netflix.ribbon.RibbonClient; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.cloud.openfeign.FeignClientsConfiguration; +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 static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * Tests the pagination encoding. + * + * @author Charlie Mordant. + */ +@SpringBootTest(classes = FeignPageableEncodingTests.Application.class, 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 + assertThat(response).isNotNull(); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(pageable.getPageSize()).isEqualTo(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, FeignClientsConfiguration.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; + } + + } + +} 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 34229888..47076d2a 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,10 @@ 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 4ccaad73..a5e3176e 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 @@ -22,6 +22,8 @@ import java.util.List; import java.util.Locale; 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; @@ -49,6 +51,14 @@ 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..50015e1b --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageableEncoderTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2013-2019 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.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.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * Tests the pagination encoding and sorting. + * + * @author Charlie Mordant. + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = SpringEncoderTests.Application.class, 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).isNotNull(); + RequestTemplate request = new RequestTemplate(); + encoder.encode(createPageAndSortRequest(), null, request); + + // Request queries shall contain three entries + assertThat(request.queries().size()).isEqualTo(3); + // Request page shall contain page + assertThat(request.queries().get("page")).contains(String.valueOf(PAGE)); + // Request size shall contain size + assertThat(request.queries().get("size")).contains(String.valueOf(SIZE)); + // Request sort size shall contain sort entries + assertThat(request.queries().get("sort").size()).isEqualTo(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).isNotNull(); + RequestTemplate request = new RequestTemplate(); + encoder.encode(createPageAndRequest(), null, request); + assertThat(request.queries().size()).isEqualTo(2); + // Request page shall contain page + assertThat(request.queries().get("page")).contains(String.valueOf(PAGE)); + // Request size shall contain size + assertThat(request.queries().get("size")).contains(String.valueOf(SIZE)); + // Request sort size shall contain sort entries + assertThat(request.queries().containsKey("sort")).isEqualTo(false); + } + + private Pageable createPageAndRequest() { + return PageRequest.of(PAGE, SIZE); + } + + @Test + public void testSortingRequest() { + Encoder encoder = this.context.getInstance("foo", Encoder.class); + assertThat(encoder).isNotNull(); + RequestTemplate request = new RequestTemplate(); + + encoder.encode(createSort(), null, request); + // Request queries shall contain three entries + assertThat(request.queries().size()).isEqualTo(1); + // Request sort size shall contain sort entries + assertThat(request.queries().get("sort").size()).isEqualTo(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 bd970035..ef7be6a5 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 @@ -24,6 +24,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; @@ -75,7 +76,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).isNotNull(); RequestTemplate request = new RequestTemplate(); @@ -97,8 +98,9 @@ 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).isNotNull(); + RequestTemplate request = new RequestTemplate(); encoder.encode("hi".getBytes(), null, request); @@ -110,7 +112,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).isNotNull(); RequestTemplate request = new RequestTemplate(); @@ -124,7 +126,7 @@ public class SpringEncoderTests { // gh-105, gh-107 @Test public void testMultipartFile2() { - SpringEncoder encoder = this.context.getInstance("foo", SpringEncoder.class); + Encoder encoder = this.context.getInstance("foo", Encoder.class); assertThat(encoder).isNotNull(); RequestTemplate request = new RequestTemplate(); request.header(ACCEPT, MediaType.MULTIPART_FORM_DATA_VALUE); diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index d55c3a1d..2bd6edec 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -12,7 +12,6 @@ -