diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml
index 17a9ff9..ffc75ee 100644
--- a/feign-form-spring/pom.xml
+++ b/feign-form-spring/pom.xml
@@ -44,7 +44,7 @@ limitations under the License.
org.springframework
spring-web
- 5.1.5.RELEASE
+ 5.2.5.RELEASE
compile
@@ -58,13 +58,13 @@ limitations under the License.
org.springframework.boot
spring-boot-starter-web
- 2.1.3.RELEASE
+ 2.2.6.RELEASE
test
org.springframework.cloud
spring-cloud-starter-openfeign
- 2.1.1.RELEASE
+ 2.2.2.RELEASE
test
diff --git a/feign-form-spring/src/main/java/feign/form/spring/PojoSerializationWriter.java b/feign-form-spring/src/main/java/feign/form/spring/PojoSerializationWriter.java
new file mode 100644
index 0000000..fca4472
--- /dev/null
+++ b/feign-form-spring/src/main/java/feign/form/spring/PojoSerializationWriter.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 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
+ *
+ * http://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 feign.form.spring;
+
+import static feign.form.ContentProcessor.CRLF;
+import static feign.form.util.PojoUtil.isUserPojo;
+
+import org.springframework.http.MediaType;
+import org.springframework.web.multipart.MultipartFile;
+
+import feign.codec.EncodeException;
+import feign.form.multipart.AbstractWriter;
+import feign.form.multipart.Output;
+
+import lombok.val;
+
+import java.io.IOException;
+
+/**
+ *
+ * @author Darren Foong
+ */
+public abstract class PojoSerializationWriter extends AbstractWriter {
+ @Override
+ public boolean isApplicable(Object object) {
+ return !(object instanceof MultipartFile) && !(object instanceof MultipartFile[])
+ && (isUserPojoCollection(object) || isUserPojo(object));
+ }
+
+ @Override
+ public void write (Output output, String key, Object object) throws EncodeException {
+ try {
+ val string = new StringBuilder()
+ .append("Content-Disposition: form-data; name=\"").append(key).append('"')
+ .append(CRLF)
+ .append("Content-Type: ").append(getContentType())
+ .append("; charset=").append(output.getCharset().name())
+ .append(CRLF)
+ .append(CRLF)
+ .append(serialize(object))
+ .toString();
+
+ output.write(string);
+ } catch (IOException e) {
+ throw new EncodeException(e.getMessage());
+ }
+ }
+
+ protected abstract MediaType getContentType();
+
+ protected abstract String serialize(Object object) throws IOException;
+
+ private boolean isUserPojoCollection(Object object) {
+ if (object.getClass().isArray()) {
+ val array = (Object[]) object;
+
+ return array.length > 1 && isUserPojo(array[0]);
+ }
+
+ if (!(object instanceof Iterable)) {
+ return false;
+ }
+
+ val iterable = (Iterable>) object;
+ val iterator = iterable.iterator();
+
+ if (iterator.hasNext()) {
+ val next = iterator.next();
+
+ return !(next instanceof MultipartFile) && isUserPojo(next);
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java b/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java
index 2ce3321..fe0cbce 100644
--- a/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java
+++ b/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java
@@ -59,38 +59,23 @@ public SpringFormEncoder (Encoder delegate) {
processor.addFirstWriter(new SpringManyMultipartFilesWriter());
}
+ public SpringFormEncoder(PojoSerializationWriter pojoSerializationWriter, Encoder delegate) {
+ super(delegate);
+
+ val processor = (MultipartFormContentProcessor) getContentProcessor(MULTIPART);
+ processor.addFirstWriter(new SpringSingleMultipartFileWriter());
+ processor.addFirstWriter(new SpringManyMultipartFilesWriter());
+ processor.addFirstWriter(pojoSerializationWriter);
+ }
+
@Override
public void encode (Object object, Type bodyType, RequestTemplate template) throws EncodeException {
- if (bodyType.equals(MultipartFile[].class)) {
- val files = (MultipartFile[]) object;
- val data = new HashMap(files.length, 1.F);
- for (val file : files) {
- data.put(file.getName(), file);
- }
- super.encode(data, MAP_STRING_WILDCARD, template);
- } else if (bodyType.equals(MultipartFile.class)) {
+ if (bodyType.equals(MultipartFile.class)) {
val file = (MultipartFile) object;
val data = singletonMap(file.getName(), object);
super.encode(data, MAP_STRING_WILDCARD, template);
- } else if (isMultipartFileCollection(object)) {
- val iterable = (Iterable>) object;
- val data = new HashMap();
- for (val item : iterable) {
- val file = (MultipartFile) item;
- data.put(file.getName(), file);
- }
- super.encode(data, MAP_STRING_WILDCARD, template);
} else {
super.encode(object, bodyType, template);
}
}
-
- private boolean isMultipartFileCollection (Object object) {
- if (!(object instanceof Iterable)) {
- return false;
- }
- val iterable = (Iterable>) object;
- val iterator = iterable.iterator();
- return iterator.hasNext() && iterator.next() instanceof MultipartFile;
- }
}
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..69d03c6 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,12 +20,15 @@
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
+import java.io.IOException;
import java.util.List;
import java.util.Map;
+import com.fasterxml.jackson.databind.ObjectMapper;
import feign.Logger;
import feign.Response;
import feign.codec.Encoder;
+import feign.form.spring.PojoSerializationWriter;
import feign.form.spring.SpringFormEncoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -33,6 +36,7 @@
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
+import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -57,7 +61,7 @@ public interface Client {
consumes = MULTIPART_FORM_DATA_VALUE
)
String upload1 (@PathVariable("folder") String folder,
- @RequestPart MultipartFile file,
+ @RequestPart("file") MultipartFile file,
@RequestParam(value = "message", required = false) String message);
@RequestMapping(
@@ -99,14 +103,42 @@ String upload4 (@PathVariable("id") String id,
method = POST,
consumes = MULTIPART_FORM_DATA_VALUE
)
- String upload6Array (@RequestPart MultipartFile[] files);
+ String upload6Array (@RequestPart("files") MultipartFile[] files);
@RequestMapping(
path = "/multipart/upload6",
method = POST,
consumes = MULTIPART_FORM_DATA_VALUE
)
- String upload6Collection (@RequestPart List files);
+ String upload6Collection (@RequestPart("files") List files);
+
+ @RequestMapping(
+ path = "/multipart/upload7",
+ method = POST,
+ consumes = MULTIPART_FORM_DATA_VALUE
+ )
+ String upload7 (@RequestPart("pojo") Pojo pojo);
+
+ @RequestMapping(
+ path = "/multipart/upload8",
+ method = POST,
+ consumes = MULTIPART_FORM_DATA_VALUE
+ )
+ String upload8 (@RequestPart("pojo") Pojo pojo, @RequestPart("files") List files);
+
+ @RequestMapping(
+ path = "/multipart/upload9",
+ method = POST,
+ consumes = MULTIPART_FORM_DATA_VALUE
+ )
+ String upload9Array (@RequestPart("pojos") Pojo[] pojos, @RequestPart("files") List files);
+
+ @RequestMapping(
+ path = "/multipart/upload9",
+ method = POST,
+ consumes = MULTIPART_FORM_DATA_VALUE
+ )
+ String upload9Collection (@RequestPart("pojos") List pojos, @RequestPart("files") List files);
class ClientConfiguration {
@@ -115,7 +147,21 @@ class ClientConfiguration {
@Bean
public Encoder feignEncoder () {
- return new SpringFormEncoder(new SpringEncoder(messageConverters));
+ PojoSerializationWriter pojoSerializationWriter = new PojoSerializationWriter() {
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ @Override
+ protected MediaType getContentType() {
+ return MediaType.APPLICATION_JSON;
+ }
+
+ @Override
+ protected String serialize(Object object) throws IOException {
+ return objectMapper.writeValueAsString(object);
+ }
+ };
+
+ return new SpringFormEncoder(pojoSerializationWriter, new SpringEncoder(messageConverters));
}
@Bean
diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Pojo.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Pojo.java
new file mode 100644
index 0000000..f0668c3
--- /dev/null
+++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Pojo.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 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
+ *
+ * http://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 feign.form.feign.spring;
+
+import static lombok.AccessLevel.PRIVATE;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.FieldDefaults;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@FieldDefaults(level = PRIVATE)
+public class Pojo {
+ String field1;
+
+ String field2;
+
+ int field3;
+}
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..584be6e 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
@@ -25,6 +25,7 @@
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import java.io.IOException;
+import java.util.List;
import java.util.Map;
import lombok.SneakyThrows;
@@ -121,9 +122,11 @@ void upload5 (Dto dto) throws IOException {
method = POST,
consumes = MULTIPART_FORM_DATA_VALUE
)
- public ResponseEntity upload6 (@RequestParam("popa1") MultipartFile popa1,
- @RequestParam("popa2") MultipartFile popa2
+ public ResponseEntity upload6 (@RequestPart("files") List files
) throws Exception {
+ MultipartFile popa1 = files.get(0);
+ MultipartFile popa2 = files.get(1);
+
HttpStatus status = I_AM_A_TEAPOT;
String result = "";
if (popa1 != null && popa2 != null) {
@@ -133,6 +136,48 @@ public ResponseEntity upload6 (@RequestParam("popa1") MultipartFile popa
return ResponseEntity.status(status).body(result);
}
+ @RequestMapping(
+ path = "/multipart/upload7",
+ method = POST,
+ consumes = MULTIPART_FORM_DATA_VALUE
+ )
+ public ResponseEntity upload7 (@RequestPart("pojo") Pojo pojo
+ ) throws Exception {
+ val result = pojo.getField1() + pojo.getField2() + pojo.getField3();
+
+ return ResponseEntity.ok(result);
+ }
+
+ @RequestMapping(
+ path = "/multipart/upload8",
+ method = POST,
+ consumes = MULTIPART_FORM_DATA_VALUE
+ )
+ public ResponseEntity upload8 (@RequestPart("pojo") Pojo pojo, @RequestPart("files") List files
+ ) throws Exception {
+ val result1 = pojo.getField1() + pojo.getField2() + pojo.getField3();
+ val result2 = new String(files.get(0).getBytes()) + new String(files.get(1).getBytes());
+
+ return ResponseEntity.ok(result1 + result2);
+ }
+
+ @RequestMapping(
+ path = "/multipart/upload9",
+ method = POST,
+ consumes = MULTIPART_FORM_DATA_VALUE
+ )
+ public ResponseEntity upload9 (@RequestPart("pojos") List pojos, @RequestPart("files") List files
+ ) throws Exception {
+ val pojo1 = pojos.get(0);
+ val pojo2 = pojos.get(1);
+
+ val result1 = pojo1.getField1() + pojo1.getField2() + pojo1.getField3();
+ val result2 = pojo2.getField1() + pojo2.getField2() + pojo2.getField3();
+ val result3 = new String(files.get(0).getBytes()) + new String(files.get(1).getBytes());
+
+ return ResponseEntity.ok(result1 + result2 + result3);
+ }
+
@RequestMapping(
path = "/multipart/download/{fileId}",
method = GET,
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..3fbb886 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
@@ -125,7 +125,7 @@ public void upload6ArrayTest () throws Exception {
@Test
public void upload6CollectionTest () throws Exception {
- List list = asList(
+ val list = asList(
(MultipartFile) new MockMultipartFile("popa1", "popa1", null, "Hello".getBytes(UTF_8)),
(MultipartFile) new MockMultipartFile("popa2", "popa2", null, " world".getBytes(UTF_8))
);
@@ -133,4 +133,77 @@ public void upload6CollectionTest () throws Exception {
val response = client.upload6Collection(list);
Assert.assertEquals("Hello world", response);
}
+
+ @Test
+ public void upload6ArraySameNameTest () throws Exception {
+ val file1 = new MockMultipartFile("popa0", "popa1", null, "Hello".getBytes(UTF_8));
+ val file2 = new MockMultipartFile("popa0", "popa2", null, " world".getBytes(UTF_8));
+
+ val response = client.upload6Array(new MultipartFile[] { file1, file2 });
+ Assert.assertEquals("Hello world", response);
+ }
+
+ @Test
+ public void upload6CollectionSameNameTest () throws Exception {
+ List list = asList(
+ (MultipartFile) new MockMultipartFile("popa0", "popa1", null, "Hello".getBytes(UTF_8)),
+ (MultipartFile) new MockMultipartFile("popa0", "popa2", null, " world".getBytes(UTF_8))
+ );
+
+ val response = client.upload6Collection(list);
+ Assert.assertEquals("Hello world", response);
+ }
+
+ @Test
+ public void upload7Test () throws Exception {
+ val pojo = new Pojo("Hello", " world", 1);
+
+ val response = client.upload7(pojo);
+ Assert.assertEquals("Hello world1", response);
+ }
+
+ @Test
+ public void upload8Test () throws Exception {
+ val pojo = new Pojo("Hello", " world", 1);
+
+ val list = asList(
+ (MultipartFile) new MockMultipartFile("files", "popa1", null, "Hello".getBytes(UTF_8)),
+ (MultipartFile) new MockMultipartFile("files", "popa2", null, " world".getBytes(UTF_8))
+ );
+
+ val response = client.upload8(pojo, list);
+ Assert.assertEquals("Hello world1Hello world", response);
+ }
+
+ @Test
+ public void upload9ArrayTest () throws Exception {
+ val pojos = new Pojo[]{
+ new Pojo("Hello", " world", 1),
+ new Pojo("Hello", " world", 2)
+ };
+
+ val list = asList(
+ (MultipartFile) new MockMultipartFile("files", "popa1", null, "Hello".getBytes(UTF_8)),
+ (MultipartFile) new MockMultipartFile("files", "popa2", null, " world".getBytes(UTF_8))
+ );
+
+ val response = client.upload9Array(pojos, list);
+ Assert.assertEquals("Hello world1Hello world2Hello world", response);
+ }
+
+ @Test
+ public void upload9CollectionTest () throws Exception {
+ val pojos = asList(
+ new Pojo("Hello", " world", 1),
+ new Pojo("Hello", " world", 2)
+ );
+
+ val list = asList(
+ (MultipartFile) new MockMultipartFile("files", "popa1", null, "Hello".getBytes(UTF_8)),
+ (MultipartFile) new MockMultipartFile("files", "popa2", null, " world".getBytes(UTF_8))
+ );
+
+ val response = client.upload9Collection(pojos, list);
+ Assert.assertEquals("Hello world1Hello world2Hello world", response);
+ }
}
diff --git a/pom.xml b/pom.xml
index d038562..9a40ef1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -146,13 +146,13 @@ limitations under the License.
org.springframework.boot
spring-boot-starter-web
- 2.1.3.RELEASE
+ 2.2.6.RELEASE
test
org.springframework.boot
spring-boot-starter-test
- 2.1.3.RELEASE
+ 2.2.6.RELEASE
test