From 38872b990a12ae67295697c25936981a781825a4 Mon Sep 17 00:00:00 2001 From: Muhammad Rifqi Fatchurrahman Date: Thu, 7 Oct 2021 10:39:10 +0700 Subject: [PATCH 1/4] update compiler source to 1.8 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d038562..6d17899 100644 --- a/pom.xml +++ b/pom.xml @@ -36,8 +36,8 @@ limitations under the License. UTF-8 UTF-8 - 1.6 - 1.6 + 1.8 + 1.8 1.8 1.8 From acdb137217b2497289f581edabba80e562e8612d Mon Sep 17 00:00:00 2001 From: Muhammad Rifqi Fatchurrahman Date: Thu, 7 Oct 2021 11:02:49 +0700 Subject: [PATCH 2/4] add test case --- .../java/feign/form/feign/spring/Client.java | 19 +++++++---- .../java/feign/form/feign/spring/Dto.java | 3 +- .../java/feign/form/feign/spring/Server.java | 18 +++++++++-- .../feign/spring/SpringFormEncoderTest.java | 23 +++++++++++-- .../java/feign/form/feign/spring/SubDto.java | 32 +++++++++++++++++++ 5 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 feign-form-spring/src/test/java/feign/form/feign/spring/SubDto.java diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java index d116fac..e24222a 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java @@ -20,20 +20,21 @@ import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.POST; -import java.util.List; -import java.util.Map; - import feign.Logger; import feign.Response; import feign.codec.Encoder; import feign.form.spring.SpringFormEncoder; +import java.util.List; +import java.util.Map; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.support.SpringEncoder; import org.springframework.context.annotation.Bean; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -99,14 +100,20 @@ String upload4 (@PathVariable("id") String id, method = POST, consumes = MULTIPART_FORM_DATA_VALUE ) - String upload6Array (@RequestPart MultipartFile[] files); + String upload6Array(@RequestPart MultipartFile[] files); @RequestMapping( path = "/multipart/upload6", method = POST, consumes = MULTIPART_FORM_DATA_VALUE ) - String upload6Collection (@RequestPart List files); + String upload6Collection(@RequestPart List files); + + @PostMapping( + path = "/multipart/upload7", + consumes = MULTIPART_FORM_DATA_VALUE + ) + Response upload7(@ModelAttribute SubDto dto); class ClientConfiguration { @@ -114,7 +121,7 @@ class ClientConfiguration { private ObjectFactory messageConverters; @Bean - public Encoder feignEncoder () { + public Encoder feignEncoder() { return new SpringFormEncoder(new SpringEncoder(messageConverters)); } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java index 2e812db..c9d3067 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java @@ -19,16 +19,17 @@ import static lombok.AccessLevel.PRIVATE; import java.io.Serializable; - import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; import lombok.experimental.FieldDefaults; import org.springframework.web.multipart.MultipartFile; @Data @NoArgsConstructor @AllArgsConstructor +@Accessors(chain = true) @FieldDefaults(level = PRIVATE) public class Dto implements Serializable { diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java index 953a0b1..7bec4f4 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java @@ -26,7 +26,6 @@ import java.io.IOException; import java.util.Map; - import lombok.SneakyThrows; import lombok.val; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -39,7 +38,9 @@ import org.springframework.http.ResponseEntity; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -122,7 +123,7 @@ void upload5 (Dto dto) throws IOException { consumes = MULTIPART_FORM_DATA_VALUE ) public ResponseEntity upload6 (@RequestParam("popa1") MultipartFile popa1, - @RequestParam("popa2") MultipartFile popa2 + @RequestParam("popa2") MultipartFile popa2 ) throws Exception { HttpStatus status = I_AM_A_TEAPOT; String result = ""; @@ -133,12 +134,23 @@ public ResponseEntity upload6 (@RequestParam("popa1") MultipartFile popa return ResponseEntity.status(status).body(result); } + @PostMapping( + path = "/multipart/upload7", + consumes = MULTIPART_FORM_DATA_VALUE + ) + public ResponseEntity upload7(@ModelAttribute SubDto subDto) { + if (subDto.getFile() == null || subDto.getSomeEnum() == null || subDto.getField1() == null) { + return ResponseEntity.status(I_AM_A_TEAPOT).body(subDto.toString()); + } + return ResponseEntity.status(OK).body(subDto.toString()); + } + @RequestMapping( path = "/multipart/download/{fileId}", method = GET, produces = MULTIPART_FORM_DATA_VALUE ) - public MultiValueMap download (@PathVariable("fileId") String fileId) { + public MultiValueMap download(@PathVariable("fileId") String fileId) { val multiParts = new LinkedMultiValueMap(); val infoString = "The text for file ID " + fileId + ". Testing unicode €"; diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java index 1736cfa..d6665b3 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java @@ -21,9 +21,10 @@ import static org.junit.Assert.assertEquals; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import feign.form.feign.spring.SubDto.SubEnumeration; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; - import lombok.val; import org.junit.Assert; import org.junit.Test; @@ -124,7 +125,7 @@ public void upload6ArrayTest () throws Exception { } @Test - public void upload6CollectionTest () throws Exception { + public void upload6CollectionTest() throws Exception { List list = asList( (MultipartFile) new MockMultipartFile("popa1", "popa1", null, "Hello".getBytes(UTF_8)), (MultipartFile) new MockMultipartFile("popa2", "popa2", null, " world".getBytes(UTF_8)) @@ -133,4 +134,22 @@ public void upload6CollectionTest () throws Exception { val response = client.upload6Collection(list); Assert.assertEquals("Hello world", response); } + + @Test + public void upload7Test() { + val file1 = new MockMultipartFile("one.txt", "One".getBytes(StandardCharsets.UTF_8)); + val file2 = new MockMultipartFile("two.txt", "Two".getBytes(StandardCharsets.UTF_8)); + + val dto = new SubDto(); + dto.setSomeEnum(SubEnumeration.THREE) + .setSubBool(null) + .setSubFile(file2) + .setFile(file1) + .setField1("Field 1") + .setField2(42); + + val response = client.upload7(dto); + Assert.assertEquals(200, response.status()); + Assert.assertEquals(dto.toString(), response.body().toString()); + } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SubDto.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SubDto.java new file mode 100644 index 0000000..e7c8b0f --- /dev/null +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SubDto.java @@ -0,0 +1,32 @@ +package feign.form.feign.spring; + +import static lombok.AccessLevel.PRIVATE; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +import org.springframework.web.multipart.MultipartFile; + +@EqualsAndHashCode(callSuper = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +@FieldDefaults(level = PRIVATE) +@ToString +public class SubDto extends Dto { + + MultipartFile subFile; + + SubEnumeration someEnum; + + Boolean subBool; + + public enum SubEnumeration { + ONE, TWO, THREE + } +} From 102fc75f99c9532cde9d88818a1c831a6f397e3b Mon Sep 17 00:00:00 2001 From: Muhammad Rifqi Fatchurrahman Date: Thu, 7 Oct 2021 12:18:00 +0700 Subject: [PATCH 3/4] form encode pojo super class and enum type --- .../java/feign/form/feign/spring/Client.java | 2 +- .../java/feign/form/feign/spring/Server.java | 10 ++- .../feign/spring/SpringFormEncoderTest.java | 3 +- .../src/main/java/feign/form/FormEncoder.java | 11 ++- .../java/feign/form/multipart/PojoWriter.java | 2 +- .../main/java/feign/form/util/PojoUtil.java | 69 +++++++++++++++++-- pom.xml | 7 +- 7 files changed, 82 insertions(+), 22 deletions(-) diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java index e24222a..e100f20 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java @@ -113,7 +113,7 @@ String upload4 (@PathVariable("id") String id, path = "/multipart/upload7", consumes = MULTIPART_FORM_DATA_VALUE ) - Response upload7(@ModelAttribute SubDto dto); + String upload7(@ModelAttribute SubDto dto); class ClientConfiguration { diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java index 7bec4f4..e64da95 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java @@ -24,6 +24,7 @@ import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; +import feign.form.feign.spring.SubDto.SubEnumeration; import java.io.IOException; import java.util.Map; import lombok.SneakyThrows; @@ -139,10 +140,13 @@ public ResponseEntity upload6 (@RequestParam("popa1") MultipartFile popa consumes = MULTIPART_FORM_DATA_VALUE ) public ResponseEntity upload7(@ModelAttribute SubDto subDto) { - if (subDto.getFile() == null || subDto.getSomeEnum() == null || subDto.getField1() == null) { - return ResponseEntity.status(I_AM_A_TEAPOT).body(subDto.toString()); + assert subDto != null; + assert subDto.getSomeEnum() != null; + if (subDto.getFile() == null || subDto.getSomeEnum() != SubEnumeration.THREE + || subDto.getField1() == null) { + return ResponseEntity.status(I_AM_A_TEAPOT).build(); } - return ResponseEntity.status(OK).body(subDto.toString()); + return ResponseEntity.status(OK).body(subDto.getSomeEnum().name()); } @RequestMapping( diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java index d6665b3..e201296 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java @@ -149,7 +149,6 @@ public void upload7Test() { .setField2(42); val response = client.upload7(dto); - Assert.assertEquals(200, response.status()); - Assert.assertEquals(dto.toString(), response.body().toString()); + Assert.assertEquals(dto.getSomeEnum().name(), response); } } diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index 0bd5a9e..f25ce26 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -22,16 +22,15 @@ import static java.util.Arrays.asList; import static lombok.AccessLevel.PRIVATE; +import feign.RequestTemplate; +import feign.codec.EncodeException; +import feign.codec.Encoder; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; - -import feign.RequestTemplate; -import feign.codec.EncodeException; -import feign.codec.Encoder; import lombok.experimental.FieldDefaults; import lombok.val; @@ -75,7 +74,7 @@ public FormEncoder (Encoder delegate) { new UrlencodedFormContentProcessor() ); - processors = new HashMap(list.size(), 1.F); + processors = new HashMap<>(list.size(), 1.F); for (ContentProcessor processor : list) { processors.put(processor.getSupportedContentType(), processor); } @@ -95,7 +94,7 @@ public void encode (Object object, Type bodyType, RequestTemplate template) thro if (MAP_STRING_WILDCARD.equals(bodyType)) { data = (Map) object; } else if (isUserPojo(bodyType)) { - data = toMap(object); + data = toMap(object, false); } else { delegate.encode(object, bodyType, template); return; diff --git a/feign-form/src/main/java/feign/form/multipart/PojoWriter.java b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java index fe27fa7..32e4ebf 100644 --- a/feign-form/src/main/java/feign/form/multipart/PojoWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java @@ -42,7 +42,7 @@ public boolean isApplicable (Object object) { @Override public void write (Output output, String boundary, String key, Object object) throws EncodeException { - val map = toMap(object); + val map = toMap(object, false); for (val entry : map.entrySet()) { val writer = findApplicableWriter(entry.getValue()); if (writer == null) { diff --git a/feign-form/src/main/java/feign/form/util/PojoUtil.java b/feign-form/src/main/java/feign/form/util/PojoUtil.java index e2b89a5..38b0cf5 100644 --- a/feign-form/src/main/java/feign/form/util/PojoUtil.java +++ b/feign-form/src/main/java/feign/form/util/PojoUtil.java @@ -20,24 +20,27 @@ import static java.lang.reflect.Modifier.isStatic; import static lombok.AccessLevel.PRIVATE; +import feign.codec.EncodeException; +import feign.form.FormProperty; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.rmi.UnexpectedException; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.AbstractMap; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; - +import java.util.stream.Collectors; import javax.annotation.Nullable; - -import feign.form.FormProperty; - import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.Setter; import lombok.SneakyThrows; import lombok.experimental.FieldDefaults; import lombok.val; +import lombok.var; /** * @@ -57,6 +60,7 @@ public static boolean isUserPojo (@NonNull Type type) { } @SneakyThrows + @Deprecated public static Map toMap (@NonNull Object object) { val result = new HashMap(); val type = object.getClass(); @@ -75,15 +79,66 @@ public static Map toMap (@NonNull Object object) { } val propertyKey = field.isAnnotationPresent(FormProperty.class) - ? field.getAnnotation(FormProperty.class).value() - : field.getName(); + ? field.getAnnotation(FormProperty.class).value() + : field.getName(); result.put(propertyKey, fieldValue); } return result; } - private PojoUtil () throws UnexpectedException { + public static Map toMap( + final @NonNull Object object, + final boolean processTransient) { + final var result = new HashMap(); + var clazz = object.getClass(); + val setAccessibleAction = new SetAccessibleAction(); + while (clazz != null) { + final var fieldResult = Arrays.stream(clazz.getDeclaredFields()) + .filter(field -> !Modifier.isFinal(field.getModifiers()) && + (processTransient || !Modifier.isTransient(field.getModifiers())) && + !Modifier.isStatic(field.getModifiers())) + .map(field -> toMapDoOnEach(setAccessibleAction, field, object)) + .filter(entry -> entry.getValue() != null) + .collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (oldObj, newObj) -> newObj, + HashMap::new)); + result.putAll(fieldResult); + clazz = clazz.getSuperclass(); + } + return result; + } + + private static void setFieldAccessible( + final SetAccessibleAction setAccessibleAction, + final Field field) { + setAccessibleAction.setField(field); + AccessController.doPrivileged(setAccessibleAction); + } + + private static Map.Entry toMapDoOnEach( + final SetAccessibleAction setAccessibleAction, + final Field field, + final Object object) throws EncodeException { + + setFieldAccessible(setAccessibleAction, field); + try { + var fieldValue = field.get(object); + if (fieldValue != null && fieldValue.getClass().isEnum()) { + fieldValue = ((Enum) fieldValue).name(); + } + final var propertyKey = field.isAnnotationPresent(FormProperty.class) + ? field.getAnnotation(FormProperty.class).value() + : field.getName(); + return new AbstractMap.SimpleEntry<>(propertyKey, fieldValue); + } catch (Exception err) { + throw new EncodeException(err.getMessage(), err); + } + } + + private PojoUtil() throws UnexpectedException { throw new UnexpectedException("It is not allowed to instantiate this class"); } diff --git a/pom.xml b/pom.xml index 6d17899..21ea80c 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,9 @@ limitations under the License. 1.8 1.8 + + 1.8 + java18 Open Feign Forms Parent @@ -132,14 +135,14 @@ limitations under the License. io.github.openfeign feign-core - 10.2.0 + 11.6 provided io.github.openfeign feign-jackson - 10.2.0 + 11.6 test From fe16edcc1b989b6b53155bf11f3b76b06c99cc18 Mon Sep 17 00:00:00 2001 From: Muhammad Rifqi Fatchurrahman Date: Thu, 7 Oct 2021 12:22:31 +0700 Subject: [PATCH 4/4] default to process final field --- feign-form/src/main/java/feign/form/FormEncoder.java | 2 +- .../src/main/java/feign/form/multipart/PojoWriter.java | 2 +- feign-form/src/main/java/feign/form/util/PojoUtil.java | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index f25ce26..46e5259 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -94,7 +94,7 @@ public void encode (Object object, Type bodyType, RequestTemplate template) thro if (MAP_STRING_WILDCARD.equals(bodyType)) { data = (Map) object; } else if (isUserPojo(bodyType)) { - data = toMap(object, false); + data = toMap(object, false, true); } else { delegate.encode(object, bodyType, template); return; diff --git a/feign-form/src/main/java/feign/form/multipart/PojoWriter.java b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java index 32e4ebf..709d63c 100644 --- a/feign-form/src/main/java/feign/form/multipart/PojoWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java @@ -42,7 +42,7 @@ public boolean isApplicable (Object object) { @Override public void write (Output output, String boundary, String key, Object object) throws EncodeException { - val map = toMap(object, false); + val map = toMap(object, false, true); for (val entry : map.entrySet()) { val writer = findApplicableWriter(entry.getValue()); if (writer == null) { diff --git a/feign-form/src/main/java/feign/form/util/PojoUtil.java b/feign-form/src/main/java/feign/form/util/PojoUtil.java index 38b0cf5..96fd249 100644 --- a/feign-form/src/main/java/feign/form/util/PojoUtil.java +++ b/feign-form/src/main/java/feign/form/util/PojoUtil.java @@ -89,15 +89,17 @@ public static Map toMap (@NonNull Object object) { public static Map toMap( final @NonNull Object object, - final boolean processTransient) { + final boolean processTransient, + final boolean processFinal) { final var result = new HashMap(); var clazz = object.getClass(); val setAccessibleAction = new SetAccessibleAction(); while (clazz != null) { final var fieldResult = Arrays.stream(clazz.getDeclaredFields()) - .filter(field -> !Modifier.isFinal(field.getModifiers()) && - (processTransient || !Modifier.isTransient(field.getModifiers())) && - !Modifier.isStatic(field.getModifiers())) + .filter(field -> + (processFinal || !Modifier.isFinal(field.getModifiers())) && + (processTransient || !Modifier.isTransient(field.getModifiers())) && + !Modifier.isStatic(field.getModifiers())) .map(field -> toMapDoOnEach(setAccessibleAction, field, object)) .filter(entry -> entry.getValue() != null) .collect(Collectors.toMap(