1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.web.servlet.mvc.method.annotation;
18
19 import java.io.Serializable;
20 import java.lang.reflect.Method;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.List;
24
25 import com.fasterxml.jackson.annotation.JsonView;
26 import org.junit.Before;
27 import org.junit.Test;
28
29 import org.springframework.aop.framework.ProxyFactory;
30 import org.springframework.aop.target.SingletonTargetSource;
31 import org.springframework.core.MethodParameter;
32 import org.springframework.http.HttpStatus;
33 import org.springframework.http.MediaType;
34 import org.springframework.http.ResponseEntity;
35 import org.springframework.http.converter.ByteArrayHttpMessageConverter;
36 import org.springframework.http.converter.HttpMessageConverter;
37 import org.springframework.http.converter.HttpMessageNotReadableException;
38 import org.springframework.http.converter.StringHttpMessageConverter;
39 import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
40 import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
41 import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
42 import org.springframework.mock.web.test.MockHttpServletRequest;
43 import org.springframework.mock.web.test.MockHttpServletResponse;
44 import org.springframework.util.MultiValueMap;
45 import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
46 import org.springframework.web.bind.WebDataBinder;
47 import org.springframework.web.bind.annotation.RequestBody;
48 import org.springframework.web.bind.annotation.RequestMapping;
49 import org.springframework.web.bind.annotation.ResponseBody;
50 import org.springframework.web.bind.annotation.RestController;
51 import org.springframework.web.bind.support.WebDataBinderFactory;
52 import org.springframework.web.context.request.NativeWebRequest;
53 import org.springframework.web.context.request.ServletWebRequest;
54 import org.springframework.web.method.HandlerMethod;
55 import org.springframework.web.method.support.ModelAndViewContainer;
56 import org.springframework.web.servlet.ModelAndView;
57 import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
58
59 import static org.junit.Assert.*;
60
61
62
63
64
65
66
67
68
69
70 public class RequestResponseBodyMethodProcessorTests {
71
72 private MethodParameter paramGenericList;
73 private MethodParameter paramSimpleBean;
74 private MethodParameter paramMultiValueMap;
75 private MethodParameter paramString;
76 private MethodParameter returnTypeString;
77
78 private ModelAndViewContainer mavContainer;
79
80 private NativeWebRequest webRequest;
81
82 private MockHttpServletRequest servletRequest;
83
84 private MockHttpServletResponse servletResponse;
85
86 private ValidatingBinderFactory binderFactory;
87
88
89 @Before
90 public void setUp() throws Exception {
91 Method method = getClass().getMethod("handle", List.class, SimpleBean.class, MultiValueMap.class, String.class);
92
93 paramGenericList = new MethodParameter(method, 0);
94 paramSimpleBean = new MethodParameter(method, 1);
95 paramMultiValueMap = new MethodParameter(method, 2);
96 paramString = new MethodParameter(method, 3);
97 returnTypeString = new MethodParameter(method, -1);
98
99 mavContainer = new ModelAndViewContainer();
100
101 servletRequest = new MockHttpServletRequest();
102 servletResponse = new MockHttpServletResponse();
103 webRequest = new ServletWebRequest(servletRequest, servletResponse);
104
105 this.binderFactory = new ValidatingBinderFactory();
106 }
107
108 @Test
109 public void resolveArgumentParameterizedType() throws Exception {
110 String content = "[{\"name\" : \"Jad\"}, {\"name\" : \"Robert\"}]";
111 this.servletRequest.setContent(content.getBytes("UTF-8"));
112 this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
113
114 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
115 converters.add(new MappingJackson2HttpMessageConverter());
116 RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
117
118 @SuppressWarnings("unchecked")
119 List<SimpleBean> result = (List<SimpleBean>) processor.resolveArgument(
120 paramGenericList, mavContainer, webRequest, binderFactory);
121
122 assertNotNull(result);
123 assertEquals("Jad", result.get(0).getName());
124 assertEquals("Robert", result.get(1).getName());
125 }
126
127 @Test
128 public void resolveArgumentRawTypeFromParameterizedType() throws Exception {
129 String content = "fruit=apple&vegetable=kale";
130 this.servletRequest.setContent(content.getBytes("UTF-8"));
131 this.servletRequest.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
132
133 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
134 converters.add(new AllEncompassingFormHttpMessageConverter());
135 RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
136
137 @SuppressWarnings("unchecked")
138 MultiValueMap<String, String> result = (MultiValueMap<String, String>) processor.resolveArgument(
139 paramMultiValueMap, mavContainer, webRequest, binderFactory);
140
141 assertNotNull(result);
142 assertEquals("apple", result.getFirst("fruit"));
143 assertEquals("kale", result.getFirst("vegetable"));
144 }
145
146 @Test
147 public void resolveArgumentClassJson() throws Exception {
148 String content = "{\"name\" : \"Jad\"}";
149 this.servletRequest.setContent(content.getBytes("UTF-8"));
150 this.servletRequest.setContentType("application/json");
151
152 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
153 converters.add(new MappingJackson2HttpMessageConverter());
154 RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
155
156 SimpleBean result = (SimpleBean) processor.resolveArgument(
157 paramSimpleBean, mavContainer, webRequest, binderFactory);
158
159 assertNotNull(result);
160 assertEquals("Jad", result.getName());
161 }
162
163 @Test
164 public void resolveArgumentClassString() throws Exception {
165 String content = "foobarbaz";
166 this.servletRequest.setContent(content.getBytes("UTF-8"));
167 this.servletRequest.setContentType("application/json");
168
169 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
170 converters.add(new StringHttpMessageConverter());
171 RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
172
173 String result = (String) processor.resolveArgument(
174 paramString, mavContainer, webRequest, binderFactory);
175
176 assertNotNull(result);
177 assertEquals("foobarbaz", result);
178 }
179
180 @Test(expected = HttpMessageNotReadableException.class)
181 public void resolveArgumentRequiredNoContent() throws Exception {
182 this.servletRequest.setContent(new byte[0]);
183 this.servletRequest.setContentType("text/plain");
184 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
185 converters.add(new StringHttpMessageConverter());
186 RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
187 processor.resolveArgument(paramString, mavContainer, webRequest, binderFactory);
188 }
189
190 @Test
191 public void resolveArgumentTypeVariable() throws Exception {
192 Method method = MyParameterizedController.class.getMethod("handleDto", Identifiable.class);
193 HandlerMethod handlerMethod = new HandlerMethod(new MySimpleParameterizedController(), method);
194 MethodParameter methodParam = handlerMethod.getMethodParameters()[0];
195
196 String content = "{\"name\" : \"Jad\"}";
197 this.servletRequest.setContent(content.getBytes("UTF-8"));
198 this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
199
200 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
201 converters.add(new MappingJackson2HttpMessageConverter());
202 RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
203
204 SimpleBean result = (SimpleBean) processor.resolveArgument(methodParam, mavContainer, webRequest, binderFactory);
205
206 assertNotNull(result);
207 assertEquals("Jad", result.getName());
208 }
209
210 @Test
211 public void resolveArgumentTypeVariableWithNonGenericConverter() throws Exception {
212 Method method = MyParameterizedController.class.getMethod("handleDto", Identifiable.class);
213 HandlerMethod handlerMethod = new HandlerMethod(new MySimpleParameterizedController(), method);
214 MethodParameter methodParam = handlerMethod.getMethodParameters()[0];
215
216 String content = "{\"name\" : \"Jad\"}";
217 this.servletRequest.setContent(content.getBytes("UTF-8"));
218 this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
219
220 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
221 HttpMessageConverter target = new MappingJackson2HttpMessageConverter();
222 HttpMessageConverter proxy = ProxyFactory.getProxy(HttpMessageConverter.class, new SingletonTargetSource(target));
223 converters.add(proxy);
224 RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
225
226 SimpleBean result = (SimpleBean) processor.resolveArgument(methodParam, mavContainer, webRequest, binderFactory);
227
228 assertNotNull(result);
229 assertEquals("Jad", result.getName());
230 }
231
232 @Test
233 public void handleReturnValueSortByQuality() throws Exception {
234 this.servletRequest.addHeader("Accept", "text/plain; q=0.5, application/json");
235
236 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
237 converters.add(new MappingJackson2HttpMessageConverter());
238 converters.add(new StringHttpMessageConverter());
239 RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
240
241 processor.writeWithMessageConverters("Foo", returnTypeString, webRequest);
242
243 assertEquals("application/json;charset=UTF-8", servletResponse.getHeader("Content-Type"));
244 }
245
246 @Test
247 public void handleReturnValueString() throws Exception {
248 List<HttpMessageConverter<?>>converters = new ArrayList<HttpMessageConverter<?>>();
249 converters.add(new ByteArrayHttpMessageConverter());
250 converters.add(new StringHttpMessageConverter());
251
252 RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
253 processor.handleReturnValue("Foo", returnTypeString, mavContainer, webRequest);
254
255 assertEquals("text/plain;charset=ISO-8859-1", servletResponse.getHeader("Content-Type"));
256 assertEquals("Foo", servletResponse.getContentAsString());
257 }
258
259 @Test
260 public void handleReturnValueStringAcceptCharset() throws Exception {
261 this.servletRequest.addHeader("Accept", "text/plain;charset=UTF-8");
262
263 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
264 converters.add(new ByteArrayHttpMessageConverter());
265 converters.add(new StringHttpMessageConverter());
266 RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
267
268 processor.writeWithMessageConverters("Foo", returnTypeString, webRequest);
269
270 assertEquals("text/plain;charset=UTF-8", servletResponse.getHeader("Content-Type"));
271 }
272
273 @Test
274 public void supportsReturnTypeResponseBodyOnType() throws Exception {
275 Method method = ResponseBodyController.class.getMethod("handle");
276 MethodParameter returnType = new MethodParameter(method, -1);
277
278 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
279 converters.add(new StringHttpMessageConverter());
280
281 RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
282
283 assertTrue("Failed to recognize type-level @ResponseBody", processor.supportsReturnType(returnType));
284 }
285
286 @Test
287 public void supportsReturnTypeRestController() throws Exception {
288 Method method = TestRestController.class.getMethod("handle");
289 MethodParameter returnType = new MethodParameter(method, -1);
290
291 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
292 converters.add(new StringHttpMessageConverter());
293
294 RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
295
296 assertTrue("Failed to recognize type-level @RestController", processor.supportsReturnType(returnType));
297 }
298
299 @Test
300 public void jacksonJsonViewWithResponseBodyAndJsonMessageConverter() throws Exception {
301 Method method = JacksonViewController.class.getMethod("handleResponseBody");
302 HandlerMethod handlerMethod = new HandlerMethod(new JacksonViewController(), method);
303 MethodParameter methodReturnType = handlerMethod.getReturnType();
304
305 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
306 converters.add(new MappingJackson2HttpMessageConverter());
307
308 RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
309 converters, null, Arrays.asList(new JsonViewResponseBodyAdvice()));
310
311 Object returnValue = new JacksonViewController().handleResponseBody();
312 processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
313
314 String content = this.servletResponse.getContentAsString();
315 assertFalse(content.contains("\"withView1\":\"with\""));
316 assertTrue(content.contains("\"withView2\":\"with\""));
317 assertFalse(content.contains("\"withoutView\":\"without\""));
318 }
319
320 @Test
321 public void jacksonJsonViewWithResponseEntityAndJsonMessageConverter() throws Exception {
322 Method method = JacksonViewController.class.getMethod("handleResponseEntity");
323 HandlerMethod handlerMethod = new HandlerMethod(new JacksonViewController(), method);
324 MethodParameter methodReturnType = handlerMethod.getReturnType();
325
326 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
327 converters.add(new MappingJackson2HttpMessageConverter());
328
329 HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
330 converters, null, Arrays.asList(new JsonViewResponseBodyAdvice()));
331
332 Object returnValue = new JacksonViewController().handleResponseEntity();
333 processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
334
335 String content = this.servletResponse.getContentAsString();
336 assertFalse(content.contains("\"withView1\":\"with\""));
337 assertTrue(content.contains("\"withView2\":\"with\""));
338 assertFalse(content.contains("\"withoutView\":\"without\""));
339 }
340
341 @Test
342 public void jacksonJsonViewWithResponseBodyAndXmlMessageConverter() throws Exception {
343 Method method = JacksonViewController.class.getMethod("handleResponseBody");
344 HandlerMethod handlerMethod = new HandlerMethod(new JacksonViewController(), method);
345 MethodParameter methodReturnType = handlerMethod.getReturnType();
346
347 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
348 converters.add(new MappingJackson2XmlHttpMessageConverter());
349
350 RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
351 converters, null, Arrays.asList(new JsonViewResponseBodyAdvice()));
352
353 Object returnValue = new JacksonViewController().handleResponseBody();
354 processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
355
356 String content = this.servletResponse.getContentAsString();
357 assertFalse(content.contains("<withView1>with</withView1>"));
358 assertTrue(content.contains("<withView2>with</withView2>"));
359 assertFalse(content.contains("<withoutView>without</withoutView>"));
360 }
361
362
363
364 @Test
365 public void jacksonJsonViewWithResponseEntityAndXmlMessageConverter() throws Exception {
366 Method method = JacksonViewController.class.getMethod("handleResponseEntity");
367 HandlerMethod handlerMethod = new HandlerMethod(new JacksonViewController(), method);
368 MethodParameter methodReturnType = handlerMethod.getReturnType();
369
370 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
371 converters.add(new MappingJackson2XmlHttpMessageConverter());
372
373 HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
374 converters, null, Arrays.asList(new JsonViewResponseBodyAdvice()));
375
376 Object returnValue = new JacksonViewController().handleResponseEntity();
377 processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
378
379 String content = this.servletResponse.getContentAsString();
380 assertFalse(content.contains("<withView1>with</withView1>"));
381 assertTrue(content.contains("<withView2>with</withView2>"));
382 assertFalse(content.contains("<withoutView>without</withoutView>"));
383 }
384
385
386 public String handle(
387 @RequestBody List<SimpleBean> list,
388 @RequestBody SimpleBean simpleBean,
389 @RequestBody MultiValueMap<String, String> multiValueMap,
390 @RequestBody String string) {
391
392 return null;
393 }
394
395
396 private static abstract class MyParameterizedController<DTO extends Identifiable> {
397
398 @SuppressWarnings("unused")
399 public void handleDto(@RequestBody DTO dto) {}
400 }
401
402
403 private static class MySimpleParameterizedController extends MyParameterizedController<SimpleBean> {
404 }
405
406
407 private interface Identifiable extends Serializable {
408
409 public Long getId();
410
411 public void setId(Long id);
412 }
413
414
415 @SuppressWarnings({ "serial" })
416 private static class SimpleBean implements Identifiable {
417
418 private Long id;
419
420 private String name;
421
422 @Override
423 public Long getId() {
424 return id;
425 }
426
427 @Override
428 public void setId(Long id) {
429 this.id = id;
430 }
431
432 public String getName() {
433 return name;
434 }
435
436 @SuppressWarnings("unused")
437 public void setName(String name) {
438 this.name = name;
439 }
440 }
441
442
443 private final class ValidatingBinderFactory implements WebDataBinderFactory {
444
445 @Override
446 public WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception {
447 LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
448 validator.afterPropertiesSet();
449 WebDataBinder dataBinder = new WebDataBinder(target, objectName);
450 dataBinder.setValidator(validator);
451 return dataBinder;
452 }
453 }
454
455
456 @ResponseBody
457 private static class ResponseBodyController {
458
459 @RequestMapping
460 public String handle() {
461 return "hello";
462 }
463 }
464
465
466 @RestController
467 private static class TestRestController {
468
469 @RequestMapping
470 public String handle() {
471 return "hello";
472 }
473 }
474
475 private interface MyJacksonView1 {};
476 private interface MyJacksonView2 {};
477
478 private static class JacksonViewBean {
479
480 @JsonView(MyJacksonView1.class)
481 private String withView1;
482
483 @JsonView(MyJacksonView2.class)
484 private String withView2;
485
486 private String withoutView;
487
488 public String getWithView1() {
489 return withView1;
490 }
491
492 public void setWithView1(String withView1) {
493 this.withView1 = withView1;
494 }
495
496 public String getWithView2() {
497 return withView2;
498 }
499
500 public void setWithView2(String withView2) {
501 this.withView2 = withView2;
502 }
503
504 public String getWithoutView() {
505 return withoutView;
506 }
507
508 public void setWithoutView(String withoutView) {
509 this.withoutView = withoutView;
510 }
511 }
512
513 private static class JacksonViewController {
514
515 @RequestMapping
516 @ResponseBody
517 @JsonView(MyJacksonView2.class)
518 public JacksonViewBean handleResponseBody() {
519 JacksonViewBean bean = new JacksonViewBean();
520 bean.setWithView1("with");
521 bean.setWithView2("with");
522 bean.setWithoutView("without");
523 return bean;
524 }
525
526 @RequestMapping
527 @JsonView(MyJacksonView2.class)
528 public ResponseEntity<JacksonViewBean> handleResponseEntity() {
529 JacksonViewBean bean = new JacksonViewBean();
530 bean.setWithView1("with");
531 bean.setWithView2("with");
532 bean.setWithoutView("without");
533 ModelAndView mav = new ModelAndView(new MappingJackson2JsonView());
534 mav.addObject("bean", bean);
535 return new ResponseEntity<JacksonViewBean>(bean, HttpStatus.OK);
536 }
537
538 }
539
540 }