View Javadoc
1   /*
2    * Copyright 2002-2014 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.servlet.mvc.method.annotation;
18  
19  import java.io.IOException;
20  import java.lang.reflect.Method;
21  import java.util.Arrays;
22  import java.util.Collections;
23  import java.util.List;
24  import javax.servlet.http.Part;
25  import javax.validation.Valid;
26  import javax.validation.constraints.NotNull;
27  
28  import org.hamcrest.Matchers;
29  import org.junit.Before;
30  import org.junit.Test;
31  
32  import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
33  import org.springframework.core.MethodParameter;
34  import org.springframework.http.MediaType;
35  import org.springframework.http.converter.HttpMessageConverter;
36  import org.springframework.mock.web.test.MockHttpServletRequest;
37  import org.springframework.mock.web.test.MockHttpServletResponse;
38  import org.springframework.mock.web.test.MockMultipartFile;
39  import org.springframework.mock.web.test.MockMultipartHttpServletRequest;
40  import org.springframework.mock.web.test.MockPart;
41  import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
42  import org.springframework.web.bind.MethodArgumentNotValidException;
43  import org.springframework.web.bind.WebDataBinder;
44  import org.springframework.web.bind.annotation.RequestParam;
45  import org.springframework.web.bind.annotation.RequestPart;
46  import org.springframework.web.bind.support.WebDataBinderFactory;
47  import org.springframework.web.context.request.NativeWebRequest;
48  import org.springframework.web.context.request.ServletWebRequest;
49  import org.springframework.web.method.support.ModelAndViewContainer;
50  import org.springframework.web.multipart.MultipartException;
51  import org.springframework.web.multipart.MultipartFile;
52  import org.springframework.web.multipart.support.MissingServletRequestPartException;
53  import org.springframework.web.multipart.support.RequestPartServletServerHttpRequest;
54  
55  import static org.junit.Assert.*;
56  import static org.mockito.BDDMockito.*;
57  
58  /**
59   * Test fixture with {@link RequestPartMethodArgumentResolver} and mock {@link HttpMessageConverter}.
60   *
61   * @author Rossen Stoyanchev
62   * @author Brian Clozel
63   */
64  public class RequestPartMethodArgumentResolverTests {
65  
66  	private RequestPartMethodArgumentResolver resolver;
67  
68  	private HttpMessageConverter<SimpleBean> messageConverter;
69  
70  	private MultipartFile multipartFile1;
71  	private MultipartFile multipartFile2;
72  
73  	private MethodParameter paramRequestPart;
74  	private MethodParameter paramNamedRequestPart;
75  	private MethodParameter paramValidRequestPart;
76  	private MethodParameter paramMultipartFile;
77  	private MethodParameter paramMultipartFileList;
78  	private MethodParameter paramMultipartFileArray;
79  	private MethodParameter paramInt;
80  	private MethodParameter paramMultipartFileNotAnnot;
81  	private MethodParameter paramPart;
82  	private MethodParameter paramPartList;
83  	private MethodParameter paramPartArray;
84  	private MethodParameter paramRequestParamAnnot;
85  
86  	private NativeWebRequest webRequest;
87  
88  	private MockMultipartHttpServletRequest multipartRequest;
89  
90  	private MockHttpServletResponse servletResponse;
91  
92  	@SuppressWarnings("unchecked")
93  	@Before
94  	public void setUp() throws Exception {
95  
96  		Method method = getClass().getMethod("handle", SimpleBean.class, SimpleBean.class,
97  				SimpleBean.class, MultipartFile.class, List.class, MultipartFile[].class,
98  				Integer.TYPE, MultipartFile.class, Part.class, List.class,
99  				Part[].class, MultipartFile.class);
100 
101 		paramRequestPart = new MethodParameter(method, 0);
102 		paramRequestPart.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
103 		paramNamedRequestPart = new MethodParameter(method, 1);
104 		paramValidRequestPart = new MethodParameter(method, 2);
105 		paramMultipartFile = new MethodParameter(method, 3);
106 		paramMultipartFileList = new MethodParameter(method, 4);
107 		paramMultipartFileArray = new MethodParameter(method, 5);
108 		paramInt = new MethodParameter(method, 6);
109 		paramMultipartFileNotAnnot = new MethodParameter(method, 7);
110 		paramMultipartFileNotAnnot.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
111 		paramPart = new MethodParameter(method, 8);
112 		paramPart.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
113 		paramPartList = new MethodParameter(method, 9);
114 		paramPartArray = new MethodParameter(method, 10);
115 		paramRequestParamAnnot = new MethodParameter(method, 11);
116 
117 		messageConverter = mock(HttpMessageConverter.class);
118 		given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
119 
120 		resolver = new RequestPartMethodArgumentResolver(Collections.<HttpMessageConverter<?>>singletonList(messageConverter));
121 		reset(messageConverter);
122 
123 		multipartFile1 = new MockMultipartFile("requestPart", "", "text/plain", (byte[]) null);
124 		multipartFile2 = new MockMultipartFile("requestPart", "", "text/plain", (byte[]) null);
125 		multipartRequest = new MockMultipartHttpServletRequest();
126 		multipartRequest.addFile(multipartFile1);
127 		multipartRequest.addFile(multipartFile2);
128 		servletResponse = new MockHttpServletResponse();
129 		webRequest = new ServletWebRequest(multipartRequest, servletResponse);
130 	}
131 
132 	@Test
133 	public void supportsParameter() {
134 		assertTrue("RequestPart parameter not supported", resolver.supportsParameter(paramRequestPart));
135 		assertTrue("MultipartFile parameter not supported", resolver.supportsParameter(paramMultipartFileNotAnnot));
136 		assertTrue("Part parameter not supported", resolver.supportsParameter(paramPart));
137 		assertTrue("List<Part> parameter not supported", resolver.supportsParameter(paramPartList));
138 		assertTrue("Part[] parameter not supported", resolver.supportsParameter(paramPartArray));
139 		assertTrue("MultipartFile parameter not supported", resolver.supportsParameter(paramMultipartFile));
140 		assertTrue("List<MultipartFile> parameter not supported", resolver.supportsParameter(paramMultipartFileList));
141 		assertTrue("MultipartFile[] parameter not supported", resolver.supportsParameter(paramMultipartFileArray));
142 		assertFalse("non-RequestPart parameter supported", resolver.supportsParameter(paramInt));
143 		assertFalse("@RequestParam args not supported", resolver.supportsParameter(paramRequestParamAnnot));
144 	}
145 
146 	@Test
147 	public void resolveMultipartFile() throws Exception {
148 		Object actual = resolver.resolveArgument(paramMultipartFile, null, webRequest, null);
149 		assertNotNull(actual);
150 		assertSame(multipartFile1, actual);
151 	}
152 
153 	@Test
154 	public void resolveMultipartFileList() throws Exception {
155 		Object actual = resolver.resolveArgument(paramMultipartFileList, null, webRequest, null);
156 		assertNotNull(actual);
157 		assertTrue(actual instanceof List);
158 		assertEquals(Arrays.asList(multipartFile1, multipartFile2), actual);
159 	}
160 
161 	@Test
162 	public void resolveMultipartFileArray() throws Exception {
163 		Object actual = resolver.resolveArgument(paramMultipartFileArray, null, webRequest, null);
164 		assertNotNull(actual);
165 		assertTrue(actual instanceof MultipartFile[]);
166 		MultipartFile[] parts = (MultipartFile[]) actual;
167 		assertEquals(parts[0], multipartFile1);
168 		assertEquals(parts[1], multipartFile2);
169 	}
170 
171 	@Test
172 	public void resolveMultipartFileNotAnnotArgument() throws Exception {
173 		MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
174 		MultipartFile expected = new MockMultipartFile("multipartFileNotAnnot", "Hello World".getBytes());
175 		request.addFile(expected);
176 		webRequest = new ServletWebRequest(request);
177 
178 		Object result = resolver.resolveArgument(paramMultipartFileNotAnnot, null, webRequest, null);
179 
180 		assertTrue(result instanceof MultipartFile);
181 		assertEquals("Invalid result", expected, result);
182 	}
183 
184 	@Test
185 	public void resolvePartArgument() throws Exception {
186 		MockPart expected = new MockPart("part", "Hello World".getBytes());
187 		MockHttpServletRequest request = new MockHttpServletRequest();
188 		request.setMethod("POST");
189 		request.setContentType("multipart/form-data");
190 		request.addPart(expected);
191 		webRequest = new ServletWebRequest(request);
192 
193 		Object result = resolver.resolveArgument(paramPart, null, webRequest, null);
194 
195 		assertTrue(result instanceof Part);
196 		assertEquals("Invalid result", expected, result);
197 	}
198 
199 	@Test
200 	public void resolvePartListArgument() throws Exception {
201 		MockPart part1 = new MockPart("requestPart1", "Hello World 1".getBytes());
202 		MockPart part2 = new MockPart("requestPart2", "Hello World 2".getBytes());
203 		MockHttpServletRequest request = new MockHttpServletRequest();
204 		request.setMethod("POST");
205 		request.setContentType("multipart/form-data");
206 		request.addPart(part1);
207 		request.addPart(part2);
208 		webRequest = new ServletWebRequest(request);
209 
210 		Object result = resolver.resolveArgument(paramPartList, null, webRequest, null);
211 
212 		assertTrue(result instanceof List);
213 		assertEquals(Arrays.asList(part1, part2), result);
214 	}
215 
216 	@Test
217 	public void resolvePartArrayArgument() throws Exception {
218 		MockPart part1 = new MockPart("requestPart1", "Hello World 1".getBytes());
219 		MockPart part2 = new MockPart("requestPart2", "Hello World 2".getBytes());
220 		MockHttpServletRequest request = new MockHttpServletRequest();
221 		request.setMethod("POST");
222 		request.setContentType("multipart/form-data");
223 		request.addPart(part1);
224 		request.addPart(part2);
225 		webRequest = new ServletWebRequest(request);
226 
227 		Object result = resolver.resolveArgument(paramPartArray, null, webRequest, null);
228 
229 		assertTrue(result instanceof Part[]);
230 		Part[] parts = (Part[]) result;
231 		assertThat(parts, Matchers.arrayWithSize(2));
232 		assertEquals(parts[0], part1);
233 		assertEquals(parts[1], part2);
234 	}
235 
236 	@Test
237 	public void resolveRequestPart() throws Exception {
238 		testResolveArgument(new SimpleBean("foo"), paramRequestPart);
239 	}
240 
241 	@Test
242 	public void resolveNamedRequestPart() throws Exception {
243 		testResolveArgument(new SimpleBean("foo"), paramNamedRequestPart);
244 	}
245 
246 	@Test
247 	public void resolveRequestPartNotValid() throws Exception {
248 		try {
249 			testResolveArgument(new SimpleBean(null), paramValidRequestPart);
250 			fail("Expected exception");
251 		} catch (MethodArgumentNotValidException e) {
252 			assertEquals("requestPart", e.getBindingResult().getObjectName());
253 			assertEquals(1, e.getBindingResult().getErrorCount());
254 			assertNotNull(e.getBindingResult().getFieldError("name"));
255 		}
256 	}
257 
258 	@Test
259 	public void resolveRequestPartValid() throws Exception {
260 		testResolveArgument(new SimpleBean("foo"), paramNamedRequestPart);
261 	}
262 
263 	@Test
264 	public void resolveRequestPartRequired() throws Exception {
265 		try {
266 			testResolveArgument(null, paramValidRequestPart);
267 			fail("Expected exception");
268 		} catch (MissingServletRequestPartException e) {
269 			assertEquals("requestPart", e.getRequestPartName());
270 		}
271 	}
272 
273 	@Test
274 	public void resolveRequestPartNotRequired() throws Exception {
275 		testResolveArgument(new SimpleBean("foo"), paramValidRequestPart);
276 	}
277 
278 	@Test(expected=MultipartException.class)
279 	public void isMultipartRequest() throws Exception {
280 		MockHttpServletRequest request = new MockHttpServletRequest();
281 		resolver.resolveArgument(paramMultipartFile, new ModelAndViewContainer(), new ServletWebRequest(request), null);
282 		fail("Expected exception");
283 	}
284 
285 	// SPR-9079
286 
287 	@Test
288 	public void isMultipartRequestPut() throws Exception {
289 		this.multipartRequest.setMethod("PUT");
290 		Object actual = resolver.resolveArgument(paramMultipartFile, null, webRequest, null);
291 		assertNotNull(actual);
292 		assertSame(multipartFile1, actual);
293 	}
294 
295 	private void testResolveArgument(SimpleBean argValue, MethodParameter parameter) throws IOException, Exception {
296 		MediaType contentType = MediaType.TEXT_PLAIN;
297 
298 		given(messageConverter.canRead(SimpleBean.class, contentType)).willReturn(true);
299 		given(messageConverter.read(eq(SimpleBean.class), isA(RequestPartServletServerHttpRequest.class))).willReturn(argValue);
300 
301 		ModelAndViewContainer mavContainer = new ModelAndViewContainer();
302 		Object actualValue = resolver.resolveArgument(parameter, mavContainer, webRequest, new ValidatingBinderFactory());
303 
304 		assertEquals("Invalid argument value", argValue, actualValue);
305 		assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled());
306 	}
307 
308 	private static class SimpleBean {
309 
310 		@NotNull
311 		private final String name;
312 
313 		public SimpleBean(String name) {
314 			this.name = name;
315 		}
316 
317 		@SuppressWarnings("unused")
318 		public String getName() {
319 			return name;
320 		}
321 	}
322 
323 	private final class ValidatingBinderFactory implements WebDataBinderFactory {
324 		@Override
325 		public WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception {
326 			LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
327 			validator.afterPropertiesSet();
328 			WebDataBinder dataBinder = new WebDataBinder(target, objectName);
329 			dataBinder.setValidator(validator);
330 			return dataBinder;
331 		}
332 	}
333 
334 	public void handle(@RequestPart SimpleBean requestPart,
335 					   @RequestPart(value="requestPart", required=false) SimpleBean namedRequestPart,
336 					   @Valid @RequestPart("requestPart") SimpleBean validRequestPart,
337 					   @RequestPart("requestPart") MultipartFile multipartFile,
338 					   @RequestPart("requestPart") List<MultipartFile> multipartFileList,
339 					   @RequestPart("requestPart") MultipartFile[] multipartFileArray,
340 					   int i,
341 					   MultipartFile multipartFileNotAnnot,
342 					   Part part,
343 					   @RequestPart("part") List<Part> partList,
344 					   @RequestPart("part") Part[] partArray,
345 					   @RequestParam MultipartFile requestParamAnnot) {
346 	}
347 
348 }