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.lang.reflect.Method;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.LinkedHashMap;
23 import java.util.List;
24 import java.util.Map;
25
26 import org.junit.Before;
27 import org.junit.BeforeClass;
28 import org.junit.Test;
29
30 import org.springframework.core.MethodParameter;
31 import org.springframework.http.HttpStatus;
32 import org.springframework.http.MediaType;
33 import org.springframework.http.ResponseEntity;
34 import org.springframework.http.converter.HttpMessageConverter;
35 import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
36 import org.springframework.http.converter.json.MappingJacksonValue;
37 import org.springframework.http.server.ServerHttpRequest;
38 import org.springframework.http.server.ServerHttpResponse;
39 import org.springframework.http.server.ServletServerHttpResponse;
40 import org.springframework.mock.web.test.MockHttpServletRequest;
41 import org.springframework.mock.web.test.MockHttpServletResponse;
42 import org.springframework.ui.Model;
43 import org.springframework.web.bind.annotation.ControllerAdvice;
44 import org.springframework.web.bind.annotation.ModelAttribute;
45 import org.springframework.web.bind.annotation.SessionAttributes;
46 import org.springframework.web.context.support.StaticWebApplicationContext;
47 import org.springframework.web.method.HandlerMethod;
48 import org.springframework.web.method.annotation.ModelMethodProcessor;
49 import org.springframework.web.method.support.HandlerMethodArgumentResolver;
50 import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
51 import org.springframework.web.method.support.InvocableHandlerMethod;
52 import org.springframework.web.servlet.DispatcherServlet;
53 import org.springframework.web.servlet.FlashMap;
54 import org.springframework.web.servlet.ModelAndView;
55
56 import static org.junit.Assert.*;
57
58
59
60
61
62
63
64
65
66 public class RequestMappingHandlerAdapterTests {
67
68 private static int RESOLVER_COUNT;
69
70 private static int INIT_BINDER_RESOLVER_COUNT;
71
72 private static int HANDLER_COUNT;
73
74 private RequestMappingHandlerAdapter handlerAdapter;
75
76 private MockHttpServletRequest request;
77
78 private MockHttpServletResponse response;
79
80 private StaticWebApplicationContext webAppContext;
81
82
83 @BeforeClass
84 public static void setupOnce() {
85 RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
86 adapter.setApplicationContext(new StaticWebApplicationContext());
87 adapter.afterPropertiesSet();
88
89 RESOLVER_COUNT = adapter.getArgumentResolvers().size();
90 INIT_BINDER_RESOLVER_COUNT = adapter.getInitBinderArgumentResolvers().size();
91 HANDLER_COUNT = adapter.getReturnValueHandlers().size();
92 }
93
94 @Before
95 public void setup() throws Exception {
96 this.webAppContext = new StaticWebApplicationContext();
97 this.handlerAdapter = new RequestMappingHandlerAdapter();
98 this.handlerAdapter.setApplicationContext(this.webAppContext);
99 this.request = new MockHttpServletRequest();
100 this.response = new MockHttpServletResponse();
101 }
102
103
104 @Test
105 public void cacheControlWithoutSessionAttributes() throws Exception {
106 HandlerMethod handlerMethod = handlerMethod(new SimpleController(), "handle");
107 this.handlerAdapter.setCacheSeconds(100);
108 this.handlerAdapter.afterPropertiesSet();
109
110 this.handlerAdapter.handle(this.request, this.response, handlerMethod);
111 assertTrue(response.getHeader("Cache-Control").contains("max-age"));
112 }
113
114 @Test
115 public void cacheControlWithSessionAttributes() throws Exception {
116 SessionAttributeController handler = new SessionAttributeController();
117 this.handlerAdapter.setCacheSeconds(100);
118 this.handlerAdapter.afterPropertiesSet();
119
120 this.handlerAdapter.handle(this.request, this.response, handlerMethod(handler, "handle"));
121 assertEquals("no-cache", this.response.getHeader("Cache-Control"));
122 }
123
124 @Test
125 public void setAlwaysUseRedirectAttributes() throws Exception {
126 HandlerMethodArgumentResolver redirectAttributesResolver = new RedirectAttributesMethodArgumentResolver();
127 HandlerMethodArgumentResolver modelResolver = new ModelMethodProcessor();
128 HandlerMethodReturnValueHandler viewHandler = new ViewNameMethodReturnValueHandler();
129
130 this.handlerAdapter.setArgumentResolvers(Arrays.asList(redirectAttributesResolver, modelResolver));
131 this.handlerAdapter.setReturnValueHandlers(Arrays.asList(viewHandler));
132 this.handlerAdapter.setIgnoreDefaultModelOnRedirect(true);
133 this.handlerAdapter.afterPropertiesSet();
134
135 this.request.setAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
136
137 HandlerMethod handlerMethod = handlerMethod(new RedirectAttributeController(), "handle", Model.class);
138 ModelAndView mav = this.handlerAdapter.handle(request, response, handlerMethod);
139
140 assertTrue("Without RedirectAttributes arg, model should be empty", mav.getModel().isEmpty());
141 }
142
143 @Test
144 public void setCustomArgumentResolvers() throws Exception {
145 HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver();
146 this.handlerAdapter.setCustomArgumentResolvers(Arrays.asList(resolver));
147 this.handlerAdapter.afterPropertiesSet();
148
149 assertTrue(this.handlerAdapter.getArgumentResolvers().contains(resolver));
150 assertMethodProcessorCount(RESOLVER_COUNT + 1, INIT_BINDER_RESOLVER_COUNT + 1, HANDLER_COUNT);
151 }
152
153 @Test
154 public void setArgumentResolvers() throws Exception {
155 HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver();
156 this.handlerAdapter.setArgumentResolvers(Arrays.asList(resolver));
157 this.handlerAdapter.afterPropertiesSet();
158
159 assertMethodProcessorCount(1, INIT_BINDER_RESOLVER_COUNT, HANDLER_COUNT);
160 }
161
162 @Test
163 public void setInitBinderArgumentResolvers() throws Exception {
164 HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver();
165 this.handlerAdapter.setInitBinderArgumentResolvers(Arrays.<HandlerMethodArgumentResolver>asList(resolver));
166 this.handlerAdapter.afterPropertiesSet();
167
168 assertMethodProcessorCount(RESOLVER_COUNT, 1, HANDLER_COUNT);
169 }
170
171 @Test
172 public void setCustomReturnValueHandlers() {
173 HandlerMethodReturnValueHandler handler = new ViewNameMethodReturnValueHandler();
174 this.handlerAdapter.setCustomReturnValueHandlers(Arrays.asList(handler));
175 this.handlerAdapter.afterPropertiesSet();
176
177 assertTrue(this.handlerAdapter.getReturnValueHandlers().contains(handler));
178 assertMethodProcessorCount(RESOLVER_COUNT, INIT_BINDER_RESOLVER_COUNT, HANDLER_COUNT + 1);
179 }
180
181 @Test
182 public void setReturnValueHandlers() {
183 HandlerMethodReturnValueHandler handler = new ModelMethodProcessor();
184 this.handlerAdapter.setReturnValueHandlers(Arrays.asList(handler));
185 this.handlerAdapter.afterPropertiesSet();
186
187 assertMethodProcessorCount(RESOLVER_COUNT, INIT_BINDER_RESOLVER_COUNT, 1);
188 }
189
190 @Test
191 public void modelAttributeAdvice() throws Exception {
192 this.webAppContext.registerSingleton("maa", ModelAttributeAdvice.class);
193 this.webAppContext.refresh();
194
195 HandlerMethod handlerMethod = handlerMethod(new SimpleController(), "handle");
196 this.handlerAdapter.afterPropertiesSet();
197 ModelAndView mav = this.handlerAdapter.handle(this.request, this.response, handlerMethod);
198
199 assertEquals("lAttr1", mav.getModel().get("attr1"));
200 assertEquals("gAttr2", mav.getModel().get("attr2"));
201 }
202
203 @Test
204 public void modelAttributeAdviceInParentContext() throws Exception {
205 StaticWebApplicationContext parent = new StaticWebApplicationContext();
206 parent.registerSingleton("maa", ModelAttributeAdvice.class);
207 parent.refresh();
208 this.webAppContext.setParent(parent);
209 this.webAppContext.refresh();
210
211 HandlerMethod handlerMethod = handlerMethod(new SimpleController(), "handle");
212 this.handlerAdapter.afterPropertiesSet();
213 ModelAndView mav = this.handlerAdapter.handle(this.request, this.response, handlerMethod);
214
215 assertEquals("lAttr1", mav.getModel().get("attr1"));
216 assertEquals("gAttr2", mav.getModel().get("attr2"));
217 }
218
219 @Test
220 public void modelAttributePackageNameAdvice() throws Exception {
221 this.webAppContext.registerSingleton("mapa", ModelAttributePackageAdvice.class);
222 this.webAppContext.registerSingleton("manupa", ModelAttributeNotUsedPackageAdvice.class);
223 this.webAppContext.refresh();
224
225 HandlerMethod handlerMethod = handlerMethod(new SimpleController(), "handle");
226 this.handlerAdapter.afterPropertiesSet();
227 ModelAndView mav = this.handlerAdapter.handle(this.request, this.response, handlerMethod);
228
229 assertEquals("lAttr1", mav.getModel().get("attr1"));
230 assertEquals("gAttr2", mav.getModel().get("attr2"));
231 assertEquals(null,mav.getModel().get("attr3"));
232 }
233
234
235
236 @Test
237 public void responseBodyAdvice() throws Exception {
238 List<HttpMessageConverter<?>> converters = new ArrayList<>();
239 converters.add(new MappingJackson2HttpMessageConverter());
240 this.handlerAdapter.setMessageConverters(converters);
241
242 this.webAppContext.registerSingleton("rba", ResponseCodeSuppressingAdvice.class);
243 this.webAppContext.registerSingleton("ja", JsonpAdvice.class);
244 this.webAppContext.refresh();
245
246 this.request.addHeader("Accept", MediaType.APPLICATION_JSON_VALUE);
247 this.request.setParameter("c", "callback");
248
249 HandlerMethod handlerMethod = handlerMethod(new SimpleController(), "handleWithResponseEntity");
250 this.handlerAdapter.afterPropertiesSet();
251 this.handlerAdapter.handle(this.request, this.response, handlerMethod);
252
253 assertEquals(200, this.response.getStatus());
254 assertEquals("callback({\"status\":400,\"message\":\"body\"});", this.response.getContentAsString());
255 }
256
257
258 private HandlerMethod handlerMethod(Object handler, String methodName, Class<?>... paramTypes) throws Exception {
259 Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes);
260 return new InvocableHandlerMethod(handler, method);
261 }
262
263 private void assertMethodProcessorCount(int resolverCount, int initBinderResolverCount, int handlerCount) {
264 assertEquals(resolverCount, this.handlerAdapter.getArgumentResolvers().size());
265 assertEquals(initBinderResolverCount, this.handlerAdapter.getInitBinderArgumentResolvers().size());
266 assertEquals(handlerCount, this.handlerAdapter.getReturnValueHandlers().size());
267 }
268
269
270 @SuppressWarnings("unused")
271 private static class SimpleController {
272
273 @ModelAttribute
274 public void addAttributes(Model model) {
275 model.addAttribute("attr1", "lAttr1");
276 }
277
278 public String handle() {
279 return null;
280 }
281
282 public ResponseEntity<String> handleWithResponseEntity() {
283 return new ResponseEntity<String>("body", HttpStatus.BAD_REQUEST);
284 }
285 }
286
287
288 @SessionAttributes("attr1")
289 private static class SessionAttributeController {
290
291 @SuppressWarnings("unused")
292 public void handle() {
293 }
294 }
295
296
297 @SuppressWarnings("unused")
298 private static class RedirectAttributeController {
299
300 public String handle(Model model) {
301 model.addAttribute("someAttr", "someAttrValue");
302 return "redirect:/path";
303 }
304 }
305
306
307 @ControllerAdvice
308 private static class ModelAttributeAdvice {
309
310 @SuppressWarnings("unused")
311 @ModelAttribute
312 public void addAttributes(Model model) {
313 model.addAttribute("attr1", "gAttr1");
314 model.addAttribute("attr2", "gAttr2");
315 }
316 }
317
318
319 @ControllerAdvice({"org.springframework.web.servlet.mvc.method.annotation", "java.lang"})
320 private static class ModelAttributePackageAdvice {
321
322 @SuppressWarnings("unused")
323 @ModelAttribute
324 public void addAttributes(Model model) {
325 model.addAttribute("attr2", "gAttr2");
326 }
327 }
328
329
330 @ControllerAdvice("java.lang")
331 private static class ModelAttributeNotUsedPackageAdvice {
332
333 @SuppressWarnings("unused")
334 @ModelAttribute
335 public void addAttributes(Model model) {
336 model.addAttribute("attr3", "gAttr3");
337 }
338 }
339
340 @ControllerAdvice
341 private static class ResponseCodeSuppressingAdvice extends AbstractMappingJacksonResponseBodyAdvice {
342
343 @SuppressWarnings("unchecked")
344 @Override
345 protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,
346 MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
347
348 int status = ((ServletServerHttpResponse) response).getServletResponse().getStatus();
349 response.setStatusCode(HttpStatus.OK);
350
351 Map<String, Object> map = new LinkedHashMap<>();
352 map.put("status", status);
353 map.put("message", bodyContainer.getValue());
354 bodyContainer.setValue(map);
355 }
356 }
357
358 @ControllerAdvice
359 private static class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
360
361 public JsonpAdvice() {
362 super("c");
363 }
364 }
365
366 }