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

Allow @RequestPart POJOs #89

Closed
Closed
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
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