Skip to content

Commit fec17ae

Browse files
authored
Adds ignorecase field to pageable spring (#1047)
1 parent d3ecd5c commit fec17ae

File tree

5 files changed

+238
-10
lines changed

5 files changed

+238
-10
lines changed

spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageableSpringEncoder.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2022 the original author or authors.
2+
* Copyright 2013-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@
3333
*
3434
* @author Pascal Büttiker
3535
* @author Yanming Zhou
36+
* @author Gokalp Kuscu
3637
*/
3738
public class PageableSpringEncoder implements Encoder {
3839

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

57+
/**
58+
* Sort ignoreCase parameter name.
59+
*/
60+
private final String ignoreCase = "ignorecase";
61+
5662
/**
5763
* Creates a new PageableSpringEncoder with the given delegate for fallback. If no
5864
* delegate is provided and this encoder cant handle the request, an EncodeException
@@ -115,7 +121,11 @@ private void applySort(RequestTemplate template, Sort sort) {
115121
}
116122
}
117123
for (Sort.Order order : sort) {
118-
sortQueries.add(order.getProperty() + "%2C" + order.getDirection());
124+
String sortQuery = order.getProperty() + "%2C" + order.getDirection();
125+
if (order.isIgnoreCase()) {
126+
sortQuery += "%2C" + ignoreCase;
127+
}
128+
sortQueries.add(sortQuery);
119129
}
120130
if (!sortQueries.isEmpty()) {
121131
template.query(sortParameter, sortQueries);

spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageableSpringQueryMapEncoder.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2022 the original author or authors.
2+
* Copyright 2013-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@
3232
*
3333
* @author Hyeonmin Park
3434
* @author Yanming Zhou
35+
* @author Gokalp Kuscu
3536
* @since 2.2.8
3637
*/
3738
public class PageableSpringQueryMapEncoder extends BeanQueryMapEncoder {
@@ -51,6 +52,11 @@ public class PageableSpringQueryMapEncoder extends BeanQueryMapEncoder {
5152
*/
5253
private String sortParameter = "sort";
5354

55+
/**
56+
* Sort ignoreCase parameter name.
57+
*/
58+
private final String ignoreCase = "ignorecase";
59+
5460
public void setPageParameter(String pageParameter) {
5561
this.pageParameter = pageParameter;
5662
}
@@ -92,7 +98,11 @@ else if (object instanceof Sort sort) {
9298
private void applySort(Map<String, Object> queryMap, Sort sort) {
9399
List<String> sortQueries = new ArrayList<>();
94100
for (Sort.Order order : sort) {
95-
sortQueries.add(order.getProperty() + "%2C" + order.getDirection());
101+
String sortQuery = order.getProperty() + "%2C" + order.getDirection();
102+
if (order.isIgnoreCase()) {
103+
sortQuery += "%2C" + ignoreCase;
104+
}
105+
sortQueries.add(sortQuery);
96106
}
97107
if (!sortQueries.isEmpty()) {
98108
queryMap.put(sortParameter, sortQueries);

spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJsonComponent.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
*
4040
* @author Can Bezmen
4141
* @author Olga Maciaszek-Sharma
42+
* @author Gokalp Kuscu
4243
*/
4344
public class SortJsonComponent {
4445

@@ -90,8 +91,23 @@ public Class<Sort> handledType() {
9091
private static Sort toSort(ArrayNode arrayNode) {
9192
List<Sort.Order> orders = new ArrayList<>();
9293
for (JsonNode jsonNode : arrayNode) {
93-
Sort.Order order = new Sort.Order(Sort.Direction.valueOf(jsonNode.get("direction").textValue()),
94-
jsonNode.get("property").textValue());
94+
Sort.Order order;
95+
// there is no way to construct without null handling
96+
if ((jsonNode.has("ignoreCase") && jsonNode.get("ignoreCase").isBoolean())
97+
&& jsonNode.has("nullHandling") && jsonNode.get("nullHandling").isTextual()) {
98+
99+
boolean ignoreCase = jsonNode.get("ignoreCase").asBoolean();
100+
String nullHandlingValue = jsonNode.get("nullHandling").textValue();
101+
102+
order = new Sort.Order(Sort.Direction.valueOf(jsonNode.get("direction").textValue()),
103+
jsonNode.get("property").textValue(), ignoreCase,
104+
Sort.NullHandling.valueOf(nullHandlingValue));
105+
}
106+
else {
107+
// backward compatibility
108+
order = new Sort.Order(Sort.Direction.valueOf(jsonNode.get("direction").textValue()),
109+
jsonNode.get("property").textValue());
110+
}
95111
orders.add(order);
96112
}
97113
return Sort.by(orders);

spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2022 the original author or authors.
2+
* Copyright 2013-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -53,6 +53,7 @@
5353
*
5454
* @author Charlie Mordant.
5555
* @author Hyeonmin Park
56+
* @author Gokalp Kuscu
5657
*/
5758
@SpringBootTest(classes = FeignPageableEncodingTests.Application.class, webEnvironment = RANDOM_PORT,
5859
value = { "spring.cloud.openfeign.compression.request.enabled=true" })
@@ -264,6 +265,110 @@ void testSortWithBody() {
264265
}
265266
}
266267

268+
@Test
269+
void testPageableWithIgnoreCase() {
270+
// given
271+
Sort.Order anySorting = Sort.Order.asc("anySorting").ignoreCase();
272+
Pageable pageable = PageRequest.of(0, 10, Sort.by(anySorting));
273+
274+
// when
275+
final ResponseEntity<Page<Invoice>> response = this.invoiceClient.getInvoicesPaged(pageable);
276+
277+
// then
278+
assertThat(response).isNotNull();
279+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
280+
assertThat(response.getBody()).isNotNull();
281+
assertThat(pageable.getPageSize()).isEqualTo(response.getBody().getSize());
282+
assertThat(response.getBody().getPageable().getSort()).hasSize(1);
283+
Optional<Sort.Order> optionalOrder = response.getBody().getPageable().getSort().get().findFirst();
284+
assertThat(optionalOrder.isPresent()).isEqualTo(true);
285+
if (optionalOrder.isPresent()) {
286+
Sort.Order order = optionalOrder.get();
287+
assertThat(order.getDirection()).isEqualTo(Sort.Direction.ASC);
288+
assertThat(order.getProperty()).isEqualTo("anySorting");
289+
assertThat(order.isIgnoreCase()).as("isIgnoreCase does not have expected value").isEqualTo(true);
290+
assertThat(order.getNullHandling()).isEqualTo(Sort.NullHandling.NATIVE);
291+
}
292+
}
293+
294+
@Test
295+
void testSortWithIgnoreCaseAndBody() {
296+
// given
297+
Sort.Order anySorting = Sort.Order.desc("amount").ignoreCase();
298+
Sort sort = Sort.by(anySorting);
299+
300+
// when
301+
final ResponseEntity<Page<Invoice>> response = this.invoiceClient.getInvoicesSortedWithBody(sort,
302+
"InvoiceTitleFromBody");
303+
304+
// then
305+
assertThat(response).isNotNull();
306+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
307+
assertThat(response.getBody()).isNotNull();
308+
assertThat(sort).isEqualTo(response.getBody().getSort());
309+
310+
Optional<Sort.Order> optionalOrder = response.getBody().getPageable().getSort().get().findFirst();
311+
assertThat(optionalOrder.isPresent()).isEqualTo(true);
312+
if (optionalOrder.isPresent()) {
313+
Sort.Order order = optionalOrder.get();
314+
assertThat(order.isIgnoreCase()).as("isIgnoreCase does not have expected value").isEqualTo(true);
315+
assertThat(order.getNullHandling()).isEqualTo(Sort.NullHandling.NATIVE);
316+
}
317+
318+
List<Invoice> invoiceList = response.getBody().getContent();
319+
assertThat(invoiceList).hasSizeGreaterThanOrEqualTo(1);
320+
321+
Invoice firstInvoice = invoiceList.get(0);
322+
assertThat(firstInvoice.getTitle()).startsWith("InvoiceTitleFromBody");
323+
324+
for (int ind = 0; ind < invoiceList.size() - 1; ind++) {
325+
assertThat(invoiceList.get(ind).getAmount()).isGreaterThanOrEqualTo(invoiceList.get(ind + 1).getAmount());
326+
}
327+
328+
}
329+
330+
@Test
331+
void testPageableMultipleSortPropertiesWithBodyAndIgnoreCase() {
332+
// given
333+
Sort.Order anySorting1 = Sort.Order.desc("anySorting1").ignoreCase();
334+
Sort.Order anySorting2 = Sort.Order.asc("anySorting2");
335+
Pageable pageable = PageRequest.of(0, 10, Sort.by(anySorting1, anySorting2));
336+
337+
// when
338+
final ResponseEntity<Page<Invoice>> response = this.invoiceClient.getInvoicesPagedWithBody(pageable,
339+
"InvoiceTitleFromBody");
340+
341+
// then
342+
assertThat(response).isNotNull();
343+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
344+
assertThat(response.getBody()).isNotNull();
345+
assertThat(pageable.getPageSize()).isEqualTo(response.getBody().getSize());
346+
347+
List<Invoice> invoiceList = response.getBody().getContent();
348+
assertThat(invoiceList).hasSizeGreaterThanOrEqualTo(1);
349+
350+
Invoice firstInvoice = invoiceList.get(0);
351+
assertThat(firstInvoice.getTitle()).startsWith("InvoiceTitleFromBody");
352+
353+
Sort sort = response.getBody().getPageable().getSort();
354+
assertThat(sort).hasSize(2);
355+
356+
List<Sort.Order> orderList = sort.toList();
357+
assertThat(orderList).hasSize(2);
358+
359+
Sort.Order firstOrder = orderList.get(0);
360+
assertThat(firstOrder.getDirection()).isEqualTo(Sort.Direction.DESC);
361+
assertThat(firstOrder.getProperty()).isEqualTo("anySorting1");
362+
assertThat(firstOrder.isIgnoreCase()).as("isIgnoreCase does not have expected value").isEqualTo(true);
363+
assertThat(firstOrder.getNullHandling()).isEqualTo(Sort.NullHandling.NATIVE);
364+
365+
Sort.Order secondOrder = orderList.get(1);
366+
assertThat(secondOrder.getDirection()).isEqualTo(Sort.Direction.ASC);
367+
assertThat(secondOrder.getProperty()).isEqualTo("anySorting2");
368+
assertThat(secondOrder.isIgnoreCase()).as("isIgnoreCase does not have expected value").isEqualTo(false);
369+
assertThat(secondOrder.getNullHandling()).isEqualTo(Sort.NullHandling.NATIVE);
370+
}
371+
267372
@EnableFeignClients(clients = InvoiceClient.class)
268373
@LoadBalancerClient(name = "local", configuration = LocalClientConfiguration.class)
269374
@SpringBootApplication(scanBasePackages = "org.springframework.cloud.openfeign.encoding.app",

spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2022 the original author or authors.
2+
* Copyright 2013-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -39,6 +39,7 @@
3939

4040
/**
4141
* @author Can Bezmen
42+
* @author Gokalp Kuscu
4243
*/
4344
@ExtendWith(MockitoExtension.class)
4445
class SortJacksonModuleTests {
@@ -53,7 +54,7 @@ public void setup() {
5354
}
5455

5556
@Test
56-
public void deserializePage() throws JsonProcessingException {
57+
public void testDeserializePage() throws JsonProcessingException {
5758
// Given
5859
String pageJson = "{\"content\":[\"A name\"],\"number\":1,\"size\":2,\"totalElements\":3,\"sort\":[{\"direction\":\"ASC\",\"property\":\"field\",\"ignoreCase\":false,\"nullHandling\":\"NATIVE\",\"descending\":false,\"ascending\":true}]}";
5960
// When
@@ -72,11 +73,85 @@ public void deserializePage() throws JsonProcessingException {
7273
Sort.Order order = optionalOrder.get();
7374
assertThat(order, hasProperty("property", is("field")));
7475
assertThat(order, hasProperty("direction", is(Sort.Direction.ASC)));
76+
assertThat(order, hasProperty("ignoreCase", is(false)));
77+
assertThat(order, hasProperty("nullHandling", is(Sort.NullHandling.NATIVE)));
7578
}
7679
}
7780

7881
@Test
79-
public void serializePage() throws IOException {
82+
public void testDeserializePageWithoutIgnoreCaseAndNullHandling() throws JsonProcessingException {
83+
// Given
84+
String pageJson = "{\"content\":[\"A name\"],\"number\":1,\"size\":2,\"totalElements\":3,\"sort\":[{\"direction\":\"ASC\",\"property\":\"field\",\"descending\":false,\"ascending\":true}]}";
85+
// When
86+
Page<?> result = objectMapper.readValue(pageJson, Page.class);
87+
// Then
88+
assertThat(result, notNullValue());
89+
assertThat(result, hasProperty("totalElements", is(3L)));
90+
assertThat(result.getContent(), hasSize(1));
91+
assertThat(result.getPageable(), notNullValue());
92+
assertThat(result.getPageable().getPageNumber(), is(1));
93+
assertThat(result.getPageable().getPageSize(), is(2));
94+
assertThat(result.getPageable().getSort(), notNullValue());
95+
result.getPageable().getSort();
96+
Optional<Sort.Order> optionalOrder = result.getPageable().getSort().get().findFirst();
97+
if (optionalOrder.isPresent()) {
98+
Sort.Order order = optionalOrder.get();
99+
assertThat(order, hasProperty("property", is("field")));
100+
assertThat(order, hasProperty("direction", is(Sort.Direction.ASC)));
101+
}
102+
}
103+
104+
@Test
105+
public void testDeserializePageWithoutNullHandling() throws JsonProcessingException {
106+
// Given
107+
String pageJson = "{\"content\":[\"A name\"],\"number\":1,\"size\":2,\"totalElements\":3,\"sort\":[{\"direction\":\"ASC\",\"property\":\"field\",\"ignoreCase\":true,\"descending\":false,\"ascending\":true}]}";
108+
// When
109+
Page<?> result = objectMapper.readValue(pageJson, Page.class);
110+
// Then
111+
assertThat(result, notNullValue());
112+
assertThat(result, hasProperty("totalElements", is(3L)));
113+
assertThat(result.getContent(), hasSize(1));
114+
assertThat(result.getPageable(), notNullValue());
115+
assertThat(result.getPageable().getPageNumber(), is(1));
116+
assertThat(result.getPageable().getPageSize(), is(2));
117+
assertThat(result.getPageable().getSort(), notNullValue());
118+
result.getPageable().getSort();
119+
Optional<Sort.Order> optionalOrder = result.getPageable().getSort().get().findFirst();
120+
if (optionalOrder.isPresent()) {
121+
Sort.Order order = optionalOrder.get();
122+
assertThat(order, hasProperty("property", is("field")));
123+
assertThat(order, hasProperty("direction", is(Sort.Direction.ASC)));
124+
assertThat(order, hasProperty("ignoreCase", is(false)));
125+
}
126+
}
127+
128+
@Test
129+
public void testDeserializePageWithTrueMarkedIgnoreCaseAndNullHandling() throws JsonProcessingException {
130+
// Given
131+
String pageJson = "{\"content\":[\"A name\"],\"number\":1,\"size\":2,\"totalElements\":3,\"sort\":[{\"direction\":\"ASC\",\"property\":\"field\",\"ignoreCase\":true,\"nullHandling\":\"NATIVE\",\"descending\":false,\"ascending\":true}]}";
132+
// When
133+
Page<?> result = objectMapper.readValue(pageJson, Page.class);
134+
// Then
135+
assertThat(result, notNullValue());
136+
assertThat(result, hasProperty("totalElements", is(3L)));
137+
assertThat(result.getContent(), hasSize(1));
138+
assertThat(result.getPageable(), notNullValue());
139+
assertThat(result.getPageable().getPageNumber(), is(1));
140+
assertThat(result.getPageable().getPageSize(), is(2));
141+
assertThat(result.getPageable().getSort(), notNullValue());
142+
result.getPageable().getSort();
143+
Optional<Sort.Order> optionalOrder = result.getPageable().getSort().get().findFirst();
144+
if (optionalOrder.isPresent()) {
145+
Sort.Order order = optionalOrder.get();
146+
assertThat(order, hasProperty("property", is("field")));
147+
assertThat(order, hasProperty("direction", is(Sort.Direction.ASC)));
148+
assertThat(order, hasProperty("ignoreCase", is(true)));
149+
assertThat(order, hasProperty("nullHandling", is(Sort.NullHandling.NATIVE)));
150+
}
151+
}
152+
153+
@Test
154+
public void testSerializePage() throws IOException {
80155
// Given
81156
Sort sort = Sort.by(Sort.Order.by("fieldName"));
82157
// When
@@ -86,4 +161,16 @@ public void serializePage() throws IOException {
86161
assertThat(result, containsString("\"property\":\"fieldName\""));
87162
}
88163

164+
@Test
165+
public void testSerializePageWithGivenIgnoreCase() throws IOException {
166+
// Given
167+
Sort sort = Sort.by(Sort.Order.by("fieldName"), Sort.Order.by("fieldName2").ignoreCase());
168+
// When
169+
String result = objectMapper.writeValueAsString(sort);
170+
// Then
171+
assertThat(result, containsString("\"direction\":\"ASC\""));
172+
assertThat(result, containsString("\"property\":\"fieldName\""));
173+
assertThat(result, containsString("\"property\":\"fieldName2\",\"ignoreCase\":true"));
174+
}
175+
89176
}

0 commit comments

Comments
 (0)