Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"));
}

}