From ea3fb866905cc70377f470f49e4df1a2afa0c48a Mon Sep 17 00:00:00 2001 From: Can Bezmen Date: Tue, 28 Apr 2020 12:40:04 +0300 Subject: [PATCH 1/8] Sort Support for feign pagination --- .../openfeign/support/PageJacksonModule.java | 15 +++- .../openfeign/support/SortJsonComponent.java | 88 +++++++++++++++++++ .../support/PageJacksonModuleTests.java | 18 +++- 3 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJsonComponent.java 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 c21aa177..255e3872 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 @@ -64,10 +64,17 @@ public class PageJacksonModule extends Module { private final Page delegate; SimplePageImpl(@JsonProperty("content") List content, - @JsonProperty("number") int number, @JsonProperty("size") int size, - @JsonProperty("totalElements") long totalElements) { - delegate = new PageImpl<>(content, PageRequest.of(number, size), - totalElements); + @JsonProperty("number") int number, @JsonProperty("size") int size, + @JsonProperty("totalElements") long totalElements, @JsonProperty("sort") Sort sort) { + PageRequest pageRequest; + if (sort != null) { + pageRequest = PageRequest.of(number, size, sort); + } + else { + pageRequest = PageRequest.of(number, size); + } + delegate = new PageImpl<>(content, pageRequest, totalElements); + } @JsonProperty 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 new file mode 100644 index 00000000..64bc6813 --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJsonComponent.java @@ -0,0 +1,88 @@ +/* + * Copyright 2013-2020 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.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.node.ArrayNode; + +import org.springframework.data.domain.Sort; + +/** + * This class provides support to serialize and deserialize spring {@link Sort} object. + * @author canbezmen + */ +public class SortJsonComponent { + + public static class SortSerializer extends JsonSerializer { + + @Override + public void serialize(Sort value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeStartArray(); + value.iterator().forEachRemaining(v -> { + try { + gen.writeObject(v); + } + catch (IOException e) { + e.printStackTrace(); + } + }); + gen.writeEndArray(); + } + + @Override + public Class handledType() { + return Sort.class; + } + + } + + public static class SortDeserializer extends JsonDeserializer { + + @Override + public Sort deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + 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 null; + } + + @Override + public Class handledType() { + return Sort.class; + } + + } +} 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 465617fd..6e954a23 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 @@ -16,12 +16,16 @@ package org.springframework.cloud.openfeign.support; +import java.util.Optional; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.data.domain.Page; +import org.springframework.data.domain.Sort; import static org.assertj.core.api.Assertions.assertThat; @@ -35,13 +39,16 @@ public class PageJacksonModuleTests { @BeforeAll public static void initialize() { objectMapper = new ObjectMapper(); - objectMapper.registerModule(new PageJacksonModule()); + SimpleModule module = new SimpleModule(); + module.addSerializer(Sort.class, new SortJsonComponent.SortSerializer()); + module.addDeserializer(Sort.class, new SortJsonComponent.SortDeserializer()); + objectMapper.registerModules(new PageJacksonModule(), module); } @Test public void deserializePage() throws JsonProcessingException { // Given - String pageJson = "{\"content\":[\"A name\"], \"number\":1, \"size\":2, \"totalElements\": 3}"; + 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 Page result = objectMapper.readValue(pageJson, Page.class); // Then @@ -51,6 +58,13 @@ public class PageJacksonModuleTests { assertThat(result.getPageable()).isNotNull(); assertThat(result.getPageable().getPageSize()).isEqualTo(2); assertThat(result.getPageable().getPageNumber()).isEqualTo(1); + assertThat(result.getPageable().getSort()).hasSize(1); + Optional optionalOrder = result.getPageable().getSort().get().findFirst(); + if (optionalOrder.isPresent()) { + Sort.Order order = optionalOrder.get(); + assertThat(order.getDirection()).isEqualTo(Sort.Direction.ASC); + assertThat(order.getProperty()).isEqualTo("field"); + } } } From 525bb603f78e9a70308f104dc0493caf3c61e9bd Mon Sep 17 00:00:00 2001 From: Can Bezmen Date: Tue, 28 Apr 2020 18:46:56 +0300 Subject: [PATCH 2/8] Fix sort problem for serialize and deserialize. Unit test added. --- .../openfeign/FeignClientsConfiguration.java | 23 +++++--- .../openfeign/support/SortJacksonModule.java | 35 +++++++++++++ .../encoding/FeignPageableEncodingTests.java | 10 ++++ .../support/PageJacksonModuleTests.java | 18 +------ .../support/SortJacksonModuleTests.java | 52 +++++++++++++++++++ 5 files changed, 114 insertions(+), 24 deletions(-) create mode 100644 spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJacksonModule.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java index af37d449..97fb278a 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java @@ -45,6 +45,7 @@ import org.springframework.cloud.openfeign.support.AbstractFormWriter; import org.springframework.cloud.openfeign.support.PageJacksonModule; import org.springframework.cloud.openfeign.support.PageableSpringEncoder; import org.springframework.cloud.openfeign.support.ResponseEntityDecoder; +import org.springframework.cloud.openfeign.support.SortJacksonModule; import org.springframework.cloud.openfeign.support.SpringDecoder; import org.springframework.cloud.openfeign.support.SpringEncoder; import org.springframework.cloud.openfeign.support.SpringMvcContract; @@ -84,7 +85,7 @@ public class FeignClientsConfiguration { @ConditionalOnMissingBean public Decoder feignDecoder() { return new OptionalDecoder( - new ResponseEntityDecoder(new SpringDecoder(this.messageConverters))); + new ResponseEntityDecoder(new SpringDecoder(this.messageConverters))); } @Bean @@ -98,17 +99,17 @@ public class FeignClientsConfiguration { @ConditionalOnClass(name = "org.springframework.data.domain.Pageable") @ConditionalOnMissingBean public Encoder feignEncoderPageable( - ObjectProvider formWriterProvider) { + ObjectProvider formWriterProvider) { PageableSpringEncoder encoder = new PageableSpringEncoder( - springEncoder(formWriterProvider)); + springEncoder(formWriterProvider)); if (springDataWebProperties != null) { encoder.setPageParameter( - springDataWebProperties.getPageable().getPageParameter()); + springDataWebProperties.getPageable().getPageParameter()); encoder.setSizeParameter( - springDataWebProperties.getPageable().getSizeParameter()); + springDataWebProperties.getPageable().getSizeParameter()); encoder.setSortParameter( - springDataWebProperties.getSort().getSortParameter()); + springDataWebProperties.getSort().getSortParameter()); } return encoder; } @@ -153,12 +154,18 @@ public class FeignClientsConfiguration { return new PageJacksonModule(); } + @Bean + @ConditionalOnClass(name = "org.springframework.data.domain.Page") + public Module sortModule() { + return new SortJacksonModule(); + } + private Encoder springEncoder(ObjectProvider formWriterProvider) { AbstractFormWriter formWriter = formWriterProvider.getIfAvailable(); if (formWriter != null) { return new SpringEncoder(new SpringPojoFormEncoder(formWriter), - this.messageConverters); + this.messageConverters); } else { return new SpringEncoder(new SpringFormEncoder(), this.messageConverters); @@ -185,7 +192,7 @@ public class FeignClientsConfiguration { super(); MultipartFormContentProcessor processor = (MultipartFormContentProcessor) getContentProcessor( - MULTIPART); + MULTIPART); processor.addFirstWriter(formWriter); } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJacksonModule.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJacksonModule.java new file mode 100644 index 00000000..e70857b5 --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJacksonModule.java @@ -0,0 +1,35 @@ +package org.springframework.cloud.openfeign.support; + +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.module.SimpleDeserializers; +import com.fasterxml.jackson.databind.module.SimpleSerializers; + +import org.springframework.data.domain.Sort; + +/** + * This jackson module provides support to add serialize and deserialize for spring {@link Sort} object. + * @author canbezmen + */ +public class SortJacksonModule extends Module { + @Override + public String getModuleName() { + return "SortModule"; + } + + @Override + public Version version() { + return new Version(0, 1, 0, "", null, null); + } + + @Override + public void setupModule(SetupContext context) { + SimpleSerializers serializers = new SimpleSerializers(); + serializers.addSerializer(Sort.class, new SortJsonComponent.SortSerializer()); + context.addSerializers(serializers); + + SimpleDeserializers deserializers = new SimpleDeserializers(); + deserializers.addDeserializer(Sort.class, new SortJsonComponent.SortDeserializer()); + context.addDeserializers(deserializers); + } +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java index 8224dfd8..524bb550 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java @@ -21,6 +21,8 @@ import java.util.Collections; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; +import java.util.Optional; + import org.junit.Test; import org.junit.runner.RunWith; @@ -81,6 +83,14 @@ public class FeignPageableEncodingTests { 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 optionalOrder = response.getBody().getPageable().getSort().get() + .findFirst(); + if (optionalOrder.isPresent()) { + Sort.Order order = optionalOrder.get(); + assertThat(order.getDirection()).isEqualTo(Sort.Direction.ASC); + assertThat(order.getProperty()).isEqualTo("sortProperty"); + } } 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 6e954a23..465617fd 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 @@ -16,16 +16,12 @@ package org.springframework.cloud.openfeign.support; -import java.util.Optional; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.data.domain.Page; -import org.springframework.data.domain.Sort; import static org.assertj.core.api.Assertions.assertThat; @@ -39,16 +35,13 @@ public class PageJacksonModuleTests { @BeforeAll public static void initialize() { objectMapper = new ObjectMapper(); - SimpleModule module = new SimpleModule(); - module.addSerializer(Sort.class, new SortJsonComponent.SortSerializer()); - module.addDeserializer(Sort.class, new SortJsonComponent.SortDeserializer()); - objectMapper.registerModules(new PageJacksonModule(), module); + objectMapper.registerModule(new PageJacksonModule()); } @Test public void deserializePage() 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}]}"; + String pageJson = "{\"content\":[\"A name\"], \"number\":1, \"size\":2, \"totalElements\": 3}"; // When Page result = objectMapper.readValue(pageJson, Page.class); // Then @@ -58,13 +51,6 @@ public class PageJacksonModuleTests { assertThat(result.getPageable()).isNotNull(); assertThat(result.getPageable().getPageSize()).isEqualTo(2); assertThat(result.getPageable().getPageNumber()).isEqualTo(1); - assertThat(result.getPageable().getSort()).hasSize(1); - Optional optionalOrder = result.getPageable().getSort().get().findFirst(); - if (optionalOrder.isPresent()) { - Sort.Order order = optionalOrder.get(); - assertThat(order.getDirection()).isEqualTo(Sort.Direction.ASC); - assertThat(order.getProperty()).isEqualTo("field"); - } } } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java new file mode 100644 index 00000000..27f0c92b --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java @@ -0,0 +1,52 @@ +package org.springframework.cloud.openfeign.support; + +import java.util.Optional; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Sort; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author canbezmen + */ +class SortJacksonModuleTests { + + private static ObjectMapper objectMapper; + + @BeforeAll + public static void initialize() { + objectMapper = new ObjectMapper(); + objectMapper.registerModules(new PageJacksonModule()); + objectMapper.registerModule(new SortJacksonModule()); + } + + @Test + public void deserializePage() 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 + Page result = objectMapper.readValue(pageJson, Page.class); + // Then + assertThat(result).isNotNull(); + assertThat(result.getTotalElements()).isEqualTo(3); + assertThat(result.getContent()).hasSize(1); + assertThat(result.getPageable()).isNotNull(); + assertThat(result.getPageable().getPageSize()).isEqualTo(2); + assertThat(result.getPageable().getPageNumber()).isEqualTo(1); + assertThat(result.getPageable().getSort()).hasSize(1); + Optional optionalOrder = result.getPageable().getSort().get() + .findFirst(); + if (optionalOrder.isPresent()) { + Sort.Order order = optionalOrder.get(); + assertThat(order.getDirection()).isEqualTo(Sort.Direction.ASC); + assertThat(order.getProperty()).isEqualTo("field"); + } + } + +} From b538d7f88cbd147fa5143d9e24059733207e4a73 Mon Sep 17 00:00:00 2001 From: Can Bezmen Date: Tue, 28 Apr 2020 20:44:27 +0300 Subject: [PATCH 3/8] Checkstyle bug for license fixed. --- .../openfeign/FeignClientsConfiguration.java | 16 ++++++------ .../openfeign/support/PageJacksonModule.java | 5 ++-- .../openfeign/support/SortJacksonModule.java | 25 +++++++++++++++++-- .../openfeign/support/SortJsonComponent.java | 13 +++++++--- .../encoding/FeignPageableEncodingTests.java | 7 +++--- .../support/SortJacksonModuleTests.java | 18 ++++++++++++- 6 files changed, 63 insertions(+), 21 deletions(-) diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java index 97fb278a..8ced1274 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java @@ -85,7 +85,7 @@ public class FeignClientsConfiguration { @ConditionalOnMissingBean public Decoder feignDecoder() { return new OptionalDecoder( - new ResponseEntityDecoder(new SpringDecoder(this.messageConverters))); + new ResponseEntityDecoder(new SpringDecoder(this.messageConverters))); } @Bean @@ -99,17 +99,17 @@ public class FeignClientsConfiguration { @ConditionalOnClass(name = "org.springframework.data.domain.Pageable") @ConditionalOnMissingBean public Encoder feignEncoderPageable( - ObjectProvider formWriterProvider) { + ObjectProvider formWriterProvider) { PageableSpringEncoder encoder = new PageableSpringEncoder( - springEncoder(formWriterProvider)); + springEncoder(formWriterProvider)); if (springDataWebProperties != null) { encoder.setPageParameter( - springDataWebProperties.getPageable().getPageParameter()); + springDataWebProperties.getPageable().getPageParameter()); encoder.setSizeParameter( - springDataWebProperties.getPageable().getSizeParameter()); + springDataWebProperties.getPageable().getSizeParameter()); encoder.setSortParameter( - springDataWebProperties.getSort().getSortParameter()); + springDataWebProperties.getSort().getSortParameter()); } return encoder; } @@ -165,7 +165,7 @@ public class FeignClientsConfiguration { if (formWriter != null) { return new SpringEncoder(new SpringPojoFormEncoder(formWriter), - this.messageConverters); + this.messageConverters); } else { return new SpringEncoder(new SpringFormEncoder(), this.messageConverters); @@ -192,7 +192,7 @@ public class FeignClientsConfiguration { super(); MultipartFormContentProcessor processor = (MultipartFormContentProcessor) getContentProcessor( - MULTIPART); + MULTIPART); processor.addFirstWriter(formWriter); } 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 255e3872..ac6c0c8c 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 @@ -64,8 +64,9 @@ public class PageJacksonModule extends Module { private final Page delegate; SimplePageImpl(@JsonProperty("content") List content, - @JsonProperty("number") int number, @JsonProperty("size") int size, - @JsonProperty("totalElements") long totalElements, @JsonProperty("sort") Sort sort) { + @JsonProperty("number") int number, @JsonProperty("size") int size, + @JsonProperty("totalElements") long totalElements, + @JsonProperty("sort") Sort sort) { PageRequest pageRequest; if (sort != null) { pageRequest = PageRequest.of(number, size, sort); diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJacksonModule.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJacksonModule.java index e70857b5..3b2ce147 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJacksonModule.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJacksonModule.java @@ -1,3 +1,19 @@ +/* + * Copyright 2013-2020 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 com.fasterxml.jackson.core.Version; @@ -8,10 +24,13 @@ import com.fasterxml.jackson.databind.module.SimpleSerializers; import org.springframework.data.domain.Sort; /** - * This jackson module provides support to add serialize and deserialize for spring {@link Sort} object. + * This jackson module provides support to add serialize and deserialize for spring + * {@link Sort} object. + * * @author canbezmen */ public class SortJacksonModule extends Module { + @Override public String getModuleName() { return "SortModule"; @@ -29,7 +48,9 @@ public class SortJacksonModule extends Module { context.addSerializers(serializers); SimpleDeserializers deserializers = new SimpleDeserializers(); - deserializers.addDeserializer(Sort.class, new SortJsonComponent.SortDeserializer()); + deserializers.addDeserializer(Sort.class, + new SortJsonComponent.SortDeserializer()); context.addDeserializers(deserializers); } + } 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 64bc6813..a45b178d 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 @@ -34,6 +34,7 @@ import org.springframework.data.domain.Sort; /** * This class provides support to serialize and deserialize spring {@link Sort} object. + * * @author canbezmen */ public class SortJsonComponent { @@ -41,7 +42,8 @@ public class SortJsonComponent { public static class SortSerializer extends JsonSerializer { @Override - public void serialize(Sort value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + public void serialize(Sort value, JsonGenerator gen, + SerializerProvider serializers) throws IOException { gen.writeStartArray(); value.iterator().forEachRemaining(v -> { try { @@ -64,14 +66,16 @@ public class SortJsonComponent { public static class SortDeserializer extends JsonDeserializer { @Override - public Sort deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + public Sort deserialize(JsonParser jsonParser, + DeserializationContext deserializationContext) throws IOException { 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()); + Sort.Order order = new Sort.Order( + Sort.Direction.valueOf(jsonNode.get("direction").textValue()), + jsonNode.get("property").textValue()); orders.add(order); } return Sort.by(orders); @@ -85,4 +89,5 @@ public class SortJsonComponent { } } + } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java index 524bb550..c909eede 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java @@ -17,12 +17,11 @@ package org.springframework.cloud.openfeign.encoding; import java.util.Collections; +import java.util.Optional; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; -import java.util.Optional; - import org.junit.Test; import org.junit.runner.RunWith; @@ -84,8 +83,8 @@ public class FeignPageableEncodingTests { assertThat(response.getBody()).isNotNull(); assertThat(pageable.getPageSize()).isEqualTo(response.getBody().getSize()); assertThat(response.getBody().getPageable().getSort()).hasSize(1); - Optional optionalOrder = response.getBody().getPageable().getSort().get() - .findFirst(); + Optional optionalOrder = response.getBody().getPageable().getSort() + .get().findFirst(); if (optionalOrder.isPresent()) { Sort.Order order = optionalOrder.get(); assertThat(order.getDirection()).isEqualTo(Sort.Direction.ASC); diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java index 27f0c92b..12d09890 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java @@ -1,3 +1,19 @@ +/* + * Copyright 2013-2020 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.Optional; @@ -41,7 +57,7 @@ class SortJacksonModuleTests { assertThat(result.getPageable().getPageNumber()).isEqualTo(1); assertThat(result.getPageable().getSort()).hasSize(1); Optional optionalOrder = result.getPageable().getSort().get() - .findFirst(); + .findFirst(); if (optionalOrder.isPresent()) { Sort.Order order = optionalOrder.get(); assertThat(order.getDirection()).isEqualTo(Sort.Direction.ASC); From 0e1a579de5022975fdf775f9cd2992a4ccb43833 Mon Sep 17 00:00:00 2001 From: Can Bezmen Date: Wed, 29 Apr 2020 16:18:22 +0300 Subject: [PATCH 4/8] Error log added for serialization and throw exception before serializing other sort objects --- .../openfeign/support/SortJsonComponent.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) 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 a45b178d..d720072e 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 @@ -29,7 +29,11 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.node.ArrayNode; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.convert.ConversionFailedException; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.data.domain.Sort; /** @@ -39,18 +43,27 @@ import org.springframework.data.domain.Sort; */ public class SortJsonComponent { + private static final Log LOG = LogFactory.getLog(SortJsonComponent.class); + public static class SortSerializer extends JsonSerializer { @Override public void serialize(Sort value, JsonGenerator gen, - SerializerProvider serializers) throws IOException { + SerializerProvider serializers) throws IOException { gen.writeStartArray(); value.iterator().forEachRemaining(v -> { try { gen.writeObject(v); } catch (IOException e) { - e.printStackTrace(); + String message = "Couldn't serialize Sort object for " + v; + if (LOG.isErrorEnabled()) { + LOG.error(message, e); + } + else { + throw new ConversionFailedException(TypeDescriptor.valueOf(Sort.class), + TypeDescriptor.valueOf(String.class), v, e); + } } }); gen.writeEndArray(); @@ -67,15 +80,15 @@ public class SortJsonComponent { @Override public Sort deserialize(JsonParser jsonParser, - DeserializationContext deserializationContext) throws IOException { + DeserializationContext deserializationContext) throws IOException { 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()); + Sort.Direction.valueOf(jsonNode.get("direction").textValue()), + jsonNode.get("property").textValue()); orders.add(order); } return Sort.by(orders); From bb499ea8236b506b6659309c99873eb4a13b3cbc Mon Sep 17 00:00:00 2001 From: Can Bezmen Date: Wed, 29 Apr 2020 18:04:20 +0300 Subject: [PATCH 5/8] Revert some code for code coverage and change test to mockito --- .../openfeign/support/SortJsonComponent.java | 21 +++----- .../support/SortJacksonModuleTests.java | 52 +++++++++++++------ 2 files changed, 43 insertions(+), 30 deletions(-) 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 d720072e..a09d3442 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 @@ -29,8 +29,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.node.ArrayNode; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.TypeDescriptor; @@ -43,27 +41,20 @@ import org.springframework.data.domain.Sort; */ public class SortJsonComponent { - private static final Log LOG = LogFactory.getLog(SortJsonComponent.class); - public static class SortSerializer extends JsonSerializer { @Override public void serialize(Sort value, JsonGenerator gen, - SerializerProvider serializers) throws IOException { + SerializerProvider serializers) throws IOException { gen.writeStartArray(); value.iterator().forEachRemaining(v -> { try { gen.writeObject(v); } catch (IOException e) { - String message = "Couldn't serialize Sort object for " + v; - if (LOG.isErrorEnabled()) { - LOG.error(message, e); - } - else { - throw new ConversionFailedException(TypeDescriptor.valueOf(Sort.class), + throw new ConversionFailedException( + TypeDescriptor.valueOf(Sort.class), TypeDescriptor.valueOf(String.class), v, e); - } } }); gen.writeEndArray(); @@ -80,15 +71,15 @@ public class SortJsonComponent { @Override public Sort deserialize(JsonParser jsonParser, - DeserializationContext deserializationContext) throws IOException { + DeserializationContext deserializationContext) throws IOException { 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()); + 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/SortJacksonModuleTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java index 12d09890..e062ecb8 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java @@ -16,28 +16,38 @@ package org.springframework.cloud.openfeign.support; +import java.io.IOException; import java.util.Optional; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.data.domain.Page; import org.springframework.data.domain.Sort; -import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; /** * @author canbezmen */ +@ExtendWith(MockitoExtension.class) class SortJacksonModuleTests { - private static ObjectMapper objectMapper; + @Spy + private ObjectMapper objectMapper; - @BeforeAll - public static void initialize() { - objectMapper = new ObjectMapper(); + @BeforeEach + public void setup() { objectMapper.registerModules(new PageJacksonModule()); objectMapper.registerModule(new SortJacksonModule()); } @@ -49,20 +59,32 @@ class SortJacksonModuleTests { // When Page result = objectMapper.readValue(pageJson, Page.class); // Then - assertThat(result).isNotNull(); - assertThat(result.getTotalElements()).isEqualTo(3); - assertThat(result.getContent()).hasSize(1); - assertThat(result.getPageable()).isNotNull(); - assertThat(result.getPageable().getPageSize()).isEqualTo(2); - assertThat(result.getPageable().getPageNumber()).isEqualTo(1); - assertThat(result.getPageable().getSort()).hasSize(1); + 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 optionalOrder = result.getPageable().getSort().get() .findFirst(); if (optionalOrder.isPresent()) { Sort.Order order = optionalOrder.get(); - assertThat(order.getDirection()).isEqualTo(Sort.Direction.ASC); - assertThat(order.getProperty()).isEqualTo("field"); + assertThat(order, hasProperty("property", is("field"))); + assertThat(order, hasProperty("direction", is(Sort.Direction.ASC))); } } + @Test + public void serializePage() throws IOException { + // Given + Sort sort = Sort.by(Sort.Order.by("fieldName")); + // When + String result = objectMapper.writeValueAsString(sort); + // Then + assertThat(result, containsString("\"direction\":\"ASC\"")); + assertThat(result, containsString("\"property\":\"fieldName\"")); + } + } From 88633c7f54c34825e26b73b76e42c910dd9853a6 Mon Sep 17 00:00:00 2001 From: Can Bezmen Date: Wed, 29 Apr 2020 18:18:41 +0300 Subject: [PATCH 6/8] Change type of exception --- .../cloud/openfeign/support/SortJsonComponent.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 a09d3442..60861127 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 @@ -29,9 +29,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.node.ArrayNode; +import feign.codec.EncodeException; -import org.springframework.core.convert.ConversionFailedException; -import org.springframework.core.convert.TypeDescriptor; import org.springframework.data.domain.Sort; /** @@ -52,9 +51,7 @@ public class SortJsonComponent { gen.writeObject(v); } catch (IOException e) { - throw new ConversionFailedException( - TypeDescriptor.valueOf(Sort.class), - TypeDescriptor.valueOf(String.class), v, e); + throw new EncodeException("Couldn't serialize object " + v); } }); gen.writeEndArray(); From 090c85c7ed3ef8a7b91969cd2eb20229f373fae2 Mon Sep 17 00:00:00 2001 From: Can Bezmen Date: Thu, 7 May 2020 19:26:25 +0300 Subject: [PATCH 7/8] Documentation added. --- docs/src/main/asciidoc/spring-cloud-openfeign.adoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc index befcb323..8e0f4ec3 100644 --- a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc +++ b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc @@ -47,6 +47,9 @@ public interface StoreClient { @RequestMapping(method = RequestMethod.GET, value = "/stores") List getStores(); + @RequestMapping(method = RequestMethod.GET, value = "/stores") + Page getStores(Pageable pageable); + @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json") Store update(@PathVariable("storeId") Long storeId, Store store); } From 06a55b8bcd93734ddd9738675f54bf39d831e851 Mon Sep 17 00:00:00 2001 From: Can Bezmen Date: Fri, 8 May 2020 21:11:27 +0300 Subject: [PATCH 8/8] Review changes. --- .../cloud/openfeign/support/PageJacksonModule.java | 2 +- .../cloud/openfeign/support/SortJacksonModule.java | 4 ++-- .../cloud/openfeign/support/SortJsonComponent.java | 5 +++-- .../cloud/openfeign/support/SortJacksonModuleTests.java | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) 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 ac6c0c8c..72662101 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 @@ -33,7 +33,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; /** - * This jackson module provides support to deserialize spring {@link Page} objects. + * This Jackson module provides support to deserialize Spring {@link Page} objects. * * @author Pascal Büttiker */ diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJacksonModule.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJacksonModule.java index 3b2ce147..a9d57b1c 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJacksonModule.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJacksonModule.java @@ -24,10 +24,10 @@ import com.fasterxml.jackson.databind.module.SimpleSerializers; import org.springframework.data.domain.Sort; /** - * This jackson module provides support to add serialize and deserialize for spring + * This Jackson module provides support for serializing and deserializing for Spring * {@link Sort} object. * - * @author canbezmen + * @author can bezmen */ public class SortJacksonModule extends Module { 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 60861127..95a7f283 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 @@ -34,9 +34,10 @@ import feign.codec.EncodeException; import org.springframework.data.domain.Sort; /** - * This class provides support to serialize and deserialize spring {@link Sort} object. + * This class provides provides support for serializing and deserializing for Spring + * {@link Sort} object. * - * @author canbezmen + * @author can bezmen */ public class SortJsonComponent { diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java index e062ecb8..54e6c833 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SortJacksonModuleTests.java @@ -38,7 +38,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; /** - * @author canbezmen + * @author can bezmen */ @ExtendWith(MockitoExtension.class) class SortJacksonModuleTests {