Skip to content

Commit

Permalink
Merge branch 'spring-cloud:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreTeigler authored Sep 12, 2024
2 parents 4d99120 + fec17ae commit f9b5e51
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 10 deletions.
2 changes: 2 additions & 0 deletions docs/modules/ROOT/pages/spring-cloud-openfeign.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ TIP: To use `@EnableFeignClients` annotation on `@Configuration`-annotated-class
or list them explicitly:
`@EnableFeignClients(clients = InventoryServiceFeignClient.class)`.

In order to load Spring Feign client beans in a multi-module setup, you need to specify the packages directly.

WARNING: Since `FactoryBean` objects may be instantiated before the initial context refresh should take place, and the instantiation of Spring Cloud OpenFeign Clients triggers a context refresh, they should not be declared within `FactoryBean` classes.

[[attribute-resolution-mode]]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2024 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.
Expand Down Expand Up @@ -33,6 +33,7 @@
*
* @author Pascal Büttiker
* @author Yanming Zhou
* @author Gokalp Kuscu
*/
public class PageableSpringEncoder implements Encoder {

Expand All @@ -53,6 +54,11 @@ public class PageableSpringEncoder implements Encoder {
*/
private String sortParameter = "sort";

/**
* Sort ignoreCase parameter name.
*/
private final String ignoreCase = "ignorecase";

/**
* Creates a new PageableSpringEncoder with the given delegate for fallback. If no
* delegate is provided and this encoder cant handle the request, an EncodeException
Expand Down Expand Up @@ -115,7 +121,11 @@ private void applySort(RequestTemplate template, Sort sort) {
}
}
for (Sort.Order order : sort) {
sortQueries.add(order.getProperty() + "%2C" + order.getDirection());
String sortQuery = order.getProperty() + "%2C" + order.getDirection();
if (order.isIgnoreCase()) {
sortQuery += "%2C" + ignoreCase;
}
sortQueries.add(sortQuery);
}
if (!sortQueries.isEmpty()) {
template.query(sortParameter, sortQueries);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2024 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.
Expand Down Expand Up @@ -32,6 +32,7 @@
*
* @author Hyeonmin Park
* @author Yanming Zhou
* @author Gokalp Kuscu
* @since 2.2.8
*/
public class PageableSpringQueryMapEncoder extends BeanQueryMapEncoder {
Expand All @@ -51,6 +52,11 @@ public class PageableSpringQueryMapEncoder extends BeanQueryMapEncoder {
*/
private String sortParameter = "sort";

/**
* Sort ignoreCase parameter name.
*/
private final String ignoreCase = "ignorecase";

public void setPageParameter(String pageParameter) {
this.pageParameter = pageParameter;
}
Expand Down Expand Up @@ -92,7 +98,11 @@ else if (object instanceof Sort sort) {
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());
String sortQuery = order.getProperty() + "%2C" + order.getDirection();
if (order.isIgnoreCase()) {
sortQuery += "%2C" + ignoreCase;
}
sortQueries.add(sortQuery);
}
if (!sortQueries.isEmpty()) {
queryMap.put(sortParameter, sortQueries);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
*
* @author Can Bezmen
* @author Olga Maciaszek-Sharma
* @author Gokalp Kuscu
*/
public class SortJsonComponent {

Expand Down Expand Up @@ -90,8 +91,23 @@ public Class<Sort> handledType() {
private static Sort toSort(ArrayNode arrayNode) {
List<Sort.Order> orders = new ArrayList<>();
for (JsonNode jsonNode : arrayNode) {
Sort.Order order = new Sort.Order(Sort.Direction.valueOf(jsonNode.get("direction").textValue()),
jsonNode.get("property").textValue());
Sort.Order order;
// there is no way to construct without null handling
if ((jsonNode.has("ignoreCase") && jsonNode.get("ignoreCase").isBoolean())
&& jsonNode.has("nullHandling") && jsonNode.get("nullHandling").isTextual()) {

boolean ignoreCase = jsonNode.get("ignoreCase").asBoolean();
String nullHandlingValue = jsonNode.get("nullHandling").textValue();

order = new Sort.Order(Sort.Direction.valueOf(jsonNode.get("direction").textValue()),
jsonNode.get("property").textValue(), ignoreCase,
Sort.NullHandling.valueOf(nullHandlingValue));
}
else {
// backward compatibility
order = new Sort.Order(Sort.Direction.valueOf(jsonNode.get("direction").textValue()),
jsonNode.get("property").textValue());
}
orders.add(order);
}
return Sort.by(orders);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2024 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.
Expand Down Expand Up @@ -53,6 +53,7 @@
*
* @author Charlie Mordant.
* @author Hyeonmin Park
* @author Gokalp Kuscu
*/
@SpringBootTest(classes = FeignPageableEncodingTests.Application.class, webEnvironment = RANDOM_PORT,
value = { "spring.cloud.openfeign.compression.request.enabled=true" })
Expand Down Expand Up @@ -264,6 +265,110 @@ void testSortWithBody() {
}
}

@Test
void testPageableWithIgnoreCase() {
// given
Sort.Order anySorting = Sort.Order.asc("anySorting").ignoreCase();
Pageable pageable = PageRequest.of(0, 10, Sort.by(anySorting));

// 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()).hasSize(1);
Optional<Sort.Order> optionalOrder = response.getBody().getPageable().getSort().get().findFirst();
assertThat(optionalOrder.isPresent()).isEqualTo(true);
if (optionalOrder.isPresent()) {
Sort.Order order = optionalOrder.get();
assertThat(order.getDirection()).isEqualTo(Sort.Direction.ASC);
assertThat(order.getProperty()).isEqualTo("anySorting");
assertThat(order.isIgnoreCase()).as("isIgnoreCase does not have expected value").isEqualTo(true);
assertThat(order.getNullHandling()).isEqualTo(Sort.NullHandling.NATIVE);
}
}

@Test
void testSortWithIgnoreCaseAndBody() {
// given
Sort.Order anySorting = Sort.Order.desc("amount").ignoreCase();
Sort sort = Sort.by(anySorting);

// 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());

Optional<Sort.Order> optionalOrder = response.getBody().getPageable().getSort().get().findFirst();
assertThat(optionalOrder.isPresent()).isEqualTo(true);
if (optionalOrder.isPresent()) {
Sort.Order order = optionalOrder.get();
assertThat(order.isIgnoreCase()).as("isIgnoreCase does not have expected value").isEqualTo(true);
assertThat(order.getNullHandling()).isEqualTo(Sort.NullHandling.NATIVE);
}

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());
}

}

@Test
void testPageableMultipleSortPropertiesWithBodyAndIgnoreCase() {
// given
Sort.Order anySorting1 = Sort.Order.desc("anySorting1").ignoreCase();
Sort.Order anySorting2 = Sort.Order.asc("anySorting2");
Pageable pageable = PageRequest.of(0, 10, Sort.by(anySorting1, anySorting2));

// 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("anySorting1");
assertThat(firstOrder.isIgnoreCase()).as("isIgnoreCase does not have expected value").isEqualTo(true);
assertThat(firstOrder.getNullHandling()).isEqualTo(Sort.NullHandling.NATIVE);

Sort.Order secondOrder = orderList.get(1);
assertThat(secondOrder.getDirection()).isEqualTo(Sort.Direction.ASC);
assertThat(secondOrder.getProperty()).isEqualTo("anySorting2");
assertThat(secondOrder.isIgnoreCase()).as("isIgnoreCase does not have expected value").isEqualTo(false);
assertThat(secondOrder.getNullHandling()).isEqualTo(Sort.NullHandling.NATIVE);
}

@EnableFeignClients(clients = InvoiceClient.class)
@LoadBalancerClient(name = "local", configuration = LocalClientConfiguration.class)
@SpringBootApplication(scanBasePackages = "org.springframework.cloud.openfeign.encoding.app",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2024 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.
Expand Down Expand Up @@ -39,6 +39,7 @@

/**
* @author Can Bezmen
* @author Gokalp Kuscu
*/
@ExtendWith(MockitoExtension.class)
class SortJacksonModuleTests {
Expand All @@ -53,7 +54,7 @@ public void setup() {
}

@Test
public void deserializePage() throws JsonProcessingException {
public void testDeserializePage() throws JsonProcessingException {
// Given
String pageJson = "{\"content\":[\"A name\"],\"number\":1,\"size\":2,\"totalElements\":3,\"sort\":[{\"direction\":\"ASC\",\"property\":\"field\",\"ignoreCase\":false,\"nullHandling\":\"NATIVE\",\"descending\":false,\"ascending\":true}]}";
// When
Expand All @@ -72,11 +73,85 @@ public void deserializePage() throws JsonProcessingException {
Sort.Order order = optionalOrder.get();
assertThat(order, hasProperty("property", is("field")));
assertThat(order, hasProperty("direction", is(Sort.Direction.ASC)));
assertThat(order, hasProperty("ignoreCase", is(false)));
assertThat(order, hasProperty("nullHandling", is(Sort.NullHandling.NATIVE)));
}
}

@Test
public void serializePage() throws IOException {
public void testDeserializePageWithoutIgnoreCaseAndNullHandling() throws JsonProcessingException {
// Given
String pageJson = "{\"content\":[\"A name\"],\"number\":1,\"size\":2,\"totalElements\":3,\"sort\":[{\"direction\":\"ASC\",\"property\":\"field\",\"descending\":false,\"ascending\":true}]}";
// When
Page<?> result = objectMapper.readValue(pageJson, Page.class);
// Then
assertThat(result, notNullValue());
assertThat(result, hasProperty("totalElements", is(3L)));
assertThat(result.getContent(), hasSize(1));
assertThat(result.getPageable(), notNullValue());
assertThat(result.getPageable().getPageNumber(), is(1));
assertThat(result.getPageable().getPageSize(), is(2));
assertThat(result.getPageable().getSort(), notNullValue());
result.getPageable().getSort();
Optional<Sort.Order> optionalOrder = result.getPageable().getSort().get().findFirst();
if (optionalOrder.isPresent()) {
Sort.Order order = optionalOrder.get();
assertThat(order, hasProperty("property", is("field")));
assertThat(order, hasProperty("direction", is(Sort.Direction.ASC)));
}
}

@Test
public void testDeserializePageWithoutNullHandling() throws JsonProcessingException {
// Given
String pageJson = "{\"content\":[\"A name\"],\"number\":1,\"size\":2,\"totalElements\":3,\"sort\":[{\"direction\":\"ASC\",\"property\":\"field\",\"ignoreCase\":true,\"descending\":false,\"ascending\":true}]}";
// When
Page<?> result = objectMapper.readValue(pageJson, Page.class);
// Then
assertThat(result, notNullValue());
assertThat(result, hasProperty("totalElements", is(3L)));
assertThat(result.getContent(), hasSize(1));
assertThat(result.getPageable(), notNullValue());
assertThat(result.getPageable().getPageNumber(), is(1));
assertThat(result.getPageable().getPageSize(), is(2));
assertThat(result.getPageable().getSort(), notNullValue());
result.getPageable().getSort();
Optional<Sort.Order> optionalOrder = result.getPageable().getSort().get().findFirst();
if (optionalOrder.isPresent()) {
Sort.Order order = optionalOrder.get();
assertThat(order, hasProperty("property", is("field")));
assertThat(order, hasProperty("direction", is(Sort.Direction.ASC)));
assertThat(order, hasProperty("ignoreCase", is(false)));
}
}

@Test
public void testDeserializePageWithTrueMarkedIgnoreCaseAndNullHandling() throws JsonProcessingException {
// Given
String pageJson = "{\"content\":[\"A name\"],\"number\":1,\"size\":2,\"totalElements\":3,\"sort\":[{\"direction\":\"ASC\",\"property\":\"field\",\"ignoreCase\":true,\"nullHandling\":\"NATIVE\",\"descending\":false,\"ascending\":true}]}";
// When
Page<?> result = objectMapper.readValue(pageJson, Page.class);
// Then
assertThat(result, notNullValue());
assertThat(result, hasProperty("totalElements", is(3L)));
assertThat(result.getContent(), hasSize(1));
assertThat(result.getPageable(), notNullValue());
assertThat(result.getPageable().getPageNumber(), is(1));
assertThat(result.getPageable().getPageSize(), is(2));
assertThat(result.getPageable().getSort(), notNullValue());
result.getPageable().getSort();
Optional<Sort.Order> optionalOrder = result.getPageable().getSort().get().findFirst();
if (optionalOrder.isPresent()) {
Sort.Order order = optionalOrder.get();
assertThat(order, hasProperty("property", is("field")));
assertThat(order, hasProperty("direction", is(Sort.Direction.ASC)));
assertThat(order, hasProperty("ignoreCase", is(true)));
assertThat(order, hasProperty("nullHandling", is(Sort.NullHandling.NATIVE)));
}
}

@Test
public void testSerializePage() throws IOException {
// Given
Sort sort = Sort.by(Sort.Order.by("fieldName"));
// When
Expand All @@ -86,4 +161,16 @@ public void serializePage() throws IOException {
assertThat(result, containsString("\"property\":\"fieldName\""));
}

@Test
public void testSerializePageWithGivenIgnoreCase() throws IOException {
// Given
Sort sort = Sort.by(Sort.Order.by("fieldName"), Sort.Order.by("fieldName2").ignoreCase());
// When
String result = objectMapper.writeValueAsString(sort);
// Then
assertThat(result, containsString("\"direction\":\"ASC\""));
assertThat(result, containsString("\"property\":\"fieldName\""));
assertThat(result, containsString("\"property\":\"fieldName2\",\"ignoreCase\":true"));
}

}

0 comments on commit f9b5e51

Please sign in to comment.