Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encoder enhancement #102

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
Expand Up @@ -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;
Expand Down Expand Up @@ -99,22 +100,28 @@ 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<MultipartFile> files);
String upload6Collection(@RequestPart List<MultipartFile> files);

@PostMapping(
path = "/multipart/upload7",
consumes = MULTIPART_FORM_DATA_VALUE
)
String upload7(@ModelAttribute SubDto dto);

class ClientConfiguration {

@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;

@Bean
public Encoder feignEncoder () {
public Encoder feignEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
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;
import lombok.val;
import org.springframework.boot.autoconfigure.SpringBootApplication;
Expand All @@ -39,7 +39,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;
Expand Down Expand Up @@ -122,7 +124,7 @@ void upload5 (Dto dto) throws IOException {
consumes = MULTIPART_FORM_DATA_VALUE
)
public ResponseEntity<String> upload6 (@RequestParam("popa1") MultipartFile popa1,
@RequestParam("popa2") MultipartFile popa2
@RequestParam("popa2") MultipartFile popa2
) throws Exception {
HttpStatus status = I_AM_A_TEAPOT;
String result = "";
Expand All @@ -133,12 +135,26 @@ public ResponseEntity<String> upload6 (@RequestParam("popa1") MultipartFile popa
return ResponseEntity.status(status).body(result);
}

@PostMapping(
path = "/multipart/upload7",
consumes = MULTIPART_FORM_DATA_VALUE
)
public ResponseEntity<String> upload7(@ModelAttribute SubDto subDto) {
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.getSomeEnum().name());
}

@RequestMapping(
path = "/multipart/download/{fileId}",
method = GET,
produces = MULTIPART_FORM_DATA_VALUE
)
public MultiValueMap<String, Object> download (@PathVariable("fileId") String fileId) {
public MultiValueMap<String, Object> download(@PathVariable("fileId") String fileId) {
val multiParts = new LinkedMultiValueMap<String, Object>();

val infoString = "The text for file ID " + fileId + ". Testing unicode €";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -124,7 +125,7 @@ public void upload6ArrayTest () throws Exception {
}

@Test
public void upload6CollectionTest () throws Exception {
public void upload6CollectionTest() throws Exception {
List<MultipartFile> list = asList(
(MultipartFile) new MockMultipartFile("popa1", "popa1", null, "Hello".getBytes(UTF_8)),
(MultipartFile) new MockMultipartFile("popa2", "popa2", null, " world".getBytes(UTF_8))
Expand All @@ -133,4 +134,21 @@ 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(dto.getSomeEnum().name(), response);
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
11 changes: 5 additions & 6 deletions feign-form/src/main/java/feign/form/FormEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -75,7 +74,7 @@ public FormEncoder (Encoder delegate) {
new UrlencodedFormContentProcessor()
);

processors = new HashMap<ContentType, ContentProcessor>(list.size(), 1.F);
processors = new HashMap<>(list.size(), 1.F);
for (ContentProcessor processor : list) {
processors.put(processor.getSupportedContentType(), processor);
}
Expand All @@ -95,7 +94,7 @@ public void encode (Object object, Type bodyType, RequestTemplate template) thro
if (MAP_STRING_WILDCARD.equals(bodyType)) {
data = (Map<String, Object>) object;
} else if (isUserPojo(bodyType)) {
data = toMap(object);
data = toMap(object, false, true);
} else {
delegate.encode(object, bodyType, template);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, true);
for (val entry : map.entrySet()) {
val writer = findApplicableWriter(entry.getValue());
if (writer == null) {
Expand Down
71 changes: 64 additions & 7 deletions feign-form/src/main/java/feign/form/util/PojoUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
*
Expand All @@ -57,6 +60,7 @@ public static boolean isUserPojo (@NonNull Type type) {
}

@SneakyThrows
@Deprecated
public static Map<String, Object> toMap (@NonNull Object object) {
val result = new HashMap<String, Object>();
val type = object.getClass();
Expand All @@ -75,15 +79,68 @@ public static Map<String, Object> 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<String, Object> toMap(
final @NonNull Object object,
final boolean processTransient,
final boolean processFinal) {
final var result = new HashMap<String, Object>();
var clazz = object.getClass();
val setAccessibleAction = new SetAccessibleAction();
while (clazz != null) {
final var fieldResult = Arrays.stream(clazz.getDeclaredFields())
.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(
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<String, Object> 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");
}

Expand Down
Loading