Skip to content

Commit f0d149b

Browse files
committed
Polishing contribution
Closes gh-27830
1 parent caaf83b commit f0d149b

File tree

3 files changed

+25
-97
lines changed

3 files changed

+25
-97
lines changed

spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/MultipartControllerTests.java

Lines changed: 20 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -32,15 +32,16 @@
3232
import javax.servlet.http.Part;
3333

3434
import org.junit.jupiter.api.Test;
35+
import org.junit.jupiter.params.ParameterizedTest;
36+
import org.junit.jupiter.params.provider.ValueSource;
3537

36-
import org.springframework.http.MediaType;
3738
import org.springframework.mock.web.MockMultipartFile;
3839
import org.springframework.mock.web.MockPart;
3940
import org.springframework.stereotype.Controller;
4041
import org.springframework.test.web.servlet.MockMvc;
42+
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
4143
import org.springframework.ui.Model;
4244
import org.springframework.util.StreamUtils;
43-
import org.springframework.validation.BindException;
4445
import org.springframework.validation.BindingResult;
4546
import org.springframework.web.bind.annotation.RequestMapping;
4647
import org.springframework.web.bind.annotation.RequestMethod;
@@ -63,16 +64,20 @@
6364
*/
6465
public class MultipartControllerTests {
6566

66-
@Test
67-
public void multipartRequestWithSingleFile() throws Exception {
67+
@ParameterizedTest
68+
@ValueSource(strings = {"/multipartfile", "/part"})
69+
public void multipartRequestWithSingleFileOrPart(String url) throws Exception {
6870
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
69-
MockMultipartFile filePart = new MockMultipartFile("file", "orig", null, fileContent);
7071

7172
byte[] json = "{\"name\":\"yeeeah\"}".getBytes(StandardCharsets.UTF_8);
7273
MockMultipartFile jsonPart = new MockMultipartFile("json", "json", "application/json", json);
7374

75+
MockMultipartHttpServletRequestBuilder requestBuilder = (url.endsWith("file") ?
76+
multipart(url).file(new MockMultipartFile("file", "orig", null, fileContent)) :
77+
multipart(url).part(new MockPart("part", "orig", fileContent)));
78+
7479
standaloneSetup(new MultipartController()).build()
75-
.perform(multipart("/multipartfile").file(filePart).file(jsonPart))
80+
.perform(requestBuilder.file(jsonPart))
7681
.andExpect(status().isFound())
7782
.andExpect(model().attribute("fileContent", fileContent))
7883
.andExpect(model().attribute("jsonContent", Collections.singletonMap("name", "yeeeah")));
@@ -229,65 +234,16 @@ public void multipartRequestWithOptionalFileListNotPresent() throws Exception {
229234
}
230235

231236
@Test
232-
public void multipartRequestWithParts_resolvesMultipartFileArguments() throws Exception {
233-
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
234-
MockPart filePart = new MockPart("file", "orig", fileContent);
235-
236-
byte[] json = "{\"name\":\"yeeeah\"}".getBytes(StandardCharsets.UTF_8);
237-
MockPart jsonPart = new MockPart("json", json);
238-
jsonPart.getHeaders().setContentType(MediaType.APPLICATION_JSON);
239-
240-
standaloneSetup(new MultipartController()).build()
241-
.perform(multipart("/multipartfile").part(filePart).part(jsonPart))
242-
.andExpect(status().isFound())
243-
.andExpect(model().attribute("fileContent", fileContent))
244-
.andExpect(model().attribute("jsonContent", Collections.singletonMap("name", "yeeeah")));
245-
}
246-
247-
@Test
248-
public void multipartRequestWithParts_resolvesPartArguments() throws Exception {
237+
public void multipartRequestWithDataBindingToFile() throws Exception {
249238
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
250239
MockPart filePart = new MockPart("file", "orig", fileContent);
251240

252-
byte[] json = "{\"name\":\"yeeeah\"}".getBytes(StandardCharsets.UTF_8);
253-
MockPart jsonPart = new MockPart("json", json);
254-
jsonPart.getHeaders().setContentType(MediaType.APPLICATION_JSON);
255-
256241
standaloneSetup(new MultipartController()).build()
257-
.perform(multipart("/part").part(filePart).part(jsonPart))
258-
.andExpect(status().isFound())
259-
.andExpect(model().attribute("fileContent", fileContent))
260-
.andExpect(model().attribute("jsonContent", Collections.singletonMap("name", "yeeeah")));
261-
}
262-
263-
@Test
264-
public void multipartRequestWithParts_resolvesMultipartFileProperties() throws Exception {
265-
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
266-
MockPart filePart = new MockPart("file", "orig", fileContent);
267-
268-
standaloneSetup(new MultipartController()).build()
269-
.perform(multipart("/multipartfileproperty").part(filePart))
242+
.perform(multipart("/multipartfilebinding").part(filePart))
270243
.andExpect(status().isFound())
271244
.andExpect(model().attribute("fileContent", fileContent));
272245
}
273246

274-
@Test
275-
public void multipartRequestWithParts_cannotResolvePartProperties() throws Exception {
276-
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
277-
MockPart filePart = new MockPart("file", "orig", fileContent);
278-
279-
Exception exception = standaloneSetup(new MultipartController()).build()
280-
.perform(multipart("/partproperty").part(filePart))
281-
.andExpect(status().is4xxClientError())
282-
.andReturn()
283-
.getResolvedException();
284-
285-
assertThat(exception).isNotNull();
286-
assertThat(exception).isInstanceOf(BindException.class);
287-
assertThat(((BindException) exception).getFieldError("file"))
288-
.as("MultipartRequest would not bind Part properties.").isNotNull();
289-
}
290-
291247
@Test // SPR-13317
292248
public void multipartRequestWrapped() throws Exception {
293249
byte[] json = "{\"name\":\"yeeeah\"}".getBytes(StandardCharsets.UTF_8);
@@ -391,11 +347,11 @@ public String processOptionalFileList(@RequestParam Optional<List<MultipartFile>
391347
}
392348

393349
@RequestMapping(value = "/part", method = RequestMethod.POST)
394-
public String processPart(@RequestPart Part file,
350+
public String processPart(@RequestPart Part part,
395351
@RequestPart Map<String, String> json, Model model) throws IOException {
396352

397-
if (file != null) {
398-
byte[] content = StreamUtils.copyToByteArray(file.getInputStream());
353+
if (part != null) {
354+
byte[] content = StreamUtils.copyToByteArray(part.getInputStream());
399355
model.addAttribute("fileContent", content);
400356
}
401357
model.addAttribute("jsonContent", json);
@@ -409,9 +365,9 @@ public String processMultipart(@RequestPart Map<String, String> json, Model mode
409365
return "redirect:/index";
410366
}
411367

412-
@RequestMapping(value = "/multipartfileproperty", method = RequestMethod.POST)
413-
public String processMultipartFileBean(MultipartFileBean multipartFileBean, Model model, BindingResult bindingResult)
414-
throws IOException {
368+
@RequestMapping(value = "/multipartfilebinding", method = RequestMethod.POST)
369+
public String processMultipartFileBean(
370+
MultipartFileBean multipartFileBean, Model model, BindingResult bindingResult) throws IOException {
415371

416372
if (!bindingResult.hasErrors()) {
417373
MultipartFile file = multipartFileBean.getFile();
@@ -421,20 +377,6 @@ public String processMultipartFileBean(MultipartFileBean multipartFileBean, Mode
421377
}
422378
return "redirect:/index";
423379
}
424-
425-
@RequestMapping(value = "/partproperty", method = RequestMethod.POST)
426-
public String processPartBean(PartBean partBean, Model model, BindingResult bindingResult)
427-
throws IOException {
428-
429-
if (!bindingResult.hasErrors()) {
430-
Part file = partBean.getFile();
431-
if (file != null) {
432-
byte[] content = StreamUtils.copyToByteArray(file.getInputStream());
433-
model.addAttribute("fileContent", content);
434-
}
435-
}
436-
return "redirect:/index";
437-
}
438380
}
439381

440382
private static class MultipartFileBean {
@@ -450,18 +392,6 @@ public void setFile(MultipartFile file) {
450392
}
451393
}
452394

453-
private static class PartBean {
454-
455-
private Part file;
456-
457-
public Part getFile() {
458-
return file;
459-
}
460-
461-
public void setFile(Part file) {
462-
this.file = file;
463-
}
464-
}
465395

466396
private static class RequestWrappingFilter extends OncePerRequestFilter {
467397

spring-web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,8 @@ public ServletRequestDataBinder(@Nullable Object target, String objectName) {
104104
* HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property,
105105
* invoking a "setUploadedFile" setter method.
106106
* <p>The type of the target property for a multipart file can be MultipartFile,
107-
* Part, byte[], or String. The Part binding is only supported when the request
108-
* is not a MultipartRequest. The latter two receive the contents of the uploaded file;
109-
* all metadata like original file name, content type, etc are lost in those cases.
107+
* byte[], or String. Servlet Part binding is also supported when the
108+
* request has not been parsed to MultipartRequest via MultipartResolver.
110109
* @param request the request with parameters to bind (can be multipart)
111110
* @see org.springframework.web.multipart.MultipartHttpServletRequest
112111
* @see org.springframework.web.multipart.MultipartRequest

spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,9 @@ public WebRequestDataBinder(@Nullable Object target, String objectName) {
107107
* <p>Multipart files are bound via their parameter name, just like normal
108108
* HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property,
109109
* invoking a "setUploadedFile" setter method.
110-
* <p>The type of the target property for a multipart file can be Part, MultipartFile,
111-
* byte[], or String. The Part binding is only supported when the request
112-
* is not a MultipartRequest. The latter two receive the contents of the uploaded file;
113-
* all metadata like original file name, content type, etc are lost in those cases.
110+
* <p>The type of the target property for a multipart file can be MultipartFile,
111+
* byte[], or String. Servlet Part binding is also supported when the
112+
* request has not been parsed to MultipartRequest via MultipartResolver.
114113
* @param request the request with parameters to bind (can be multipart)
115114
* @see org.springframework.web.multipart.MultipartRequest
116115
* @see org.springframework.web.multipart.MultipartFile

0 commit comments

Comments
 (0)