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.config.annotation;
18  
19  import java.util.List;
20  import java.util.Locale;
21  import java.util.Map;
22  import javax.servlet.http.HttpServletRequest;
23  
24  import com.fasterxml.jackson.databind.DeserializationFeature;
25  import com.fasterxml.jackson.databind.MapperFeature;
26  import com.fasterxml.jackson.databind.ObjectMapper;
27  import com.fasterxml.jackson.dataformat.xml.XmlMapper;
28  import org.joda.time.DateTime;
29  import org.junit.Test;
30  
31  import org.springframework.beans.DirectFieldAccessor;
32  import org.springframework.beans.factory.BeanFactoryUtils;
33  import org.springframework.context.ApplicationContext;
34  import org.springframework.context.MessageSource;
35  import org.springframework.context.annotation.Bean;
36  import org.springframework.context.annotation.Configuration;
37  import org.springframework.context.annotation.Scope;
38  import org.springframework.context.annotation.ScopedProxyMode;
39  import org.springframework.context.i18n.LocaleContextHolder;
40  import org.springframework.context.support.StaticMessageSource;
41  import org.springframework.core.Ordered;
42  import org.springframework.core.convert.ConversionService;
43  import org.springframework.format.annotation.DateTimeFormat;
44  import org.springframework.format.annotation.DateTimeFormat.ISO;
45  import org.springframework.format.support.FormattingConversionService;
46  import org.springframework.http.HttpEntity;
47  import org.springframework.http.HttpStatus;
48  import org.springframework.http.converter.HttpMessageConverter;
49  import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
50  import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
51  import org.springframework.mock.web.test.MockHttpServletRequest;
52  import org.springframework.mock.web.test.MockHttpServletResponse;
53  import org.springframework.mock.web.test.MockServletContext;
54  import org.springframework.stereotype.Controller;
55  import org.springframework.util.AntPathMatcher;
56  import org.springframework.util.PathMatcher;
57  import org.springframework.validation.Validator;
58  import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
59  import org.springframework.web.bind.annotation.PathVariable;
60  import org.springframework.web.bind.annotation.RequestMapping;
61  import org.springframework.web.bind.annotation.ResponseStatus;
62  import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
63  import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
64  import org.springframework.web.method.support.CompositeUriComponentsContributor;
65  import org.springframework.web.servlet.HandlerExceptionResolver;
66  import org.springframework.web.servlet.HandlerExecutionChain;
67  import org.springframework.web.servlet.ViewResolver;
68  import org.springframework.web.servlet.handler.AbstractHandlerMapping;
69  import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
70  import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
71  import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite;
72  import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
73  import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
74  import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice;
75  import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
76  import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
77  import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
78  import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
79  import org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor;
80  import org.springframework.web.servlet.view.BeanNameViewResolver;
81  import org.springframework.web.servlet.view.InternalResourceViewResolver;
82  import org.springframework.web.servlet.view.ViewResolverComposite;
83  import org.springframework.web.util.UrlPathHelper;
84  
85  import static org.junit.Assert.*;
86  
87  /**
88   * A test fixture with an {@link WebMvcConfigurationSupport} instance.
89   *
90   * @author Rossen Stoyanchev
91   * @author Juergen Hoeller
92   * @author Sebastien Deleuze
93   */
94  public class WebMvcConfigurationSupportTests {
95  
96  	@Test
97  	public void requestMappingHandlerMapping() throws Exception {
98  		ApplicationContext context = initContext(WebConfig.class, ScopedController.class, ScopedProxyController.class);
99  		RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
100 		assertEquals(0, handlerMapping.getOrder());
101 
102 		HandlerExecutionChain chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/"));
103 		assertNotNull(chain);
104 		assertNotNull(chain.getInterceptors());
105 		assertEquals(ConversionServiceExposingInterceptor.class, chain.getInterceptors()[0].getClass());
106 
107 		chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/scoped"));
108 		assertNotNull(chain);
109 
110 		chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/scopedProxy"));
111 		assertNotNull(chain);
112 	}
113 
114 	@Test
115 	public void emptyViewControllerHandlerMapping() {
116 		ApplicationContext context = initContext(WebConfig.class);
117 		String name = "viewControllerHandlerMapping";
118 		AbstractHandlerMapping handlerMapping = context.getBean(name, AbstractHandlerMapping.class);
119 
120 		assertNotNull(handlerMapping);
121 		assertEquals(Integer.MAX_VALUE, handlerMapping.getOrder());
122 		assertTrue(handlerMapping.getClass().getName().endsWith("EmptyHandlerMapping"));
123 	}
124 
125 	@Test
126 	public void beanNameHandlerMapping() throws Exception {
127 		ApplicationContext context = initContext(WebConfig.class);
128 		BeanNameUrlHandlerMapping handlerMapping = context.getBean(BeanNameUrlHandlerMapping.class);
129 		assertEquals(2, handlerMapping.getOrder());
130 
131 		HttpServletRequest request = new MockHttpServletRequest("GET", "/testController");
132 		HandlerExecutionChain chain = handlerMapping.getHandler(request);
133 
134 		assertNotNull(chain.getInterceptors());
135 		assertEquals(3, chain.getInterceptors().length);
136 		assertEquals(ConversionServiceExposingInterceptor.class, chain.getInterceptors()[1].getClass());
137 		assertEquals(ResourceUrlProviderExposingInterceptor.class, chain.getInterceptors()[2].getClass());
138 	}
139 
140 	@Test
141 	public void emptyResourceHandlerMapping() {
142 		ApplicationContext context = initContext(WebConfig.class);
143 		AbstractHandlerMapping handlerMapping = context.getBean("resourceHandlerMapping", AbstractHandlerMapping.class);
144 
145 		assertNotNull(handlerMapping);
146 		assertEquals(Integer.MAX_VALUE, handlerMapping.getOrder());
147 		assertTrue(handlerMapping.getClass().getName().endsWith("EmptyHandlerMapping"));
148 	}
149 
150 	@Test
151 	public void emptyDefaultServletHandlerMapping() {
152 		ApplicationContext context = initContext(WebConfig.class);
153 		String name = "defaultServletHandlerMapping";
154 		AbstractHandlerMapping handlerMapping = context.getBean(name, AbstractHandlerMapping.class);
155 
156 		assertNotNull(handlerMapping);
157 		assertEquals(Integer.MAX_VALUE, handlerMapping.getOrder());
158 		assertTrue(handlerMapping.getClass().getName().endsWith("EmptyHandlerMapping"));
159 	}
160 
161 	@Test
162 	public void requestMappingHandlerAdapter() throws Exception {
163 		ApplicationContext context = initContext(WebConfig.class);
164 		RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class);
165 		List<HttpMessageConverter<?>> converters = adapter.getMessageConverters();
166 		assertEquals(9, converters.size());
167 		for(HttpMessageConverter<?> converter : converters) {
168 			if (converter instanceof AbstractJackson2HttpMessageConverter) {
169 				ObjectMapper objectMapper = ((AbstractJackson2HttpMessageConverter)converter).getObjectMapper();
170 				assertFalse(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION));
171 				assertFalse(objectMapper.getSerializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION));
172 				assertFalse(objectMapper.getDeserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
173 				if (converter instanceof MappingJackson2XmlHttpMessageConverter) {
174 					assertEquals(XmlMapper.class, objectMapper.getClass());
175 				}
176 			}
177 		}
178 
179 		ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer();
180 		assertNotNull(initializer);
181 
182 		ConversionService conversionService = initializer.getConversionService();
183 		assertNotNull(conversionService);
184 		assertTrue(conversionService instanceof FormattingConversionService);
185 
186 		Validator validator = initializer.getValidator();
187 		assertNotNull(validator);
188 		assertTrue(validator instanceof LocalValidatorFactoryBean);
189 
190 		DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(adapter);
191 		List<Object> interceptors = (List<Object>) fieldAccessor.getPropertyValue("responseBodyAdvice");
192 		assertEquals(1, interceptors.size());
193 		assertEquals(JsonViewResponseBodyAdvice.class, interceptors.get(0).getClass());
194 	}
195 
196 	@Test
197 	public void uriComponentsContributor() throws Exception {
198 		ApplicationContext context = initContext(WebConfig.class);
199 		CompositeUriComponentsContributor uriComponentsContributor = context.getBean(
200 				MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME,
201 				CompositeUriComponentsContributor.class);
202 
203 		assertNotNull(uriComponentsContributor);
204 	}
205 
206 	@Test
207 	public void handlerExceptionResolver() throws Exception {
208 		ApplicationContext context = initContext(WebConfig.class);
209 		HandlerExceptionResolverComposite compositeResolver =
210 				context.getBean("handlerExceptionResolver", HandlerExceptionResolverComposite.class);
211 
212 		assertEquals(0, compositeResolver.getOrder());
213 		List<HandlerExceptionResolver> expectedResolvers = compositeResolver.getExceptionResolvers();
214 
215 		assertEquals(ExceptionHandlerExceptionResolver.class, expectedResolvers.get(0).getClass());
216 		assertEquals(ResponseStatusExceptionResolver.class, expectedResolvers.get(1).getClass());
217 		assertEquals(DefaultHandlerExceptionResolver.class, expectedResolvers.get(2).getClass());
218 
219 		ExceptionHandlerExceptionResolver eher = (ExceptionHandlerExceptionResolver) expectedResolvers.get(0);
220 		assertNotNull(eher.getApplicationContext());
221 
222 		DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(eher);
223 		List<Object> interceptors = (List<Object>) fieldAccessor.getPropertyValue("responseBodyAdvice");
224 		assertEquals(1, interceptors.size());
225 		assertEquals(JsonViewResponseBodyAdvice.class, interceptors.get(0).getClass());
226 
227 		LocaleContextHolder.setLocale(Locale.ENGLISH);
228 		try {
229 			ResponseStatusExceptionResolver rser = (ResponseStatusExceptionResolver) expectedResolvers.get(1);
230 			MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
231 			MockHttpServletResponse response = new MockHttpServletResponse();
232 			rser.resolveException(request, response, context.getBean(TestController.class), new UserAlreadyExistsException());
233 			assertEquals("User already exists!", response.getErrorMessage());
234 		}
235 		finally {
236 			LocaleContextHolder.resetLocaleContext();
237 		}
238 	}
239 
240 	@Test
241 	public void mvcViewResolver() {
242 		ApplicationContext context = initContext(WebConfig.class);
243 		ViewResolverComposite resolver = context.getBean("mvcViewResolver", ViewResolverComposite.class);
244 
245 		assertNotNull(resolver);
246 		assertEquals(1, resolver.getViewResolvers().size());
247 		assertEquals(InternalResourceViewResolver.class, resolver.getViewResolvers().get(0).getClass());
248 		assertEquals(Ordered.LOWEST_PRECEDENCE, resolver.getOrder());
249 	}
250 
251 	@Test
252 	public void mvcViewResolverWithExistingResolver() throws Exception {
253 		ApplicationContext context = initContext(WebConfig.class, ViewResolverConfig.class);
254 		ViewResolverComposite resolver = context.getBean("mvcViewResolver", ViewResolverComposite.class);
255 
256 		assertNotNull(resolver);
257 		assertEquals(0, resolver.getViewResolvers().size());
258 		assertEquals(Ordered.LOWEST_PRECEDENCE, resolver.getOrder());
259 		assertNull(resolver.resolveViewName("anyViewName", Locale.ENGLISH));
260 	}
261 
262 	@Test
263 	public void mvcViewResolverWithOrderSet() {
264 		ApplicationContext context = initContext(CustomViewResolverOrderConfig.class);
265 		ViewResolverComposite resolver = context.getBean("mvcViewResolver", ViewResolverComposite.class);
266 
267 		Map<String, ViewResolver> map = BeanFactoryUtils.beansOfTypeIncludingAncestors(
268 				context, ViewResolver.class, true, false);
269 
270 		assertNotNull(resolver);
271 		assertEquals(1, resolver.getViewResolvers().size());
272 		assertEquals(InternalResourceViewResolver.class, resolver.getViewResolvers().get(0).getClass());
273 		assertEquals(123, resolver.getOrder());
274 	}
275 
276 	@Test
277 	public void defaultPathMatchConfiguration() throws Exception {
278 		ApplicationContext context = initContext(WebConfig.class);
279 		UrlPathHelper urlPathHelper = context.getBean(UrlPathHelper.class);
280 		PathMatcher pathMatcher = context.getBean(PathMatcher.class);
281 
282 		assertNotNull(urlPathHelper);
283 		assertNotNull(pathMatcher);
284 		assertEquals(AntPathMatcher.class, pathMatcher.getClass());
285 	}
286 
287 
288 	private ApplicationContext initContext(Class... configClasses) {
289 		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
290 		context.setServletContext(new MockServletContext());
291 		context.register(configClasses);
292 		context.refresh();
293 		return context;
294 	}
295 
296 
297 	@EnableWebMvc
298 	@Configuration
299 	public static class WebConfig {
300 
301 		@Bean(name="/testController")
302 		public TestController testController() {
303 			return new TestController();
304 		}
305 
306 		@Bean
307 		public MessageSource messageSource() {
308 			StaticMessageSource messageSource = new StaticMessageSource();
309 			messageSource.addMessage("exception.user.exists", Locale.ENGLISH, "User already exists!");
310 			return messageSource;
311 		}
312 	}
313 
314 
315 	@Configuration
316 	public static class ViewResolverConfig {
317 
318 		@Bean
319 		public ViewResolver beanNameViewResolver() {
320 			return new BeanNameViewResolver();
321 		}
322 	}
323 
324 
325 	@EnableWebMvc
326 	@Configuration
327 	public static class CustomViewResolverOrderConfig extends WebMvcConfigurerAdapter {
328 
329 		@Override
330 		public void configureViewResolvers(ViewResolverRegistry registry) {
331 			registry.jsp();
332 			registry.order(123);
333 		}
334 	}
335 
336 
337 	@Controller
338 	public static class TestController {
339 
340 		@RequestMapping("/")
341 		public void handle() {
342 		}
343 
344 		@RequestMapping("/foo/{id}/bar/{date}")
345 		public HttpEntity<Void> methodWithTwoPathVariables(@PathVariable Integer id,
346 				@DateTimeFormat(iso = ISO.DATE) @PathVariable DateTime date) {
347 			return null;
348 		}
349 	}
350 
351 
352 	@Controller
353 	@Scope("prototype")
354 	public static class ScopedController {
355 
356 		@RequestMapping("/scoped")
357 		public void handle() {
358 		}
359 	}
360 
361 
362 	@Controller
363 	@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
364 	public static class ScopedProxyController {
365 
366 		@RequestMapping("/scopedProxy")
367 		public void handle() {
368 		}
369 	}
370 
371 
372 	@ResponseStatus(value = HttpStatus.BAD_REQUEST,  reason = "exception.user.exists")
373 	@SuppressWarnings("serial")
374 	public static class UserAlreadyExistsException extends RuntimeException {
375 	}
376 
377 }