Skip to content
This repository has been archived by the owner on Dec 31, 2024. It is now read-only.

Allow @RequestPart POJOs #89

Closed
6 changes: 3 additions & 3 deletions feign-form-spring/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ limitations under the License.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.5.RELEASE</version>
<version>5.2.5.RELEASE</version>
<scope>compile</scope>
</dependency>

Expand All @@ -58,13 +58,13 @@ limitations under the License.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.3.RELEASE</version>
<version>2.2.6.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.1.RELEASE</version>
<version>2.2.2.RELEASE</version>
<scope>test</scope>
<exclusions>
<exclusion>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Object>(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<String, Object>();
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,23 @@
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;
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.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -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(
Expand Down Expand Up @@ -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<MultipartFile> files);
String upload6Collection (@RequestPart("files") List<MultipartFile> 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<MultipartFile> files);

@RequestMapping(
path = "/multipart/upload9",
method = POST,
consumes = MULTIPART_FORM_DATA_VALUE
)
String upload9Array (@RequestPart("pojos") Pojo[] pojos, @RequestPart("files") List<MultipartFile> files);

@RequestMapping(
path = "/multipart/upload9",
method = POST,
consumes = MULTIPART_FORM_DATA_VALUE
)
String upload9Collection (@RequestPart("pojos") List<Pojo> pojos, @RequestPart("files") List<MultipartFile> files);

class ClientConfiguration {

Expand All @@ -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
Expand Down
36 changes: 36 additions & 0 deletions feign-form-spring/src/test/java/feign/form/feign/spring/Pojo.java
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -121,9 +122,11 @@ void upload5 (Dto dto) throws IOException {
method = POST,
consumes = MULTIPART_FORM_DATA_VALUE
)
public ResponseEntity<String> upload6 (@RequestParam("popa1") MultipartFile popa1,
@RequestParam("popa2") MultipartFile popa2
public ResponseEntity<String> upload6 (@RequestPart("files") List<MultipartFile> 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) {
Expand All @@ -133,6 +136,48 @@ public ResponseEntity<String> upload6 (@RequestParam("popa1") MultipartFile popa
return ResponseEntity.status(status).body(result);
}

@RequestMapping(
path = "/multipart/upload7",
method = POST,
consumes = MULTIPART_FORM_DATA_VALUE
)
public ResponseEntity<String> 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<String> upload8 (@RequestPart("pojo") Pojo pojo, @RequestPart("files") List<MultipartFile> 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<String> upload9 (@RequestPart("pojos") List<Pojo> pojos, @RequestPart("files") List<MultipartFile> 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,
Expand Down
Loading