Skip to content

Commit 28e7909

Browse files
Basic Multipart support in Spring MVC (#3)
Adds support for binding MultipartFile and Servlet API Part objects using the FormParameter and RequestParameter annotations.
1 parent 897c6dd commit 28e7909

13 files changed

+1339
-13
lines changed

integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterBean.java

+74-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2020 the original author or authors.
2+
* Copyright 2019-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,7 +19,9 @@
1919
import com.mattbertolini.spring.web.bind.annotation.BeanParameter;
2020
import com.mattbertolini.spring.web.bind.annotation.FormParameter;
2121
import org.springframework.util.MultiValueMap;
22+
import org.springframework.web.multipart.MultipartFile;
2223

24+
import javax.servlet.http.Part;
2325
import javax.validation.constraints.NotEmpty;
2426
import java.util.Map;
2527

@@ -101,4 +103,75 @@ public NestedBean getNestedBean() {
101103
public void setNestedBean(NestedBean nestedBean) {
102104
this.nestedBean = nestedBean;
103105
}
106+
107+
/**
108+
* Test bean for multipart binding in a Servlet/WebMVC application
109+
*/
110+
static class ServletMultipartBean {
111+
@FormParameter("file")
112+
private MultipartFile multipartFile;
113+
114+
@FormParameter("part")
115+
private Part part;
116+
117+
@FormParameter
118+
private Map<String, MultipartFile> multipartFileMap;
119+
120+
@FormParameter
121+
private MultiValueMap<String, MultipartFile> multiValueMultipartMap;
122+
123+
@FormParameter
124+
private Map<String, Part> partMap;
125+
126+
@FormParameter
127+
private MultiValueMap<String, Part> multiValuePartMap;
128+
129+
public MultipartFile getMultipartFile() {
130+
return multipartFile;
131+
}
132+
133+
public void setMultipartFile(MultipartFile multipartFile) {
134+
this.multipartFile = multipartFile;
135+
}
136+
137+
public Part getPart() {
138+
return part;
139+
}
140+
141+
public void setPart(Part part) {
142+
this.part = part;
143+
}
144+
145+
public Map<String, MultipartFile> getMultipartFileMap() {
146+
return multipartFileMap;
147+
}
148+
149+
public void setMultipartFileMap(Map<String, MultipartFile> multipartFileMap) {
150+
this.multipartFileMap = multipartFileMap;
151+
}
152+
153+
public MultiValueMap<String, MultipartFile> getMultiValueMultipartMap() {
154+
return multiValueMultipartMap;
155+
}
156+
157+
public void setMultiValueMultipartMap(MultiValueMap<String, MultipartFile> multiValueMultipartMap) {
158+
this.multiValueMultipartMap = multiValueMultipartMap;
159+
}
160+
161+
public Map<String, Part> getPartMap() {
162+
return partMap;
163+
}
164+
165+
public void setPartMap(Map<String, Part> partMap) {
166+
this.partMap = partMap;
167+
}
168+
169+
public MultiValueMap<String, Part> getMultiValuePartMap() {
170+
return multiValuePartMap;
171+
}
172+
173+
public void setMultiValuePartMap(MultiValueMap<String, Part> multiValuePartMap) {
174+
this.multiValuePartMap = multiValuePartMap;
175+
}
176+
}
104177
}

integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java

+69-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2020 the original author or authors.
2+
* Copyright 2019-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,12 +19,19 @@
1919
import com.mattbertolini.spring.web.bind.annotation.BeanParameter;
2020
import org.springframework.http.MediaType;
2121
import org.springframework.util.MultiValueMap;
22+
import org.springframework.util.StreamUtils;
2223
import org.springframework.validation.BindingResult;
2324
import org.springframework.web.bind.annotation.PostMapping;
2425
import org.springframework.web.bind.annotation.RestController;
26+
import org.springframework.web.multipart.MultipartFile;
2527

28+
import javax.servlet.http.Part;
2629
import javax.validation.Valid;
30+
import java.io.IOException;
31+
import java.nio.charset.StandardCharsets;
32+
import java.util.List;
2733
import java.util.Map;
34+
import java.util.stream.Collectors;
2835

2936
@RestController
3037
public class FormParameterController {
@@ -73,6 +80,67 @@ public String validatedWithBindingResult(@Valid @BeanParameter FormParameterBean
7380
return "valid";
7481
}
7582

83+
@PostMapping(value = "/multipartFile", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
84+
public String multipartFile(@BeanParameter FormParameterBean.ServletMultipartBean formParameterBean) throws Exception {
85+
MultipartFile multipartFile = formParameterBean.getMultipartFile();
86+
if (multipartFile == null || multipartFile.isEmpty()) {
87+
throw new RuntimeException("Multipart file is null or empty");
88+
}
89+
return new String(multipartFile.getBytes());
90+
}
91+
92+
@PostMapping(value = "/part", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
93+
public String part(@BeanParameter FormParameterBean.ServletMultipartBean formParameterBean) throws Exception {
94+
Part part = formParameterBean.getPart();
95+
if (part == null || part.getSize() <= 0) {
96+
throw new RuntimeException("Multipart file is null or empty");
97+
}
98+
return StreamUtils.copyToString(part.getInputStream(), StandardCharsets.UTF_8);
99+
}
100+
101+
@PostMapping(value = "/multipartFileMap", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
102+
public String multipartFileMap(@BeanParameter FormParameterBean.ServletMultipartBean formParameterBean) throws Exception {
103+
Map<String, MultipartFile> multipartFileMap = formParameterBean.getMultipartFileMap();
104+
MultipartFile fileOne = multipartFileMap.get("fileOne");
105+
MultipartFile fileTwo = multipartFileMap.get("fileTwo");
106+
return new String(fileOne.getBytes()) + ", " + new String(fileTwo.getBytes());
107+
}
108+
109+
@PostMapping(value = "/multiValueMultipartFileMap", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
110+
public String multiValueMultipartFileMap(@BeanParameter FormParameterBean.ServletMultipartBean formParameterBean) {
111+
MultiValueMap<String, MultipartFile> multiValueMultipartMap = formParameterBean.getMultiValueMultipartMap();
112+
List<MultipartFile> files = multiValueMultipartMap.get("file");
113+
return files.stream().map(multipartFile -> {
114+
try {
115+
return new String(multipartFile.getBytes());
116+
} catch (IOException e) {
117+
throw new RuntimeException(e);
118+
}
119+
}).collect(Collectors.joining(", "));
120+
}
121+
122+
@PostMapping(value = "/partMap", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
123+
public String partMap(@BeanParameter FormParameterBean.ServletMultipartBean formParameterBean) throws Exception {
124+
Map<String, Part> partMap = formParameterBean.getPartMap();
125+
Part fileOne = partMap.get("fileOne");
126+
Part fileTwo = partMap.get("fileTwo");
127+
return StreamUtils.copyToString(fileOne.getInputStream(), StandardCharsets.UTF_8)
128+
+ ", " + StreamUtils.copyToString(fileTwo.getInputStream(), StandardCharsets.UTF_8);
129+
}
130+
131+
@PostMapping(value = "/multiValuePartMap", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
132+
public String multiValuePartMap(@BeanParameter FormParameterBean.ServletMultipartBean formParameterBean) {
133+
MultiValueMap<String, Part> multiValuePartMap = formParameterBean.getMultiValuePartMap();
134+
List<Part> files = multiValuePartMap.get("file");
135+
return files.stream().map(part -> {
136+
try {
137+
return StreamUtils.copyToString(part.getInputStream(), StandardCharsets.UTF_8);
138+
} catch (IOException e) {
139+
throw new RuntimeException(e);
140+
}
141+
}).collect(Collectors.joining(", "));
142+
}
143+
76144
@PostMapping(value = "/nested", produces = MediaType.TEXT_PLAIN_VALUE)
77145
public String nestedBeanParameter(@BeanParameter FormParameterBean formParameterBean) {
78146
return formParameterBean.getNestedBean().getFormData();

integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterBean.java

+74-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2020 the original author or authors.
2+
* Copyright 2019-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,7 +19,9 @@
1919
import com.mattbertolini.spring.web.bind.annotation.BeanParameter;
2020
import com.mattbertolini.spring.web.bind.annotation.RequestParameter;
2121
import org.springframework.util.MultiValueMap;
22+
import org.springframework.web.multipart.MultipartFile;
2223

24+
import javax.servlet.http.Part;
2325
import javax.validation.constraints.NotEmpty;
2426
import java.util.Map;
2527

@@ -101,4 +103,75 @@ public NestedBean getNestedBean() {
101103
public void setNestedBean(NestedBean nestedBean) {
102104
this.nestedBean = nestedBean;
103105
}
106+
107+
/**
108+
* Test bean for multipart binding in a Servlet/WebMVC application
109+
*/
110+
static class ServletMultipartBean {
111+
@RequestParameter("file")
112+
private MultipartFile multipartFile;
113+
114+
@RequestParameter("part")
115+
private Part part;
116+
117+
@RequestParameter
118+
private Map<String, MultipartFile> multipartFileMap;
119+
120+
@RequestParameter
121+
private MultiValueMap<String, MultipartFile> multiValueMultipartMap;
122+
123+
@RequestParameter
124+
private Map<String, Part> partMap;
125+
126+
@RequestParameter
127+
private MultiValueMap<String, Part> multiValuePartMap;
128+
129+
public MultipartFile getMultipartFile() {
130+
return multipartFile;
131+
}
132+
133+
public void setMultipartFile(MultipartFile multipartFile) {
134+
this.multipartFile = multipartFile;
135+
}
136+
137+
public Part getPart() {
138+
return part;
139+
}
140+
141+
public void setPart(Part part) {
142+
this.part = part;
143+
}
144+
145+
public Map<String, MultipartFile> getMultipartFileMap() {
146+
return multipartFileMap;
147+
}
148+
149+
public void setMultipartFileMap(Map<String, MultipartFile> multipartFileMap) {
150+
this.multipartFileMap = multipartFileMap;
151+
}
152+
153+
public MultiValueMap<String, MultipartFile> getMultiValueMultipartMap() {
154+
return multiValueMultipartMap;
155+
}
156+
157+
public void setMultiValueMultipartMap(MultiValueMap<String, MultipartFile> multiValueMultipartMap) {
158+
this.multiValueMultipartMap = multiValueMultipartMap;
159+
}
160+
161+
public Map<String, Part> getPartMap() {
162+
return partMap;
163+
}
164+
165+
public void setPartMap(Map<String, Part> partMap) {
166+
this.partMap = partMap;
167+
}
168+
169+
public MultiValueMap<String, Part> getMultiValuePartMap() {
170+
return multiValuePartMap;
171+
}
172+
173+
public void setMultiValuePartMap(MultiValueMap<String, Part> multiValuePartMap) {
174+
this.multiValuePartMap = multiValuePartMap;
175+
}
176+
}
104177
}

integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterController.java

+70-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2020 the original author or authors.
2+
* Copyright 2019-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,12 +19,20 @@
1919
import com.mattbertolini.spring.web.bind.annotation.BeanParameter;
2020
import org.springframework.http.MediaType;
2121
import org.springframework.util.MultiValueMap;
22+
import org.springframework.util.StreamUtils;
2223
import org.springframework.validation.BindingResult;
2324
import org.springframework.web.bind.annotation.GetMapping;
25+
import org.springframework.web.bind.annotation.PostMapping;
2426
import org.springframework.web.bind.annotation.RestController;
27+
import org.springframework.web.multipart.MultipartFile;
2528

29+
import javax.servlet.http.Part;
2630
import javax.validation.Valid;
31+
import java.io.IOException;
32+
import java.nio.charset.StandardCharsets;
33+
import java.util.List;
2734
import java.util.Map;
35+
import java.util.stream.Collectors;
2836

2937
@RestController
3038
public class RequestParameterController {
@@ -73,6 +81,67 @@ public String validatedWithBindingResult(@Valid @BeanParameter RequestParameterB
7381
return "valid";
7482
}
7583

84+
@PostMapping(value = "/multipartFile", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
85+
public String multipartFile(@BeanParameter RequestParameterBean.ServletMultipartBean requestParameterBean) throws Exception {
86+
MultipartFile multipartFile = requestParameterBean.getMultipartFile();
87+
if (multipartFile == null || multipartFile.isEmpty()) {
88+
throw new RuntimeException("Multipart file is null or empty");
89+
}
90+
return new String(multipartFile.getBytes());
91+
}
92+
93+
@PostMapping(value = "/part", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
94+
public String part(@BeanParameter RequestParameterBean.ServletMultipartBean requestParameterBean) throws Exception {
95+
Part part = requestParameterBean.getPart();
96+
if (part == null || part.getSize() <= 0) {
97+
throw new RuntimeException("Multipart file is null or empty");
98+
}
99+
return StreamUtils.copyToString(part.getInputStream(), StandardCharsets.UTF_8);
100+
}
101+
102+
@PostMapping(value = "/multipartFileMap", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
103+
public String multipartFileMap(@BeanParameter RequestParameterBean.ServletMultipartBean requestParameterBean) throws Exception {
104+
Map<String, MultipartFile> multipartFileMap = requestParameterBean.getMultipartFileMap();
105+
MultipartFile fileOne = multipartFileMap.get("fileOne");
106+
MultipartFile fileTwo = multipartFileMap.get("fileTwo");
107+
return new String(fileOne.getBytes()) + ", " + new String(fileTwo.getBytes());
108+
}
109+
110+
@PostMapping(value = "/multiValueMultipartFileMap", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
111+
public String multiValueMultipartFileMap(@BeanParameter RequestParameterBean.ServletMultipartBean requestParameterBean) {
112+
MultiValueMap<String, MultipartFile> multiValueMultipartMap = requestParameterBean.getMultiValueMultipartMap();
113+
List<MultipartFile> files = multiValueMultipartMap.get("file");
114+
return files.stream().map(multipartFile -> {
115+
try {
116+
return new String(multipartFile.getBytes());
117+
} catch (IOException e) {
118+
throw new RuntimeException(e);
119+
}
120+
}).collect(Collectors.joining(", "));
121+
}
122+
123+
@PostMapping(value = "/partMap", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
124+
public String partMap(@BeanParameter RequestParameterBean.ServletMultipartBean requestParameterBean) throws Exception {
125+
Map<String, Part> partMap = requestParameterBean.getPartMap();
126+
Part fileOne = partMap.get("fileOne");
127+
Part fileTwo = partMap.get("fileTwo");
128+
return StreamUtils.copyToString(fileOne.getInputStream(), StandardCharsets.UTF_8)
129+
+ ", " + StreamUtils.copyToString(fileTwo.getInputStream(), StandardCharsets.UTF_8);
130+
}
131+
132+
@PostMapping(value = "/multiValuePartMap", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
133+
public String multiValuePartMap(@BeanParameter RequestParameterBean.ServletMultipartBean requestParameterBean) {
134+
MultiValueMap<String, Part> multiValuePartMap = requestParameterBean.getMultiValuePartMap();
135+
List<Part> files = multiValuePartMap.get("file");
136+
return files.stream().map(part -> {
137+
try {
138+
return StreamUtils.copyToString(part.getInputStream(), StandardCharsets.UTF_8);
139+
} catch (IOException e) {
140+
throw new RuntimeException(e);
141+
}
142+
}).collect(Collectors.joining(", "));
143+
}
144+
76145
@GetMapping(value = "/nested", produces = MediaType.TEXT_PLAIN_VALUE)
77146
public String nestedBeanParameter(@BeanParameter RequestParameterBean requestParameterBean) {
78147
return requestParameterBean.getNestedBean().getQueryParam();

0 commit comments

Comments
 (0)