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.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   * Unit tests for {@link RequestMappingHandlerAdapter}.
60   *
61   * @author Rossen Stoyanchev
62   * @see ServletAnnotationControllerHandlerMethodTests
63   * @see HandlerMethodAnnotationDetectionTests
64   * @see RequestMappingHandlerAdapterIntegrationTests
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 	// SPR-10859
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 }