Skip to content

Commit e91ce23

Browse files
bclozelrstoyanchev
authored andcommitted
Add javax.servlet.http.Part support for data binding
Prior to this commit, Multipart databinding would only support MultiPartFile databinding using commons-multipart. Now the WebRequestDataBinder supports Part and List<Part> databinding for Servlet 3.0 compliant containers. Issue: SPR-10591
1 parent 41e411a commit e91ce23

File tree

2 files changed

+256
-4
lines changed

2 files changed

+256
-4
lines changed

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

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 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.
@@ -16,11 +16,20 @@
1616

1717
package org.springframework.web.bind.support;
1818

19+
import java.io.IOException;
20+
21+
import javax.servlet.ServletException;
22+
import javax.servlet.http.HttpServletRequest;
23+
import javax.servlet.http.Part;
24+
1925
import org.springframework.beans.MutablePropertyValues;
26+
import org.springframework.util.ClassUtils;
27+
import org.springframework.util.StringUtils;
2028
import org.springframework.validation.BindException;
2129
import org.springframework.web.bind.WebDataBinder;
2230
import org.springframework.web.context.request.NativeWebRequest;
2331
import org.springframework.web.context.request.WebRequest;
32+
import org.springframework.web.multipart.MultipartException;
2433
import org.springframework.web.multipart.MultipartRequest;
2534

2635
/**
@@ -50,6 +59,7 @@
5059
* ...</pre>
5160
*
5261
* @author Juergen Hoeller
62+
* @author Brian Clozel
5363
* @since 2.5.2
5464
* @see #bind(org.springframework.web.context.request.WebRequest)
5565
* @see #registerCustomEditor
@@ -59,6 +69,7 @@
5969
*/
6070
public class WebRequestDataBinder extends WebDataBinder {
6171

72+
6273
/**
6374
* Create a new WebRequestDataBinder instance, with default object name.
6475
* @param target the target object to bind onto (or {@code null}
@@ -89,22 +100,27 @@ public WebRequestDataBinder(Object target, String objectName) {
89100
* <p>Multipart files are bound via their parameter name, just like normal
90101
* HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property,
91102
* invoking a "setUploadedFile" setter method.
92-
* <p>The type of the target property for a multipart file can be MultipartFile,
103+
* <p>The type of the target property for a multipart file can be Part, MultipartFile,
93104
* byte[], or String. The latter two receive the contents of the uploaded file;
94105
* all metadata like original file name, content type, etc are lost in those cases.
95106
* @param request request with parameters to bind (can be multipart)
96107
* @see org.springframework.web.multipart.MultipartRequest
97108
* @see org.springframework.web.multipart.MultipartFile
98-
* @see #bindMultipartFiles
109+
* @see javax.servlet.http.Part
99110
* @see #bind(org.springframework.beans.PropertyValues)
100111
*/
101112
public void bind(WebRequest request) {
102113
MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap());
103-
if (request instanceof NativeWebRequest) {
114+
115+
if(isMultipartRequest(request) && (request instanceof NativeWebRequest)) {
104116
MultipartRequest multipartRequest = ((NativeWebRequest) request).getNativeRequest(MultipartRequest.class);
105117
if (multipartRequest != null) {
106118
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
107119
}
120+
else if (ClassUtils.hasMethod(HttpServletRequest.class, "getParts")) {
121+
HttpServletRequest serlvetRequest = ((NativeWebRequest) request).getNativeRequest(HttpServletRequest.class);
122+
new Servlet3MultipartHelper().bindParts(serlvetRequest, mpvs);
123+
}
108124
}
109125
doBind(mpvs);
110126
}
@@ -121,4 +137,37 @@ public void closeNoCatch() throws BindException {
121137
}
122138
}
123139

140+
/**
141+
* Check if the request is a multipart request (by checking its Content-Type header).
142+
*
143+
* @param request request with parameters to bind
144+
*/
145+
private boolean isMultipartRequest(WebRequest request) {
146+
String contentType = request.getHeader("Content-Type");
147+
return ((contentType != null) && StringUtils.startsWithIgnoreCase(contentType, "multipart"));
148+
}
149+
150+
151+
/**
152+
* Encapsulate Part binding code for Servlet 3.0+ only containers.
153+
* @see javax.servlet.http.Part
154+
*/
155+
private static class Servlet3MultipartHelper {
156+
157+
public void bindParts(HttpServletRequest request, MutablePropertyValues mpvs) {
158+
try {
159+
for(Part part : request.getParts()) {
160+
mpvs.add(part.getName(), part);
161+
}
162+
}
163+
catch (IOException ex) {
164+
throw new MultipartException("Failed to get request parts", ex);
165+
}
166+
catch(ServletException ex) {
167+
throw new MultipartException("Failed to get request parts", ex);
168+
}
169+
}
170+
171+
}
172+
124173
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.bind.support;
18+
19+
import java.io.IOException;
20+
import java.util.List;
21+
22+
import javax.servlet.ServletException;
23+
import javax.servlet.http.HttpServlet;
24+
import javax.servlet.http.HttpServletRequest;
25+
import javax.servlet.http.HttpServletResponse;
26+
import javax.servlet.http.Part;
27+
28+
import org.eclipse.jetty.server.Server;
29+
import org.eclipse.jetty.servlet.ServletContextHandler;
30+
import org.eclipse.jetty.servlet.ServletHolder;
31+
import org.junit.AfterClass;
32+
import org.junit.Before;
33+
import org.junit.BeforeClass;
34+
import org.junit.Test;
35+
import org.springframework.core.io.ClassPathResource;
36+
import org.springframework.core.io.Resource;
37+
import org.springframework.http.MediaType;
38+
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
39+
import org.springframework.mock.web.test.MockMultipartFile;
40+
import org.springframework.util.LinkedMultiValueMap;
41+
import org.springframework.util.MultiValueMap;
42+
import org.springframework.util.SocketUtils;
43+
import org.springframework.web.client.RestTemplate;
44+
import org.springframework.web.context.request.ServletWebRequest;
45+
46+
import static org.junit.Assert.*;
47+
48+
49+
/**
50+
* @author Brian Clozel
51+
*/
52+
public class WebRequestDataBinderIntegrationTests {
53+
54+
protected static String baseUrl;
55+
56+
protected static MediaType contentType;
57+
58+
private static Server jettyServer;
59+
60+
private RestTemplate template;
61+
62+
private static PartsServlet partsServlet;
63+
64+
private static PartListServlet partListServlet;
65+
66+
67+
@Before
68+
public void createTemplate() {
69+
template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
70+
}
71+
72+
@BeforeClass
73+
public static void startJettyServer() throws Exception {
74+
int port = SocketUtils.findAvailableTcpPort();
75+
jettyServer = new Server(port);
76+
baseUrl = "http://localhost:" + port;
77+
ServletContextHandler handler = new ServletContextHandler();
78+
79+
partsServlet = new PartsServlet();
80+
partListServlet = new PartListServlet();
81+
82+
handler.addServlet(new ServletHolder(partsServlet), "/parts");
83+
handler.addServlet(new ServletHolder(partListServlet), "/partlist");
84+
jettyServer.setHandler(handler);
85+
jettyServer.start();
86+
}
87+
88+
@AfterClass
89+
public static void stopJettyServer() throws Exception {
90+
if (jettyServer != null) {
91+
jettyServer.stop();
92+
}
93+
}
94+
95+
96+
@Test
97+
public void testPartsBinding() {
98+
99+
PartsBean bean = new PartsBean();
100+
partsServlet.setBean(bean);
101+
102+
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
103+
MockMultipartFile firstPart = new MockMultipartFile("fileName", "aValue".getBytes());
104+
parts.add("firstPart", firstPart);
105+
parts.add("secondPart", "secondValue");
106+
107+
template.postForLocation(baseUrl + "/parts", parts);
108+
109+
assertNotNull(bean.getFirstPart());
110+
assertNotNull(bean.getSecondPart());
111+
}
112+
113+
@Test
114+
public void testPartListBinding() {
115+
116+
PartListBean bean = new PartListBean();
117+
partListServlet.setBean(bean);
118+
119+
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
120+
parts.add("partList", "first value");
121+
parts.add("partList", "second value");
122+
Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg");
123+
parts.add("partList", logo);
124+
125+
template.postForLocation(baseUrl + "/partlist", parts);
126+
127+
assertNotNull(bean.getPartList());
128+
assertEquals(parts.size(), bean.getPartList().size());
129+
}
130+
131+
132+
@SuppressWarnings("serial")
133+
private abstract static class AbstractStandardMultipartServlet<T> extends HttpServlet {
134+
135+
private T bean;
136+
137+
@Override
138+
public void service(HttpServletRequest request, HttpServletResponse response) throws
139+
ServletException, IOException {
140+
141+
WebRequestDataBinder binder = new WebRequestDataBinder(bean);
142+
ServletWebRequest webRequest = new ServletWebRequest(request, response);
143+
144+
binder.bind(webRequest);
145+
146+
response.setStatus(HttpServletResponse.SC_OK);
147+
}
148+
149+
public void setBean(T bean) {
150+
this.bean = bean;
151+
}
152+
}
153+
154+
private static class PartsBean {
155+
156+
public Part firstPart;
157+
158+
public Part secondPart;
159+
160+
public Part getFirstPart() {
161+
return firstPart;
162+
}
163+
164+
@SuppressWarnings("unused")
165+
public void setFirstPart(Part firstPart) {
166+
this.firstPart = firstPart;
167+
}
168+
169+
public Part getSecondPart() {
170+
return secondPart;
171+
}
172+
173+
@SuppressWarnings("unused")
174+
public void setSecondPart(Part secondPart) {
175+
this.secondPart = secondPart;
176+
}
177+
}
178+
179+
@SuppressWarnings("serial")
180+
private static class PartsServlet extends AbstractStandardMultipartServlet<PartsBean> {
181+
182+
}
183+
184+
private static class PartListBean {
185+
186+
public List<Part> partList;
187+
188+
public List<Part> getPartList() {
189+
return partList;
190+
}
191+
192+
@SuppressWarnings("unused")
193+
public void setPartList(List<Part> partList) {
194+
this.partList = partList;
195+
}
196+
}
197+
198+
@SuppressWarnings("serial")
199+
private static class PartListServlet extends AbstractStandardMultipartServlet<PartListBean> {
200+
201+
}
202+
203+
}

0 commit comments

Comments
 (0)