Support Pageable, Sort annotated as SpringQueryMap to use with RequestBody (#502)

This commit is contained in:
Hyeonmin Park
2021-03-11 01:30:36 +09:00
committed by GitHub
parent 6b436c4133
commit 2a23b6d304
5 changed files with 296 additions and 7 deletions

View File

@@ -23,6 +23,7 @@ import com.netflix.hystrix.HystrixCommand;
import feign.Contract;
import feign.Feign;
import feign.Logger;
import feign.QueryMapEncoder;
import feign.Retryer;
import feign.codec.Decoder;
import feign.codec.Encoder;
@@ -47,6 +48,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.PageableSpringEncoder;
import org.springframework.cloud.openfeign.support.PageableSpringQueryMapEncoder;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;
@@ -65,6 +67,7 @@ import static feign.form.ContentType.MULTIPART;
* @author Venil Noronha
* @author Darren Foong
* @author Olga Maciaszek-Sharma
* @author Hyeonmin Park
*/
@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {
@@ -123,6 +126,13 @@ public class FeignClientsConfiguration {
return encoder;
}
@Bean
@ConditionalOnClass(name = "org.springframework.data.domain.Pageable")
@ConditionalOnMissingBean
public QueryMapEncoder feignQueryMapEncoderPageable() {
return new PageableSpringQueryMapEncoder();
}
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2013-2021 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.HashMap;
import java.util.List;
import java.util.Map;
import feign.querymap.BeanQueryMapEncoder;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
/**
* Provides support for encoding Pageable annotated as
* {@link org.springframework.cloud.openfeign.SpringQueryMap}.
*
* @author Hyeonmin Park
* @since 2.2.8
*/
public class PageableSpringQueryMapEncoder extends BeanQueryMapEncoder {
@Override
public Map<String, Object> encode(Object object) {
if (supports(object)) {
Map<String, Object> queryMap = new HashMap<>();
if (object instanceof Pageable) {
Pageable pageable = (Pageable) object;
if (pageable.isPaged()) {
queryMap.put("page", pageable.getPageNumber());
queryMap.put("size", pageable.getPageSize());
}
if (pageable.getSort() != null) {
applySort(queryMap, pageable.getSort());
}
}
else if (object instanceof Sort) {
Sort sort = (Sort) object;
applySort(queryMap, sort);
}
return queryMap;
}
else {
return super.encode(object);
}
}
private void applySort(Map<String, Object> queryMap, Sort sort) {
List<String> sortQueries = new ArrayList<>();
for (Sort.Order order : sort) {
sortQueries.add(order.getProperty() + "%2C" + order.getDirection());
}
if (!sortQueries.isEmpty()) {
queryMap.put("sort", sortQueries);
}
}
protected boolean supports(Object object) {
return object instanceof Pageable || object instanceof Sort;
}
}

View File

@@ -71,7 +71,6 @@ public class FeignPageableEncodingTests {
@Test
public void testPageable() {
// given
Pageable pageable = PageRequest.of(0, 10, Sort.Direction.ASC, "sortProperty");
@@ -92,7 +91,6 @@ public class FeignPageableEncodingTests {
assertThat(order.getDirection()).isEqualTo(Sort.Direction.ASC);
assertThat(order.getProperty()).isEqualTo("sortProperty");
}
}
@Test
@@ -120,7 +118,6 @@ public class FeignPageableEncodingTests {
Sort.Order order = optionalOrder.get();
assertThat(order.getDirection()).isEqualTo(Sort.Direction.DESC);
assertThat(order.getProperty()).isEqualTo("sortProperty");
}
@Test
@@ -152,7 +149,133 @@ public class FeignPageableEncodingTests {
Sort.Order secondOrder = orderList.get(1);
assertThat(secondOrder.getDirection()).isEqualTo(Sort.Direction.ASC);
assertThat(secondOrder.getProperty()).isEqualTo("sortProperty2");
}
@Test
public void testPageableWithoutSort() {
// given
Pageable pageable = PageRequest.of(0, 10);
// when
final ResponseEntity<Page<Invoice>> 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());
assertThat(response.getBody().getPageable().getSort().isSorted()).isFalse();
List<Invoice> invoiceList = response.getBody().getContent();
assertThat(invoiceList).hasSizeGreaterThanOrEqualTo(1);
}
@Test
public void testPageableWithoutSortWithBody() {
// given
Pageable pageable = PageRequest.of(0, 10);
// when
final ResponseEntity<Page<Invoice>> response = this.invoiceClient
.getInvoicesPagedWithBody(pageable, "InvoiceTitleFromBody");
// then
assertThat(response).isNotNull();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
assertThat(pageable.getPageSize()).isEqualTo(response.getBody().getSize());
List<Invoice> invoiceList = response.getBody().getContent();
assertThat(invoiceList).hasSizeGreaterThanOrEqualTo(1);
Invoice firstInvoice = invoiceList.get(0);
assertThat(firstInvoice.getTitle()).startsWith("InvoiceTitleFromBody");
}
@Test
public void testPageableWithBody() {
// given
Pageable pageable = PageRequest.of(0, 10, Sort
.by(Sort.Order.desc("sortProperty1"), Sort.Order.asc("sortProperty2")));
// when
final ResponseEntity<Page<Invoice>> response = this.invoiceClient
.getInvoicesPagedWithBody(pageable, "InvoiceTitleFromBody");
// then
assertThat(response).isNotNull();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
assertThat(pageable.getPageSize()).isEqualTo(response.getBody().getSize());
List<Invoice> invoiceList = response.getBody().getContent();
assertThat(invoiceList).hasSizeGreaterThanOrEqualTo(1);
Invoice firstInvoice = invoiceList.get(0);
assertThat(firstInvoice.getTitle()).startsWith("InvoiceTitleFromBody");
Sort sort = response.getBody().getPageable().getSort();
assertThat(sort).hasSize(2);
List<Sort.Order> orderList = sort.toList();
assertThat(orderList).hasSize(2);
Sort.Order firstOrder = orderList.get(0);
assertThat(firstOrder.getDirection()).isEqualTo(Sort.Direction.DESC);
assertThat(firstOrder.getProperty()).isEqualTo("sortProperty1");
Sort.Order secondOrder = orderList.get(1);
assertThat(secondOrder.getDirection()).isEqualTo(Sort.Direction.ASC);
assertThat(secondOrder.getProperty()).isEqualTo("sortProperty2");
}
@Test
public void testUnpagedWithBody() {
// given
Pageable unpaged = Pageable.unpaged();
// when
final ResponseEntity<Page<Invoice>> response = this.invoiceClient
.getInvoicesPagedWithBody(unpaged, "InvoiceTitleFromBody");
// then
assertThat(response).isNotNull();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
List<Invoice> invoiceList = response.getBody().getContent();
assertThat(invoiceList).hasSizeGreaterThanOrEqualTo(1);
Invoice firstInvoice = invoiceList.get(0);
assertThat(firstInvoice.getTitle()).startsWith("InvoiceTitleFromBody");
}
@Test
public void testSortWithBody() {
// given
Sort sort = Sort.by(Sort.Order.desc("amount"));
// when
final ResponseEntity<Page<Invoice>> response = this.invoiceClient
.getInvoicesSortedWithBody(sort, "InvoiceTitleFromBody");
// then
assertThat(response).isNotNull();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
assertThat(sort).isEqualTo(response.getBody().getSort());
List<Invoice> invoiceList = response.getBody().getContent();
assertThat(invoiceList).hasSizeGreaterThanOrEqualTo(1);
Invoice firstInvoice = invoiceList.get(0);
assertThat(firstInvoice.getTitle()).startsWith("InvoiceTitleFromBody");
for (int ind = 0; ind < invoiceList.size() - 1; ind++) {
assertThat(invoiceList.get(ind).getAmount())
.isGreaterThanOrEqualTo(invoiceList.get(ind + 1).getAmount());
}
}
@EnableFeignClients(clients = InvoiceClient.class)

View File

@@ -19,10 +19,12 @@ package org.springframework.cloud.openfeign.encoding.app.client;
import java.util.List;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -30,6 +32,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
* Simple Feign client for retrieving the invoice list.
*
* @author Jakub Narloch
* @author Hyeonmin Park
*/
@FeignClient("local")
public interface InvoiceClient {
@@ -39,6 +42,20 @@ public interface InvoiceClient {
ResponseEntity<Page<Invoice>> getInvoicesPaged(
org.springframework.data.domain.Pageable pageable);
@RequestMapping(value = "invoicesPagedWithBody", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Page<Invoice>> getInvoicesPagedWithBody(
@SpringQueryMap org.springframework.data.domain.Pageable pageable,
@RequestBody String titlePrefix);
@RequestMapping(value = "invoicesSortedWithBody", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Page<Invoice>> getInvoicesSortedWithBody(
@SpringQueryMap org.springframework.data.domain.Sort sort,
@RequestBody String titlePrefix);
@RequestMapping(value = "invoices", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<List<Invoice>> getInvoices();

View File

@@ -18,12 +18,14 @@ package org.springframework.cloud.openfeign.encoding.app.resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
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.data.domain.PageRequest;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
@@ -35,6 +37,7 @@ import org.springframework.web.bind.annotation.RestController;
* An sample REST controller, that potentially returns large response - used for testing.
*
* @author Jakub Narloch
* @author Hyeonmin Park
*/
@RestController
public class InvoiceResource {
@@ -43,7 +46,7 @@ public class InvoiceResource {
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Invoice>> getInvoices() {
return ResponseEntity.ok(createInvoiceList(100));
return ResponseEntity.ok(createInvoiceList(null, 100, null));
}
@RequestMapping(value = "invoices", method = RequestMethod.POST,
@@ -58,20 +61,76 @@ public class InvoiceResource {
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Page<Invoice>> getInvoicesPaged(
org.springframework.data.domain.Pageable pageable) {
Page<Invoice> page = new PageImpl<>(createInvoiceList(pageable.getPageSize()),
Page<Invoice> page = new PageImpl<>(
createInvoiceList(null, pageable.getPageSize(), pageable.getSort()),
pageable, 100);
return ResponseEntity.ok(page);
}
private List<Invoice> createInvoiceList(int count) {
@RequestMapping(value = "invoicesPagedWithBody", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Page<Invoice>> getInvoicesPagedWithBody(
org.springframework.data.domain.Pageable pageable,
@RequestBody String titlePrefix) {
Page<Invoice> page = new PageImpl<>(createInvoiceList(titlePrefix,
pageable.getPageSize(), pageable.getSort()), pageable, 100);
return ResponseEntity.ok(page);
}
@RequestMapping(value = "invoicesSortedWithBody", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Page<Invoice>> getInvoicesSortedWithBody(
org.springframework.data.domain.Sort sort, @RequestBody String titlePrefix) {
Page<Invoice> page = new PageImpl<>(createInvoiceList(titlePrefix, 100, sort),
PageRequest.of(0, 100, sort), 100);
return ResponseEntity.ok(page);
}
private List<Invoice> createInvoiceList(String titlePrefix, int count,
org.springframework.data.domain.Sort sort) {
if (titlePrefix == null) {
titlePrefix = "Invoice";
}
final List<Invoice> invoices = new ArrayList<>();
for (int ind = 0; ind < count; ind++) {
final Invoice invoice = new Invoice();
invoice.setTitle("Invoice " + (ind + 1));
invoice.setTitle(titlePrefix + " " + (ind + 1));
invoice.setAmount(new BigDecimal(
String.format(Locale.US, "%.2f", Math.random() * 1000)));
invoices.add(invoice);
}
if (sort != null) {
Comparator<Invoice> comparatorForSort = null;
for (org.springframework.data.domain.Sort.Order order : sort) {
Comparator<Invoice> comparatorForOrder;
if (order.getProperty().equals("title")) {
comparatorForOrder = Comparator.comparing(Invoice::getTitle);
}
else if (order.getProperty().equals("amount")) {
comparatorForOrder = Comparator.comparing(Invoice::getAmount);
}
else {
continue;
}
if (order.isDescending()) {
comparatorForOrder = comparatorForOrder.reversed();
}
if (comparatorForSort == null) {
comparatorForSort = comparatorForOrder;
}
else {
comparatorForSort = comparatorForSort
.thenComparing(comparatorForOrder);
}
}
if (comparatorForSort != null) {
invoices.sort(comparatorForSort);
}
}
return invoices;
}