/*
 * Copyright 2002-2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.config;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import static org.junit.Assert.*;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.*;

import javax.servlet.RequestDispatcher;
import javax.validation.constraints.NotNull;

import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.Ordered;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.io.ClassPathResource;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.format.support.FormattingConversionServiceFactoryBean;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.mock.web.test.MockRequestDispatcher;
import org.springframework.mock.web.test.MockServletContext;
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import org.springframework.stereotype.Controller;
import org.springframework.util.PathMatcher;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.accept.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptorAdapter;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.CompositeUriComponentsContributor;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.handler.*;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import org.springframework.web.servlet.resource.AppCacheManifestTransformer;
import org.springframework.web.servlet.resource.CachingResourceResolver;
import org.springframework.web.servlet.resource.CachingResourceTransformer;
import org.springframework.web.servlet.resource.ContentVersionStrategy;
import org.springframework.web.servlet.resource.CssLinkResourceTransformer;
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
import org.springframework.web.servlet.resource.FixedVersionStrategy;
import org.springframework.web.servlet.resource.GzipResourceResolver;
import org.springframework.web.servlet.resource.PathResourceResolver;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.servlet.resource.ResourceResolver;
import org.springframework.web.servlet.resource.ResourceTransformer;
import org.springframework.web.servlet.resource.ResourceUrlProvider;
import org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor;
import org.springframework.web.servlet.resource.VersionResourceResolver;
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
import org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer;
import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.servlet.view.*;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
import org.springframework.web.servlet.view.velocity.VelocityConfigurer;
import org.springframework.web.servlet.view.velocity.VelocityViewResolver;

/**
 * Tests loading actual MVC namespace configuration.
 *
 * @author Keith Donald
 * @author Arjen Poutsma
 * @author Jeremy Grelle
 * @author Brian Clozel
 * @author Sebastien Deleuze
 */
public class MvcNamespaceTests {

	public static final String VIEWCONTROLLER_BEAN_NAME = "org.springframework.web.servlet.config.viewControllerHandlerMapping";
	
	private GenericWebApplicationContext appContext;

	private TestController handler;

	private HandlerMethod handlerMethod;

	@Before
	public void setUp() throws Exception {
		TestMockServletContext servletContext = new TestMockServletContext();
		appContext = new GenericWebApplicationContext();
		appContext.setServletContext(servletContext);
		LocaleContextHolder.setLocale(Locale.US);

		String attributeName = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
		appContext.getServletContext().setAttribute(attributeName, appContext);

		handler = new TestController();
		Method method = TestController.class.getMethod("testBind", Date.class, TestBean.class, BindingResult.class);
		handlerMethod = new InvocableHandlerMethod(handler, method);
	}


	@Test
	public void testDefaultConfig() throws Exception {
		loadBeanDefinitions("mvc-config.xml", 13);

		RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
		assertNotNull(mapping);
		assertEquals(0, mapping.getOrder());
		assertTrue(mapping.getUrlPathHelper().shouldRemoveSemicolonContent());
		mapping.setDefaultHandler(handlerMethod);

		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.json");
		NativeWebRequest webRequest = new ServletWebRequest(request);
		ContentNegotiationManager manager = mapping.getContentNegotiationManager();
		assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(webRequest));

		RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class);
		assertNotNull(adapter);
		assertEquals(false, new DirectFieldAccessor(adapter).getPropertyValue("ignoreDefaultModelOnRedirect"));

		List<HttpMessageConverter<?>> converters = adapter.getMessageConverters();
		assertTrue(converters.size() > 0);
		for(HttpMessageConverter<?> converter : converters) {
			if(converter instanceof AbstractJackson2HttpMessageConverter) {
				ObjectMapper objectMapper = ((AbstractJackson2HttpMessageConverter)converter).getObjectMapper();
				assertFalse(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION));
				assertFalse(objectMapper.getSerializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION));
				assertFalse(objectMapper.getDeserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
				if(converter instanceof MappingJackson2XmlHttpMessageConverter) {
					assertEquals(XmlMapper.class, objectMapper.getClass());
				}
			}
		}

		assertNotNull(appContext.getBean(FormattingConversionServiceFactoryBean.class));
		assertNotNull(appContext.getBean(ConversionService.class));
		assertNotNull(appContext.getBean(LocalValidatorFactoryBean.class));
		assertNotNull(appContext.getBean(Validator.class));

		// default web binding initializer behavior test
		request = new MockHttpServletRequest("GET", "/");
		request.addParameter("date", "2009-10-31");
		MockHttpServletResponse response = new MockHttpServletResponse();

		HandlerExecutionChain chain = mapping.getHandler(request);
		assertEquals(1, chain.getInterceptors().length);
		assertTrue(chain.getInterceptors()[0] instanceof ConversionServiceExposingInterceptor);
		ConversionServiceExposingInterceptor interceptor = (ConversionServiceExposingInterceptor) chain.getInterceptors()[0];
		interceptor.preHandle(request, response, handlerMethod);
		assertSame(appContext.getBean(ConversionService.class), request.getAttribute(ConversionService.class.getName()));

		adapter.handle(request, response, handlerMethod);
		assertTrue(handler.recordedValidationError);

		CompositeUriComponentsContributor uriComponentsContributor = this.appContext.getBean(
				MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME,
				CompositeUriComponentsContributor.class);

		assertNotNull(uriComponentsContributor);
	}

	@Test(expected=TypeMismatchException.class)
	public void testCustomConversionService() throws Exception {
		loadBeanDefinitions("mvc-config-custom-conversion-service.xml", 13);

		RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
		assertNotNull(mapping);
		mapping.setDefaultHandler(handlerMethod);

		// default web binding initializer behavior test
		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
		request.setRequestURI("/accounts/12345");
		request.addParameter("date", "2009-10-31");
		MockHttpServletResponse response = new MockHttpServletResponse();

		HandlerExecutionChain chain = mapping.getHandler(request);
		assertEquals(1, chain.getInterceptors().length);
		assertTrue(chain.getInterceptors()[0] instanceof ConversionServiceExposingInterceptor);
		ConversionServiceExposingInterceptor interceptor = (ConversionServiceExposingInterceptor) chain.getInterceptors()[0];
		interceptor.preHandle(request, response, handler);
		assertSame(appContext.getBean("conversionService"), request.getAttribute(ConversionService.class.getName()));

		RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class);
		assertNotNull(adapter);
		adapter.handle(request, response, handlerMethod);
	}

	@Test
	public void testCustomValidator() throws Exception {
		doTestCustomValidator("mvc-config-custom-validator.xml");
	}

	@Test
	public void testCustomValidator32() throws Exception {
		doTestCustomValidator("mvc-config-custom-validator-32.xml");
	}

	/**
	 * @throws Exception
	 */
	private void doTestCustomValidator(String xml) throws Exception {
		loadBeanDefinitions(xml, 13);

		RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
		assertNotNull(mapping);
		assertFalse(mapping.getUrlPathHelper().shouldRemoveSemicolonContent());

		RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class);
		assertNotNull(adapter);
		assertEquals(true, new DirectFieldAccessor(adapter).getPropertyValue("ignoreDefaultModelOnRedirect"));

		// default web binding initializer behavior test
		MockHttpServletRequest request = new MockHttpServletRequest();
		request.addParameter("date", "2009-10-31");
		MockHttpServletResponse response = new MockHttpServletResponse();
		adapter.handle(request, response, handlerMethod);

		assertTrue(appContext.getBean(TestValidator.class).validatorInvoked);
		assertFalse(handler.recordedValidationError);
	}

	@Test
	public void testInterceptors() throws Exception {
		loadBeanDefinitions("mvc-config-interceptors.xml", 20);

		RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
		assertNotNull(mapping);
		mapping.setDefaultHandler(handlerMethod);

		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
		request.setRequestURI("/accounts/12345");
		request.addParameter("locale", "en");
		request.addParameter("theme", "green");

		HandlerExecutionChain chain = mapping.getHandler(request);
		assertEquals(5, chain.getInterceptors().length);
		assertTrue(chain.getInterceptors()[0] instanceof ConversionServiceExposingInterceptor);
		assertTrue(chain.getInterceptors()[1] instanceof LocaleChangeInterceptor);
		assertTrue(chain.getInterceptors()[2] instanceof WebRequestHandlerInterceptorAdapter);
		assertTrue(chain.getInterceptors()[3] instanceof ThemeChangeInterceptor);
		assertTrue(chain.getInterceptors()[4] instanceof UserRoleAuthorizationInterceptor);

		request.setRequestURI("/admin/users");
		chain = mapping.getHandler(request);
		assertEquals(3, chain.getInterceptors().length);

		request.setRequestURI("/logged/accounts/12345");
		chain = mapping.getHandler(request);
		assertEquals(5, chain.getInterceptors().length);
		assertTrue(chain.getInterceptors()[4] instanceof WebRequestHandlerInterceptorAdapter);

		request.setRequestURI("/foo/logged");
		chain = mapping.getHandler(request);
		assertEquals(5, chain.getInterceptors().length);
		assertTrue(chain.getInterceptors()[4] instanceof WebRequestHandlerInterceptorAdapter);
	}

	@Test
	public void testResources() throws Exception {
		loadBeanDefinitions("mvc-config-resources.xml", 9);

		HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class);
		assertNotNull(adapter);

		ResourceHttpRequestHandler handler = appContext.getBean(ResourceHttpRequestHandler.class);
		assertNotNull(handler);

		SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
		assertNotNull(mapping);
		assertEquals(Ordered.LOWEST_PRECEDENCE - 1, mapping.getOrder());

		BeanNameUrlHandlerMapping beanNameMapping = appContext.getBean(BeanNameUrlHandlerMapping.class);
		assertNotNull(beanNameMapping);
		assertEquals(2, beanNameMapping.getOrder());

		ResourceUrlProvider urlProvider = appContext.getBean(ResourceUrlProvider.class);
		assertNotNull(urlProvider);

		MappedInterceptor mappedInterceptor = appContext.getBean(MappedInterceptor.class);
		assertNotNull(urlProvider);
		assertEquals(ResourceUrlProviderExposingInterceptor.class, mappedInterceptor.getInterceptor().getClass());

		MockHttpServletRequest request = new MockHttpServletRequest();
		request.setRequestURI("/resources/foo.css");
		request.setMethod("GET");

		HandlerExecutionChain chain = mapping.getHandler(request);
		assertTrue(chain.getHandler() instanceof ResourceHttpRequestHandler);

		MockHttpServletResponse response = new MockHttpServletResponse();
		for (HandlerInterceptor interceptor : chain.getInterceptors()) {
			interceptor.preHandle(request, response, chain.getHandler());
		}
		ModelAndView mv = adapter.handle(request, response, chain.getHandler());
		assertNull(mv);
	}

	@Test
	public void testResourcesWithOptionalAttributes() throws Exception {
		loadBeanDefinitions("mvc-config-resources-optional-attrs.xml", 9);

		SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
		assertNotNull(mapping);
		assertEquals(5, mapping.getOrder());
		assertNotNull(mapping.getUrlMap().get("/resources/**"));

		ResourceHttpRequestHandler handler = appContext.getBean((String)mapping.getUrlMap().get("/resources/**"),
				ResourceHttpRequestHandler.class);
		assertNotNull(handler);
		assertEquals(3600, handler.getCacheSeconds());
	}

	@Test
	public void testResourcesWithResolversTransformers() throws Exception {
		loadBeanDefinitions("mvc-config-resources-chain.xml", 10);

		SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
		assertNotNull(mapping);
		assertNotNull(mapping.getUrlMap().get("/resources/**"));
		ResourceHttpRequestHandler handler = appContext.getBean((String)mapping.getUrlMap().get("/resources/**"),
				ResourceHttpRequestHandler.class);
		assertNotNull(handler);

		List<ResourceResolver> resolvers = handler.getResourceResolvers();
		assertThat(resolvers, Matchers.hasSize(3));
		assertThat(resolvers.get(0), Matchers.instanceOf(CachingResourceResolver.class));
		assertThat(resolvers.get(1), Matchers.instanceOf(VersionResourceResolver.class));
		assertThat(resolvers.get(2), Matchers.instanceOf(PathResourceResolver.class));

		CachingResourceResolver cachingResolver = (CachingResourceResolver) resolvers.get(0);
		assertThat(cachingResolver.getCache(), Matchers.instanceOf(ConcurrentMapCache.class));
		assertEquals("test-resource-cache", cachingResolver.getCache().getName());

		VersionResourceResolver versionResolver = (VersionResourceResolver) resolvers.get(1);
		assertThat(versionResolver.getStrategyMap().get("/**/*.js"),
				Matchers.instanceOf(FixedVersionStrategy.class));
		assertThat(versionResolver.getStrategyMap().get("/**"),
				Matchers.instanceOf(ContentVersionStrategy.class));

		List<ResourceTransformer> transformers = handler.getResourceTransformers();
		assertThat(transformers, Matchers.hasSize(3));
		assertThat(transformers.get(0), Matchers.instanceOf(CachingResourceTransformer.class));
		assertThat(transformers.get(1), Matchers.instanceOf(CssLinkResourceTransformer.class));
		assertThat(transformers.get(2), Matchers.instanceOf(AppCacheManifestTransformer.class));

		CachingResourceTransformer cachingTransformer = (CachingResourceTransformer) transformers.get(0);
		assertThat(cachingTransformer.getCache(), Matchers.instanceOf(ConcurrentMapCache.class));
		assertEquals("test-resource-cache", cachingTransformer.getCache().getName());
	}

	@Test
	public void testResourcesWithResolversTransformersCustom() throws Exception {
		loadBeanDefinitions("mvc-config-resources-chain-no-auto.xml", 11);

		SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
		assertNotNull(mapping);
		assertNotNull(mapping.getUrlMap().get("/resources/**"));
		ResourceHttpRequestHandler handler = appContext.getBean((String)mapping.getUrlMap().get("/resources/**"),
				ResourceHttpRequestHandler.class);
		assertNotNull(handler);

		List<ResourceResolver> resolvers = handler.getResourceResolvers();
		assertThat(resolvers, Matchers.hasSize(3));
		assertThat(resolvers.get(0), Matchers.instanceOf(VersionResourceResolver.class));
		assertThat(resolvers.get(1), Matchers.instanceOf(GzipResourceResolver.class));
		assertThat(resolvers.get(2), Matchers.instanceOf(PathResourceResolver.class));

		VersionResourceResolver versionResolver = (VersionResourceResolver) resolvers.get(0);
		assertThat(versionResolver.getStrategyMap().get("/**/*.js"),
				Matchers.instanceOf(FixedVersionStrategy.class));
		assertThat(versionResolver.getStrategyMap().get("/**"),
				Matchers.instanceOf(ContentVersionStrategy.class));

		List<ResourceTransformer> transformers = handler.getResourceTransformers();
		assertThat(transformers, Matchers.hasSize(2));
		assertThat(transformers.get(0), Matchers.instanceOf(CachingResourceTransformer.class));
		assertThat(transformers.get(1), Matchers.instanceOf(AppCacheManifestTransformer.class));
	}

	@Test
	public void testDefaultServletHandler() throws Exception {
		loadBeanDefinitions("mvc-config-default-servlet.xml", 5);

		HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class);
		assertNotNull(adapter);

		DefaultServletHttpRequestHandler handler = appContext.getBean(DefaultServletHttpRequestHandler.class);
		assertNotNull(handler);

		SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
		assertNotNull(mapping);
		assertEquals(Ordered.LOWEST_PRECEDENCE, mapping.getOrder());

		MockHttpServletRequest request = new MockHttpServletRequest();
		request.setRequestURI("/foo.css");
		request.setMethod("GET");

		HandlerExecutionChain chain = mapping.getHandler(request);
		assertTrue(chain.getHandler() instanceof DefaultServletHttpRequestHandler);

		MockHttpServletResponse response = new MockHttpServletResponse();
		ModelAndView mv = adapter.handle(request, response, chain.getHandler());
		assertNull(mv);
	}

	@Test
	public void testDefaultServletHandlerWithOptionalAttributes() throws Exception {
		loadBeanDefinitions("mvc-config-default-servlet-optional-attrs.xml", 5);

		HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class);
		assertNotNull(adapter);

		DefaultServletHttpRequestHandler handler = appContext.getBean(DefaultServletHttpRequestHandler.class);
		assertNotNull(handler);

		SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
		assertNotNull(mapping);
		assertEquals(Ordered.LOWEST_PRECEDENCE, mapping.getOrder());

		MockHttpServletRequest request = new MockHttpServletRequest();
		request.setRequestURI("/foo.css");
		request.setMethod("GET");

		HandlerExecutionChain chain = mapping.getHandler(request);
		assertTrue(chain.getHandler() instanceof DefaultServletHttpRequestHandler);

		MockHttpServletResponse response = new MockHttpServletResponse();
		ModelAndView mv = adapter.handle(request, response, chain.getHandler());
		assertNull(mv);
	}

	@Test
	public void testBeanDecoration() throws Exception {
		loadBeanDefinitions("mvc-config-bean-decoration.xml", 15);

		RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
		assertNotNull(mapping);
		mapping.setDefaultHandler(handlerMethod);

		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");

		HandlerExecutionChain chain = mapping.getHandler(request);
		assertEquals(3, chain.getInterceptors().length);
		assertTrue(chain.getInterceptors()[0] instanceof ConversionServiceExposingInterceptor);
		assertTrue(chain.getInterceptors()[1] instanceof LocaleChangeInterceptor);
		assertTrue(chain.getInterceptors()[2] instanceof ThemeChangeInterceptor);
		LocaleChangeInterceptor interceptor = (LocaleChangeInterceptor) chain.getInterceptors()[1];
		assertEquals("lang", interceptor.getParamName());
		ThemeChangeInterceptor interceptor2 = (ThemeChangeInterceptor) chain.getInterceptors()[2];
		assertEquals("style", interceptor2.getParamName());
	}

	@Test
	public void testViewControllers() throws Exception {
		loadBeanDefinitions("mvc-config-view-controllers.xml", 18);

		RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
		assertNotNull(mapping);
		mapping.setDefaultHandler(handlerMethod);

		BeanNameUrlHandlerMapping beanNameMapping = appContext.getBean(BeanNameUrlHandlerMapping.class);
		assertNotNull(beanNameMapping);
		assertEquals(2, beanNameMapping.getOrder());

		MockHttpServletRequest request = new MockHttpServletRequest();
		request.setMethod("GET");

		HandlerExecutionChain chain = mapping.getHandler(request);
		assertEquals(3, chain.getInterceptors().length);
		assertTrue(chain.getInterceptors()[0] instanceof ConversionServiceExposingInterceptor);
		assertTrue(chain.getInterceptors()[1] instanceof LocaleChangeInterceptor);
		assertTrue(chain.getInterceptors()[2] instanceof ThemeChangeInterceptor);

		SimpleUrlHandlerMapping mapping2 = appContext.getBean(SimpleUrlHandlerMapping.class);
		assertNotNull(mapping2);

		SimpleControllerHandlerAdapter adapter = appContext.getBean(SimpleControllerHandlerAdapter.class);
		assertNotNull(adapter);

		request = new MockHttpServletRequest("GET", "/foo");
		chain = mapping2.getHandler(request);
		assertEquals(4, chain.getInterceptors().length);
		assertTrue(chain.getInterceptors()[1] instanceof ConversionServiceExposingInterceptor);
		assertTrue(chain.getInterceptors()[2] instanceof LocaleChangeInterceptor);
		assertTrue(chain.getInterceptors()[3] instanceof ThemeChangeInterceptor);
		ModelAndView mv = adapter.handle(request, new MockHttpServletResponse(), chain.getHandler());
		assertNull(mv.getViewName());

		request = new MockHttpServletRequest("GET", "/myapp/app/bar");
		request.setContextPath("/myapp");
		request.setServletPath("/app");
		chain = mapping2.getHandler(request);
		assertEquals(4, chain.getInterceptors().length);
		assertTrue(chain.getInterceptors()[1] instanceof ConversionServiceExposingInterceptor);
		assertTrue(chain.getInterceptors()[2] instanceof LocaleChangeInterceptor);
		assertTrue(chain.getInterceptors()[3] instanceof ThemeChangeInterceptor);
		mv = adapter.handle(request, new MockHttpServletResponse(), chain.getHandler());
		assertEquals("baz", mv.getViewName());

		request = new MockHttpServletRequest("GET", "/myapp/app/");
		request.setContextPath("/myapp");
		request.setServletPath("/app");
		chain = mapping2.getHandler(request);
		assertEquals(4, chain.getInterceptors().length);
		assertTrue(chain.getInterceptors()[1] instanceof ConversionServiceExposingInterceptor);
		assertTrue(chain.getInterceptors()[2] instanceof LocaleChangeInterceptor);
		assertTrue(chain.getInterceptors()[3] instanceof ThemeChangeInterceptor);
		mv = adapter.handle(request, new MockHttpServletResponse(), chain.getHandler());
		assertEquals("root", mv.getViewName());

		request = new MockHttpServletRequest("GET", "/myapp/app/old");
		request.setContextPath("/myapp");
		request.setServletPath("/app");
		request.setQueryString("a=b");
		chain = mapping2.getHandler(request);
		mv = adapter.handle(request, new MockHttpServletResponse(), chain.getHandler());
		assertNotNull(mv.getView());
		assertEquals(RedirectView.class, mv.getView().getClass());
		RedirectView redirectView = (RedirectView) mv.getView();
		MockHttpServletResponse response = new MockHttpServletResponse();
		redirectView.render(Collections.emptyMap(), request, response);
		assertEquals("/new?a=b", response.getRedirectedUrl());
		assertEquals(308, response.getStatus());

		request = new MockHttpServletRequest("GET", "/bad");
		chain = mapping2.getHandler(request);
		response = new MockHttpServletResponse();
		mv = adapter.handle(request, response, chain.getHandler());
		assertNull(mv);
		assertEquals(404, response.getStatus());
	}

	/** WebSphere gives trailing servlet path slashes by default!! */
	@Test
	public void testViewControllersOnWebSphere() throws Exception {
		loadBeanDefinitions("mvc-config-view-controllers.xml", 18);

		SimpleUrlHandlerMapping mapping2 = appContext.getBean(SimpleUrlHandlerMapping.class);
		SimpleControllerHandlerAdapter adapter = appContext.getBean(SimpleControllerHandlerAdapter.class);

		MockHttpServletRequest request = new MockHttpServletRequest();
		request.setMethod("GET");
		request.setRequestURI("/myapp/app/bar");
		request.setContextPath("/myapp");
		request.setServletPath("/app/");
		request.setAttribute("com.ibm.websphere.servlet.uri_non_decoded", "/myapp/app/bar");
		HandlerExecutionChain chain = mapping2.getHandler(request);
		assertEquals(4, chain.getInterceptors().length);
		assertTrue(chain.getInterceptors()[1] instanceof ConversionServiceExposingInterceptor);
		assertTrue(chain.getInterceptors()[2] instanceof LocaleChangeInterceptor);
		assertTrue(chain.getInterceptors()[3] instanceof ThemeChangeInterceptor);
		ModelAndView mv2 = adapter.handle(request, new MockHttpServletResponse(), chain.getHandler());
		assertEquals("baz", mv2.getViewName());

		request.setRequestURI("/myapp/app/");
		request.setContextPath("/myapp");
		request.setServletPath("/app/");
		chain = mapping2.getHandler(request);
		assertEquals(4, chain.getInterceptors().length);
		assertTrue(chain.getInterceptors()[1] instanceof ConversionServiceExposingInterceptor);
		assertTrue(chain.getInterceptors()[2] instanceof LocaleChangeInterceptor);
		assertTrue(chain.getInterceptors()[3] instanceof ThemeChangeInterceptor);
		ModelAndView mv3 = adapter.handle(request, new MockHttpServletResponse(), chain.getHandler());
		assertEquals("root", mv3.getViewName());

		request.setRequestURI("/myapp/");
		request.setContextPath("/myapp");
		request.setServletPath("/");
		chain = mapping2.getHandler(request);
		assertEquals(4, chain.getInterceptors().length);
		assertTrue(chain.getInterceptors()[1] instanceof ConversionServiceExposingInterceptor);
		assertTrue(chain.getInterceptors()[2] instanceof LocaleChangeInterceptor);
		assertTrue(chain.getInterceptors()[3] instanceof ThemeChangeInterceptor);
		mv3 = adapter.handle(request, new MockHttpServletResponse(), chain.getHandler());
		assertEquals("root", mv3.getViewName());
	}

	@Test
	public void testViewControllersDefaultConfig() {
		loadBeanDefinitions("mvc-config-view-controllers-minimal.xml", 6);

		SimpleUrlHandlerMapping hm = this.appContext.getBean(SimpleUrlHandlerMapping.class);
		assertNotNull(hm);
		assertNotNull(hm.getUrlMap().get("/path"));
		assertNotNull(hm.getUrlMap().get("/old"));
		assertNotNull(hm.getUrlMap().get("/bad"));

		BeanNameUrlHandlerMapping beanNameMapping = this.appContext.getBean(BeanNameUrlHandlerMapping.class);
		assertNotNull(beanNameMapping);
		assertEquals(2, beanNameMapping.getOrder());
	}

	@Test
	public void testContentNegotiationManager() throws Exception {
		loadBeanDefinitions("mvc-config-content-negotiation-manager.xml", 13);

		RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
		ContentNegotiationManager manager = mapping.getContentNegotiationManager();

		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.xml");
		NativeWebRequest webRequest = new ServletWebRequest(request);
		assertEquals(Arrays.asList(MediaType.valueOf("application/rss+xml")), manager.resolveMediaTypes(webRequest));
	}

	@Test
	public void testAsyncSupportOptions() throws Exception {
		loadBeanDefinitions("mvc-config-async-support.xml", 14);

		RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class);
		assertNotNull(adapter);

		DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(adapter);
		assertEquals(ConcurrentTaskExecutor.class, fieldAccessor.getPropertyValue("taskExecutor").getClass());
		assertEquals(2500L, fieldAccessor.getPropertyValue("asyncRequestTimeout"));

		CallableProcessingInterceptor[] callableInterceptors =
				(CallableProcessingInterceptor[]) fieldAccessor.getPropertyValue("callableInterceptors");
		assertEquals(1, callableInterceptors.length);

		DeferredResultProcessingInterceptor[] deferredResultInterceptors =
				(DeferredResultProcessingInterceptor[]) fieldAccessor.getPropertyValue("deferredResultInterceptors");
		assertEquals(1, deferredResultInterceptors.length);
	}
	
	@Test
	public void testViewResolution() throws Exception {
		loadBeanDefinitions("mvc-config-view-resolution.xml", 6);

		ViewResolverComposite compositeResolver = this.appContext.getBean(ViewResolverComposite.class);
		assertNotNull(compositeResolver);
		assertEquals("Actual: " + compositeResolver.getViewResolvers(), 8, compositeResolver.getViewResolvers().size());
		assertEquals(Ordered.LOWEST_PRECEDENCE, compositeResolver.getOrder());

		List<ViewResolver> resolvers = compositeResolver.getViewResolvers();
		assertEquals(BeanNameViewResolver.class, resolvers.get(0).getClass());

		ViewResolver resolver = resolvers.get(1);
		assertEquals(InternalResourceViewResolver.class, resolver.getClass());
		DirectFieldAccessor accessor = new DirectFieldAccessor(resolver);
		assertEquals(InternalResourceView.class, accessor.getPropertyValue("viewClass"));

		assertEquals(TilesViewResolver.class, resolvers.get(2).getClass());

		resolver = resolvers.get(3);
		FreeMarkerViewResolver freeMarkerViewResolver = (FreeMarkerViewResolver) resolver;
		accessor = new DirectFieldAccessor(resolver);
		assertEquals("freemarker-", accessor.getPropertyValue("prefix"));
		assertEquals(".freemarker", accessor.getPropertyValue("suffix"));
		assertArrayEquals(new String[] {"my*", "*Report"}, (String[]) accessor.getPropertyValue("viewNames"));
		assertEquals(1024, accessor.getPropertyValue("cacheLimit"));

		resolver = resolvers.get(4);
		VelocityViewResolver velocityViewResolver = (VelocityViewResolver) resolver;
		accessor = new DirectFieldAccessor(resolver);
		assertEquals("", accessor.getPropertyValue("prefix"));
		assertEquals(".vm", accessor.getPropertyValue("suffix"));
		assertEquals(0, accessor.getPropertyValue("cacheLimit"));

		resolver = resolvers.get(5);
		GroovyMarkupViewResolver groovyMarkupViewResolver = (GroovyMarkupViewResolver) resolver;
		accessor = new DirectFieldAccessor(resolver);
		assertEquals("", accessor.getPropertyValue("prefix"));
		assertEquals(".tpl", accessor.getPropertyValue("suffix"));
		assertEquals(1024, accessor.getPropertyValue("cacheLimit"));

		assertEquals(InternalResourceViewResolver.class, resolvers.get(6).getClass());
		assertEquals(InternalResourceViewResolver.class, resolvers.get(7).getClass());


		TilesConfigurer tilesConfigurer = appContext.getBean(TilesConfigurer.class);
		assertNotNull(tilesConfigurer);
		String[] definitions = {
				"/org/springframework/web/servlet/resource/tiles/tiles1.xml",
				"/org/springframework/web/servlet/resource/tiles/tiles2.xml"
		};
		accessor = new DirectFieldAccessor(tilesConfigurer);
		assertArrayEquals(definitions, (String[]) accessor.getPropertyValue("definitions"));
		assertTrue((boolean)accessor.getPropertyValue("checkRefresh"));

		FreeMarkerConfigurer freeMarkerConfigurer = appContext.getBean(FreeMarkerConfigurer.class);
		assertNotNull(freeMarkerConfigurer);
		accessor = new DirectFieldAccessor(freeMarkerConfigurer);
		assertArrayEquals(new String[]{"/", "/test"}, (String[]) accessor.getPropertyValue("templateLoaderPaths"));

		VelocityConfigurer velocityConfigurer = appContext.getBean(VelocityConfigurer.class);
		assertNotNull(velocityConfigurer);
		accessor = new DirectFieldAccessor(velocityConfigurer);
		assertEquals("/test", accessor.getPropertyValue("resourceLoaderPath"));

		GroovyMarkupConfigurer groovyMarkupConfigurer = appContext.getBean(GroovyMarkupConfigurer.class);
		assertNotNull(groovyMarkupConfigurer);
		assertEquals("/test", groovyMarkupConfigurer.getResourceLoaderPath());
		assertTrue(groovyMarkupConfigurer.isAutoIndent());
		assertFalse(groovyMarkupConfigurer.isCacheTemplates());
	}

	@Test
	public void testViewResolutionWithContentNegotiation() throws Exception {
		loadBeanDefinitions("mvc-config-view-resolution-content-negotiation.xml", 6);

		ViewResolverComposite compositeResolver = this.appContext.getBean(ViewResolverComposite.class);
		assertNotNull(compositeResolver);
		assertEquals(1, compositeResolver.getViewResolvers().size());
		assertEquals(Ordered.HIGHEST_PRECEDENCE, compositeResolver.getOrder());

		List<ViewResolver> resolvers = compositeResolver.getViewResolvers();
		assertEquals(ContentNegotiatingViewResolver.class, resolvers.get(0).getClass());
		ContentNegotiatingViewResolver cnvr = (ContentNegotiatingViewResolver) resolvers.get(0);
		assertEquals(6, cnvr.getViewResolvers().size());
		assertEquals(1, cnvr.getDefaultViews().size());
		assertTrue(cnvr.isUseNotAcceptableStatusCode());

		String beanName = "contentNegotiationManager";
		DirectFieldAccessor accessor = new DirectFieldAccessor(cnvr);
		ContentNegotiationManager manager = (ContentNegotiationManager) accessor.getPropertyValue(beanName);
		assertNotNull(manager);
		assertSame(manager, this.appContext.getBean(ContentNegotiationManager.class));
	}

	@Test
	public void testViewResolutionWithOrderSet() throws Exception {
		loadBeanDefinitions("mvc-config-view-resolution-custom-order.xml", 1);

		ViewResolverComposite compositeResolver = this.appContext.getBean(ViewResolverComposite.class);
		assertNotNull(compositeResolver);
		assertEquals("Actual: " + compositeResolver.getViewResolvers(), 1, compositeResolver.getViewResolvers().size());
		assertEquals(123, compositeResolver.getOrder());
	}

	@Test
	public void testPathMatchingHandlerMappings() throws Exception {
		loadBeanDefinitions("mvc-config-path-matching-mappings.xml", 22);

		RequestMappingHandlerMapping requestMapping = appContext.getBean(RequestMappingHandlerMapping.class);
		assertNotNull(requestMapping);
		assertEquals(TestPathHelper.class, requestMapping.getUrlPathHelper().getClass());
		assertEquals(TestPathMatcher.class, requestMapping.getPathMatcher().getClass());

		SimpleUrlHandlerMapping viewController = appContext.getBean(VIEWCONTROLLER_BEAN_NAME, SimpleUrlHandlerMapping.class);
		assertNotNull(viewController);
		assertEquals(TestPathHelper.class, viewController.getUrlPathHelper().getClass());
		assertEquals(TestPathMatcher.class, viewController.getPathMatcher().getClass());

		for(SimpleUrlHandlerMapping handlerMapping : appContext.getBeansOfType(SimpleUrlHandlerMapping.class).values()) {
			assertNotNull(handlerMapping);
			assertEquals(TestPathHelper.class, handlerMapping.getUrlPathHelper().getClass());
			assertEquals(TestPathMatcher.class, handlerMapping.getPathMatcher().getClass());
		}

	}


	private void loadBeanDefinitions(String fileName, int expectedBeanCount) {
		XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
		ClassPathResource resource = new ClassPathResource(fileName, AnnotationDrivenBeanDefinitionParserTests.class);
		reader.loadBeanDefinitions(resource);
		String names = Arrays.toString(this.appContext.getBeanDefinitionNames());
		assertEquals("Bean names: " + names, expectedBeanCount, appContext.getBeanDefinitionCount());
		appContext.refresh();
	}


	@Controller
	public static class TestController {

		private boolean recordedValidationError;

		@RequestMapping
		public void testBind(@RequestParam @DateTimeFormat(iso=ISO.DATE) Date date, @Validated(MyGroup.class) TestBean bean, BindingResult result) {
			this.recordedValidationError = (result.getErrorCount() == 1);
		}
	}

	public static class TestValidator implements Validator {

		boolean validatorInvoked;

		@Override
		public boolean supports(Class<?> clazz) {
			return true;
		}

		@Override
		public void validate(Object target, Errors errors) {
			this.validatorInvoked = true;
		}
	}

	@Retention(RetentionPolicy.RUNTIME)
	public @interface MyGroup {
	}

	private static class TestBean {

		@NotNull(groups=MyGroup.class)
		private String field;

		@SuppressWarnings("unused")
		public String getField() {
			return field;
		}

		@SuppressWarnings("unused")
		public void setField(String field) {
			this.field = field;
		}
	}

	private static class TestMockServletContext extends MockServletContext {

		@Override
		public RequestDispatcher getNamedDispatcher(String path) {
			if (path.equals("default") || path.equals("custom")) {
				return new MockRequestDispatcher("/");
			} else {
				return null;
			}
		}
	}

	public static class TestCallableProcessingInterceptor extends CallableProcessingInterceptorAdapter { }

	public static class TestDeferredResultProcessingInterceptor extends DeferredResultProcessingInterceptorAdapter { }

	public static class TestPathMatcher implements PathMatcher {

		@Override
		public boolean isPattern(String path) {
			return false;
		}

		@Override
		public boolean match(String pattern, String path) {
			return path.matches(pattern);
		}

		@Override
		public boolean matchStart(String pattern, String path) {
			return false;
		}

		@Override
		public String extractPathWithinPattern(String pattern, String path) {
			return null;
		}

		@Override
		public Map<String, String> extractUriTemplateVariables(String pattern, String path) {
			return null;
		}

		@Override
		public Comparator<String> getPatternComparator(String path) {
			return null;
		}

		@Override
		public String combine(String pattern1, String pattern2) {
			return null;
		}
	}

	public static class TestPathHelper extends UrlPathHelper { }

	public static class TestCacheManager implements CacheManager {
		@Override
		public Cache getCache(String name) {
			return new ConcurrentMapCache(name);
		}

		@Override
		public Collection<String> getCacheNames() {
			return null;
		}
	}


}
