From a8867cfafec0ca88ffb0a21320ef2a6a4b89e241 Mon Sep 17 00:00:00 2001 From: Olga MaciaszekSharma Date: Wed, 14 Feb 2024 15:31:38 +0100 Subject: [PATCH] Fix deserialising from Pageable. --- .../openfeign/support/PageJacksonModule.java | 95 ++++++++++++++++--- .../openfeign/support/SortJsonComponent.java | 29 ++++-- .../support/PageJacksonModuleTests.java | 35 +++++-- .../src/test/resources/withPageable.json | 90 ++++++++++++++++++ .../src/test/resources/withoutPageable.json | 84 ++++++++++++++++ 5 files changed, 304 insertions(+), 29 deletions(-) create mode 100644 spring-cloud-openfeign-core/src/test/resources/withPageable.json create mode 100644 spring-cloud-openfeign-core/src/test/resources/withoutPageable.json 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 index 23068bf6..a7c85a39 100644 --- 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 @@ -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. @@ -57,6 +57,18 @@ public class PageJacksonModule extends Module { @Override public void setupModule(SetupContext context) { context.setMixInAnnotations(Page.class, PageMixIn.class); + context.setMixInAnnotations(Pageable.class, PageableMixIn.class); + } + + private static PageRequest buildPageRequest(int number, int size, Sort sort) { + PageRequest pageRequest; + if (sort != null) { + pageRequest = PageRequest.of(number, size, sort); + } + else { + pageRequest = PageRequest.of(number, size); + } + return pageRequest; } @JsonDeserialize(as = SimplePageImpl.class) @@ -65,24 +77,29 @@ public class PageJacksonModule extends Module { } + @JsonDeserialize(as = SimplePageable.class) + @JsonIgnoreProperties(ignoreUnknown = true) + private interface PageableMixIn { + + } + static class SimplePageImpl implements Page { private final Page delegate; - SimplePageImpl(@JsonProperty("content") List content, @JsonProperty("number") int number, - @JsonProperty("size") int size, @JsonProperty("totalElements") @JsonAlias({ "total-elements", - "total_elements", "totalelements", "TotalElements" }) long totalElements, + SimplePageImpl(@JsonProperty("content") List content, @JsonProperty("pageable") Pageable pageable, + @JsonProperty("number") @JsonAlias("pageNumber") int number, + @JsonProperty("size") @JsonAlias("pageSize") int size, + @JsonProperty("totalElements") @JsonAlias({ "total-elements", "total_elements", "totalelements", + "TotalElements", "total" }) long totalElements, @JsonProperty("sort") Sort sort) { if (size > 0) { - PageRequest pageRequest; - if (sort != null) { - pageRequest = PageRequest.of(number, size, sort); - } - else { - pageRequest = PageRequest.of(number, size); - } + PageRequest pageRequest = buildPageRequest(number, size, sort); delegate = new PageImpl<>(content, pageRequest, totalElements); } + else if (pageable != null && pageable.getPageSize() > 0) { + delegate = new PageImpl<>(content, pageable, totalElements); + } else { delegate = new PageImpl<>(content); } @@ -213,4 +230,60 @@ public class PageJacksonModule extends Module { } + static class SimplePageable implements Pageable { + + private final PageRequest delegate; + + SimplePageable(@JsonProperty("pageNumber") int number, @JsonProperty("pageSize") int size, + @JsonProperty("sort") Sort sort) { + delegate = buildPageRequest(number, size, sort); + } + + @Override + public int getPageNumber() { + return delegate.getPageNumber(); + } + + @Override + public int getPageSize() { + return delegate.getPageSize(); + } + + @Override + public long getOffset() { + return delegate.getOffset(); + } + + @Override + public Sort getSort() { + return delegate.getSort(); + } + + @Override + public Pageable next() { + return delegate.next(); + } + + @Override + public Pageable previousOrFirst() { + return delegate.previousOrFirst(); + } + + @Override + public Pageable first() { + return delegate.first(); + } + + @Override + public Pageable withPage(int pageNumber) { + return delegate.withPage(pageNumber); + } + + @Override + public boolean hasPrevious() { + return delegate.hasPrevious(); + } + + } + } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJsonComponent.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJsonComponent.java index 44aa30d6..cf22a0db 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJsonComponent.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJsonComponent.java @@ -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. @@ -34,10 +34,11 @@ import feign.codec.EncodeException; import org.springframework.data.domain.Sort; /** - * This class provides provides support for serializing and deserializing for Spring - * {@link Sort} object. + * This class provides support for serializing and deserializing for Spring {@link Sort} + * object. * * @author Can Bezmen + * @author Olga Maciaszek-Sharma */ public class SortJsonComponent { @@ -72,13 +73,11 @@ public class SortJsonComponent { TreeNode treeNode = jsonParser.getCodec().readTree(jsonParser); if (treeNode.isArray()) { ArrayNode arrayNode = (ArrayNode) treeNode; - List orders = new ArrayList<>(); - for (JsonNode jsonNode : arrayNode) { - Sort.Order order = new Sort.Order(Sort.Direction.valueOf(jsonNode.get("direction").textValue()), - jsonNode.get("property").textValue()); - orders.add(order); - } - return Sort.by(orders); + return toSort(arrayNode); + } + else if (treeNode.get("orders") != null && treeNode.get("orders").isArray()) { + ArrayNode arrayNode = (ArrayNode) treeNode.get("orders"); + return toSort(arrayNode); } return null; } @@ -88,6 +87,16 @@ public class SortJsonComponent { return Sort.class; } + private static Sort toSort(ArrayNode arrayNode) { + List orders = new ArrayList<>(); + for (JsonNode jsonNode : arrayNode) { + Sort.Order order = new Sort.Order(Sort.Direction.valueOf(jsonNode.get("direction").textValue()), + jsonNode.get("property").textValue()); + orders.add(order); + } + return Sort.by(orders); + } + } } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageJacksonModuleTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageJacksonModuleTests.java index dc8bc769..9795dc36 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageJacksonModuleTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageJacksonModuleTests.java @@ -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. @@ -16,6 +16,8 @@ package org.springframework.cloud.openfeign.support; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import com.fasterxml.jackson.core.JsonProcessingException; @@ -29,6 +31,7 @@ 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; import static org.assertj.core.api.Assertions.assertThat; @@ -40,12 +43,12 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Pedro Mendes * @author Nikita Konev */ -public class PageJacksonModuleTests { +class PageJacksonModuleTests { private static ObjectMapper objectMapper; @BeforeAll - public static void initialize() { + static void initialize() { objectMapper = new ObjectMapper(); objectMapper.registerModule(new PageJacksonModule()); objectMapper.registerModule(new SortJacksonModule()); @@ -53,7 +56,7 @@ public class PageJacksonModuleTests { @ParameterizedTest @ValueSource(strings = { "totalElements", "total-elements", "total_elements", "totalelements", "TotalElements" }) - public void deserializePage(String totalElements) throws JsonProcessingException { + void deserializePage(String totalElements) throws JsonProcessingException { // Given String pageJson = "{\"content\":[\"A name\"], \"number\":1, \"size\":2, \"" + totalElements + "\": 3}"; // When @@ -67,8 +70,24 @@ public class PageJacksonModuleTests { assertThat(result.getPageable().getPageNumber()).isEqualTo(1); } + @SuppressWarnings("DataFlowIssue") + @ParameterizedTest + @ValueSource(strings = {"./src/test/resources/withPageable.json", "./src/test/resources/withoutPageable.json"}) + void deserializePageFromFileWithPageable(String filePath) throws IOException { + File file = new File(filePath); + + Page result = objectMapper.readValue(file, Page.class); + + assertThat(result.getTotalElements()).isEqualTo(11); + assertThat(result.getContent()).hasSize(10); + assertThat(result.getPageable().getPageNumber()).isEqualTo(0); + assertThat(result.getPageable().getSort().getOrderFor("lastName") + .getDirection()).isEqualTo(Sort.Direction.DESC); + } + + @Test - public void serializeAndDeserializeEmpty() throws JsonProcessingException { + void serializeAndDeserializeEmpty() throws JsonProcessingException { // Given PageImpl objects = new PageImpl<>(new ArrayList<>(), Pageable.ofSize(1), 0); String pageJson = objectMapper.writeValueAsString(objects); @@ -81,7 +100,7 @@ public class PageJacksonModuleTests { } @Test - public void serializeAndDeserializeFilledMultiple() throws JsonProcessingException { + void serializeAndDeserializeFilledMultiple() throws JsonProcessingException { // Given ArrayList pageElements = new ArrayList<>(); pageElements.add("first element"); @@ -104,7 +123,7 @@ public class PageJacksonModuleTests { } @Test - public void serializeAndDeserializeEmptyCascade() throws JsonProcessingException { + void serializeAndDeserializeEmptyCascade() throws JsonProcessingException { // Given PageImpl objects = new PageImpl<>(new ArrayList<>(), Pageable.ofSize(1), 0); String pageJson = objectMapper.writeValueAsString(objects); @@ -123,7 +142,7 @@ public class PageJacksonModuleTests { } @Test - public void serializeAndDeserializeFilledMultipleCascade() throws JsonProcessingException { + void serializeAndDeserializeFilledMultipleCascade() throws JsonProcessingException { // Given ArrayList pageElements = new ArrayList<>(); pageElements.add("first element in cascaded serialization"); diff --git a/spring-cloud-openfeign-core/src/test/resources/withPageable.json b/spring-cloud-openfeign-core/src/test/resources/withPageable.json new file mode 100644 index 00000000..e124b11c --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/resources/withPageable.json @@ -0,0 +1,90 @@ +{ + "content": [ + { + "id": 3, + "lastName": "Williams", + "firstName": "Thomas", + "email": "w.t@my.domain.com" + }, + { + "id": 1, + "lastName": "Smith", + "firstName": "James", + "email": "s.j@my.domain.com" + }, + { + "id": 11, + "lastName": "Scott", + "firstName": "Steven", + "email": "s.s@my.domain.com" + }, + { + "id": 8, + "lastName": "Rodriguez", + "firstName": "Daniel", + "email": "r.d@my.domain.com" + }, + { + "id": 9, + "lastName": "Martinez", + "firstName": "Robert", + "email": "m.r@my.domain.com" + }, + { + "id": 5, + "lastName": "Jones", + "firstName": "James", + "email": "j.j@my.domain.com" + }, + { + "id": 2, + "lastName": "Johnson", + "firstName": "Robert", + "email": "j.r@my.domain.com" + }, + { + "id": 6, + "lastName": "Garcia", + "firstName": "William", + "email": "g.w@my.domain.com" + }, + { + "id": 7, + "lastName": "Davis", + "firstName": "Richard", + "email": "d.r@my.domain.com" + }, + { + "id": 4, + "lastName": "Brown", + "firstName": "Paul", + "email": "b.p@my.domain.com" + } + ], + + "pageable": { + "pageNumber": 0, + "pageSize": 10, + "sort": { + "orders": [ + { + "direction": "DESC", + "property": "lastName", + "ignoreCase": false, + "nullHandling": "NATIVE", + "ascending": false, + "descending": true + } + ], + "sorted": true, + "empty": false, + "unsorted": false + }, + "paged": true, + "unpaged": false + }, + "total": 11, + "last": false, + "first": true, + "empty": false +} diff --git a/spring-cloud-openfeign-core/src/test/resources/withoutPageable.json b/spring-cloud-openfeign-core/src/test/resources/withoutPageable.json new file mode 100644 index 00000000..e66f3a87 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/resources/withoutPageable.json @@ -0,0 +1,84 @@ +{ + "content": [ + { + "id": 3, + "lastName": "Williams", + "firstName": "Thomas", + "email": "w.t@my.domain.com" + }, + { + "id": 1, + "lastName": "Smith", + "firstName": "James", + "email": "s.j@my.domain.com" + }, + { + "id": 11, + "lastName": "Scott", + "firstName": "Steven", + "email": "s.s@my.domain.com" + }, + { + "id": 8, + "lastName": "Rodriguez", + "firstName": "Daniel", + "email": "r.d@my.domain.com" + }, + { + "id": 9, + "lastName": "Martinez", + "firstName": "Robert", + "email": "m.r@my.domain.com" + }, + { + "id": 5, + "lastName": "Jones", + "firstName": "James", + "email": "j.j@my.domain.com" + }, + { + "id": 2, + "lastName": "Johnson", + "firstName": "Robert", + "email": "j.r@my.domain.com" + }, + { + "id": 6, + "lastName": "Garcia", + "firstName": "William", + "email": "g.w@my.domain.com" + }, + { + "id": 7, + "lastName": "Davis", + "firstName": "Richard", + "email": "d.r@my.domain.com" + }, + { + "id": 4, + "lastName": "Brown", + "firstName": "Paul", + "email": "b.p@my.domain.com" + } + ], + "sort": { + "orders": [ + { + "direction": "DESC", + "property": "lastName", + "ignoreCase": false, + "nullHandling": "NATIVE", + "ascending": false, + "descending": true + } + ], + "sorted": true, + "empty": false, + "unsorted": false + }, + "size": 2, + "total": 11, + "last": false, + "first": true, + "empty": false +}