/*
* Copyright 2012-2017 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.boot.autoconfigure.web.servlet;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ValidatorFactory;
import org.assertj.core.api.Condition;
import org.joda.time.DateTime;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WelcomePageHandlerMapping;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.filter.OrderedHttpPutFormContentFilter;
import org.springframework.boot.web.servlet.server.MockServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.filter.HttpPutFormContentFilter;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
import org.springframework.web.servlet.i18n.FixedLocaleResolver;
import org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
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.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.VersionResourceResolver;
import org.springframework.web.servlet.view.AbstractView;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link WebMvcAutoConfiguration}.
*
* @author Phillip Webb
* @author Dave Syer
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Brian Clozel
* @author EddĂș MelĂ©ndez
*/
public class WebMvcAutoConfigurationTests {
private static final MockServletWebServerFactory webServerFactory = new MockServletWebServerFactory();
@Rule
public ExpectedException thrown = ExpectedException.none();
private AnnotationConfigServletWebServerApplicationContext context;
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void handlerAdaptersCreated() throws Exception {
load();
assertThat(this.context.getBeanNamesForType(HandlerAdapter.class).length)
.isEqualTo(3);
assertThat(this.context.getBean(RequestMappingHandlerAdapter.class)
.getMessageConverters()).isNotEmpty()
.isEqualTo(this.context.getBean(HttpMessageConverters.class)
.getConverters());
}
@Test
public void handlerMappingsCreated() throws Exception {
load();
assertThat(this.context.getBeanNamesForType(HandlerMapping.class).length)
.isEqualTo(7);
}
@Test
public void resourceHandlerMapping() throws Exception {
load();
Map<String, List<Resource>> mappingLocations = getResourceMappingLocations();
assertThat(mappingLocations.get("/**")).hasSize(5);
assertThat(mappingLocations.get("/webjars/**")).hasSize(1);
assertThat(mappingLocations.get("/webjars/**").get(0))
.isEqualTo(new ClassPathResource("/META-INF/resources/webjars/"));
assertThat(getResourceResolvers("/webjars/**")).hasSize(1);
assertThat(getResourceTransformers("/webjars/**")).hasSize(0);
assertThat(getResourceResolvers("/**")).hasSize(1);
assertThat(getResourceTransformers("/**")).hasSize(0);
}
@Test
public void customResourceHandlerMapping() throws Exception {
load("spring.mvc.static-path-pattern:/static/**");
Map<String, List<Resource>> mappingLocations = getResourceMappingLocations();
assertThat(mappingLocations.get("/static/**")).hasSize(5);
assertThat(getResourceResolvers("/static/**")).hasSize(1);
}
@Test
public void resourceHandlerMappingOverrideWebjars() throws Exception {
load(WebJars.class);
Map<String, List<Resource>> mappingLocations = getResourceMappingLocations();
assertThat(mappingLocations.get("/webjars/**")).hasSize(1);
assertThat(mappingLocations.get("/webjars/**").get(0))
.isEqualTo(new ClassPathResource("/foo/"));
}
@Test
public void resourceHandlerMappingOverrideAll() throws Exception {
load(AllResources.class);
Map<String, List<Resource>> mappingLocations = getResourceMappingLocations();
assertThat(mappingLocations.get("/**")).hasSize(1);
assertThat(mappingLocations.get("/**").get(0))
.isEqualTo(new ClassPathResource("/foo/"));
}
@Test
public void resourceHandlerMappingDisabled() throws Exception {
load("spring.resources.add-mappings:false");
Map<String, List<Resource>> mappingLocations = getResourceMappingLocations();
assertThat(mappingLocations.size()).isEqualTo(0);
}
@Test
public void resourceHandlerChainEnabled() throws Exception {
load("spring.resources.chain.enabled:true");
assertThat(getResourceResolvers("/webjars/**")).hasSize(2);
assertThat(getResourceTransformers("/webjars/**")).hasSize(1);
assertThat(getResourceResolvers("/**")).extractingResultOf("getClass")
.containsOnly(CachingResourceResolver.class, PathResourceResolver.class);
assertThat(getResourceTransformers("/**")).extractingResultOf("getClass")
.containsOnly(CachingResourceTransformer.class);
}
@Test
public void resourceHandlerFixedStrategyEnabled() throws Exception {
load("spring.resources.chain.strategy.fixed.enabled:true",
"spring.resources.chain.strategy.fixed.version:test",
"spring.resources.chain.strategy.fixed.paths:/**/*.js");
assertThat(getResourceResolvers("/webjars/**")).hasSize(3);
assertThat(getResourceTransformers("/webjars/**")).hasSize(2);
assertThat(getResourceResolvers("/**")).extractingResultOf("getClass")
.containsOnly(CachingResourceResolver.class,
VersionResourceResolver.class, PathResourceResolver.class);
assertThat(getResourceTransformers("/**")).extractingResultOf("getClass")
.containsOnly(CachingResourceTransformer.class,
CssLinkResourceTransformer.class);
VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers(
"/**").get(1);
assertThat(resolver.getStrategyMap().get("/**/*.js"))
.isInstanceOf(FixedVersionStrategy.class);
}
@Test
public void resourceHandlerContentStrategyEnabled() throws Exception {
load("spring.resources.chain.strategy.content.enabled:true",
"spring.resources.chain.strategy.content.paths:/**,/*.png");
assertThat(getResourceResolvers("/webjars/**")).hasSize(3);
assertThat(getResourceTransformers("/webjars/**")).hasSize(2);
assertThat(getResourceResolvers("/**")).extractingResultOf("getClass")
.containsOnly(CachingResourceResolver.class,
VersionResourceResolver.class, PathResourceResolver.class);
assertThat(getResourceTransformers("/**")).extractingResultOf("getClass")
.containsOnly(CachingResourceTransformer.class,
CssLinkResourceTransformer.class);
VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers(
"/**").get(1);
assertThat(resolver.getStrategyMap().get("/*.png"))
.isInstanceOf(ContentVersionStrategy.class);
}
@Test
public void resourceHandlerChainCustomized() throws Exception {
load("spring.resources.chain.enabled:true", "spring.resources.chain.cache:false",
"spring.resources.chain.strategy.content.enabled:true",
"spring.resources.chain.strategy.content.paths:/**,/*.png",
"spring.resources.chain.strategy.fixed.enabled:true",
"spring.resources.chain.strategy.fixed.version:test",
"spring.resources.chain.strategy.fixed.paths:/**/*.js",
"spring.resources.chain.html-application-cache:true",
"spring.resources.chain.gzipped:true");
assertThat(getResourceResolvers("/webjars/**")).hasSize(3);
assertThat(getResourceTransformers("/webjars/**")).hasSize(2);
assertThat(getResourceResolvers("/**")).extractingResultOf("getClass")
.containsOnly(VersionResourceResolver.class, GzipResourceResolver.class,
PathResourceResolver.class);
assertThat(getResourceTransformers("/**")).extractingResultOf("getClass")
.containsOnly(CssLinkResourceTransformer.class,
AppCacheManifestTransformer.class);
VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers(
"/**").get(0);
assertThat(resolver.getStrategyMap().get("/*.png"))
.isInstanceOf(ContentVersionStrategy.class);
assertThat(resolver.getStrategyMap().get("/**/*.js"))
.isInstanceOf(FixedVersionStrategy.class);
}
@Test
public void noLocaleResolver() throws Exception {
load(AllResources.class);
this.thrown.expect(NoSuchBeanDefinitionException.class);
this.context.getBean(LocaleResolver.class);
}
@Test
public void overrideLocale() throws Exception {
load(AllResources.class, "spring.mvc.locale:en_UK",
"spring.mvc.locale-resolver=fixed");
// mock request and set user preferred locale
MockHttpServletRequest request = new MockHttpServletRequest();
request.addPreferredLocale(StringUtils.parseLocaleString("nl_NL"));
request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "nl_NL");
LocaleResolver localeResolver = this.context.getBean(LocaleResolver.class);
assertThat(localeResolver).isInstanceOf(FixedLocaleResolver.class);
Locale locale = localeResolver.resolveLocale(request);
// test locale resolver uses fixed locale and not user preferred locale
assertThat(locale.toString()).isEqualTo("en_UK");
}
@Test
public void useAcceptHeaderLocale() {
load(AllResources.class, "spring.mvc.locale:en_UK");
// mock request and set user preferred locale
MockHttpServletRequest request = new MockHttpServletRequest();
request.addPreferredLocale(StringUtils.parseLocaleString("nl_NL"));
request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "nl_NL");
LocaleResolver localeResolver = this.context.getBean(LocaleResolver.class);
assertThat(localeResolver).isInstanceOf(AcceptHeaderLocaleResolver.class);
Locale locale = localeResolver.resolveLocale(request);
// test locale resolver uses user preferred locale
assertThat(locale.toString()).isEqualTo("nl_NL");
}
@Test
public void useDefaultLocaleIfAcceptHeaderNoSet() {
load(AllResources.class, "spring.mvc.locale:en_UK");
// mock request and set user preferred locale
MockHttpServletRequest request = new MockHttpServletRequest();
LocaleResolver localeResolver = this.context.getBean(LocaleResolver.class);
assertThat(localeResolver).isInstanceOf(AcceptHeaderLocaleResolver.class);
Locale locale = localeResolver.resolveLocale(request);
// test locale resolver uses default locale if no header is set
assertThat(locale.toString()).isEqualTo("en_UK");
}
@Test
public void noDateFormat() throws Exception {
load(AllResources.class);
FormattingConversionService cs = this.context
.getBean(FormattingConversionService.class);
Date date = new DateTime(1988, 6, 25, 20, 30).toDate();
// formatting cs should use simple toString()
assertThat(cs.convert(date, String.class)).isEqualTo(date.toString());
}
@Test
public void overrideDateFormat() throws Exception {
load(AllResources.class, "spring.mvc.date-format:dd*MM*yyyy");
FormattingConversionService cs = this.context
.getBean(FormattingConversionService.class);
Date date = new DateTime(1988, 6, 25, 20, 30).toDate();
assertThat(cs.convert(date, String.class)).isEqualTo("25*06*1988");
}
@Test
public void noMessageCodesResolver() throws Exception {
load(AllResources.class);
assertThat(this.context.getBean(WebMvcAutoConfigurationAdapter.class)
.getMessageCodesResolver()).isNull();
}
@Test
public void overrideMessageCodesFormat() throws Exception {
load(AllResources.class,
"spring.mvc.messageCodesResolverFormat:POSTFIX_ERROR_CODE");
assertThat(this.context.getBean(WebMvcAutoConfigurationAdapter.class)
.getMessageCodesResolver()).isNotNull();
}
protected Map<String, List<Resource>> getFaviconMappingLocations()
throws IllegalAccessException {
HandlerMapping mapping = (HandlerMapping) this.context
.getBean("faviconHandlerMapping");
return getMappingLocations(mapping);
}
protected Map<String, List<Resource>> getResourceMappingLocations()
throws IllegalAccessException {
HandlerMapping mapping = (HandlerMapping) this.context
.getBean("resourceHandlerMapping");
return getMappingLocations(mapping);
}
protected List<ResourceResolver> getResourceResolvers(String mapping) {
SimpleUrlHandlerMapping handler = (SimpleUrlHandlerMapping) this.context
.getBean("resourceHandlerMapping");
ResourceHttpRequestHandler resourceHandler = (ResourceHttpRequestHandler) handler
.getHandlerMap().get(mapping);
return resourceHandler.getResourceResolvers();
}
protected List<ResourceTransformer> getResourceTransformers(String mapping) {
SimpleUrlHandlerMapping handler = (SimpleUrlHandlerMapping) this.context
.getBean("resourceHandlerMapping");
ResourceHttpRequestHandler resourceHandler = (ResourceHttpRequestHandler) handler
.getHandlerMap().get(mapping);
return resourceHandler.getResourceTransformers();
}
@SuppressWarnings("unchecked")
protected Map<String, List<Resource>> getMappingLocations(HandlerMapping mapping)
throws IllegalAccessException {
Map<String, List<Resource>> mappingLocations = new LinkedHashMap<>();
if (mapping instanceof SimpleUrlHandlerMapping) {
Field locationsField = ReflectionUtils
.findField(ResourceHttpRequestHandler.class, "locations");
locationsField.setAccessible(true);
for (Map.Entry<String, Object> entry : ((SimpleUrlHandlerMapping) mapping)
.getHandlerMap().entrySet()) {
ResourceHttpRequestHandler handler = (ResourceHttpRequestHandler) entry
.getValue();
mappingLocations.put(entry.getKey(),
(List<Resource>) locationsField.get(handler));
}
}
return mappingLocations;
}
@Test
public void ignoreDefaultModelOnRedirectIsTrue() throws Exception {
load();
RequestMappingHandlerAdapter adapter = this.context
.getBean(RequestMappingHandlerAdapter.class);
assertThat(adapter).extracting("ignoreDefaultModelOnRedirect")
.containsExactly(true);
}
@Test
public void overrideIgnoreDefaultModelOnRedirect() throws Exception {
this.context = new AnnotationConfigServletWebServerApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context,
"spring.mvc.ignore-default-model-on-redirect:false");
this.context.register(Config.class, WebMvcAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
RequestMappingHandlerAdapter adapter = this.context
.getBean(RequestMappingHandlerAdapter.class);
assertThat(adapter).extracting("ignoreDefaultModelOnRedirect")
.containsExactly(false);
}
@Test
public void customViewResolver() throws Exception {
load(CustomViewResolver.class);
assertThat(this.context.getBean("viewResolver"))
.isInstanceOf(MyViewResolver.class);
}
@Test
public void customContentNegotiatingViewResolver() throws Exception {
load(CustomContentNegotiatingViewResolver.class);
Map<String, ContentNegotiatingViewResolver> beans = this.context
.getBeansOfType(ContentNegotiatingViewResolver.class);
assertThat(beans.size()).isEqualTo(1);
assertThat(beans.keySet().iterator().next()).isEqualTo("myViewResolver");
}
@Test
public void faviconMapping() throws IllegalAccessException {
load();
assertThat(this.context.getBeansOfType(ResourceHttpRequestHandler.class)
.get("faviconRequestHandler")).isNotNull();
assertThat(this.context.getBeansOfType(SimpleUrlHandlerMapping.class)
.get("faviconHandlerMapping")).isNotNull();
Map<String, List<Resource>> mappingLocations = getFaviconMappingLocations();
assertThat(mappingLocations.get("/**/favicon.ico")).hasSize(6);
}
@Test
public void faviconMappingUsesStaticLocations() throws IllegalAccessException {
load("spring.resources.static-locations=classpath:/static");
Map<String, List<Resource>> mappingLocations = getFaviconMappingLocations();
assertThat(mappingLocations.get("/**/favicon.ico")).hasSize(2);
}
@Test
public void faviconMappingDisabled() throws IllegalAccessException {
load("spring.mvc.favicon.enabled:false");
assertThat(this.context.getBeansOfType(ResourceHttpRequestHandler.class)
.get("faviconRequestHandler")).isNull();
assertThat(this.context.getBeansOfType(SimpleUrlHandlerMapping.class)
.get("faviconHandlerMapping")).isNull();
}
@Test
public void defaultAsyncRequestTimeout() throws Exception {
load();
RequestMappingHandlerAdapter adapter = this.context
.getBean(RequestMappingHandlerAdapter.class);
assertThat(ReflectionTestUtils.getField(adapter, "asyncRequestTimeout")).isNull();
}
@Test
public void customAsyncRequestTimeout() throws Exception {
load("spring.mvc.async.request-timeout:123456");
RequestMappingHandlerAdapter adapter = this.context
.getBean(RequestMappingHandlerAdapter.class);
Object actual = ReflectionTestUtils.getField(adapter, "asyncRequestTimeout");
assertThat(actual).isEqualTo(123456L);
}
@Test
public void customMediaTypes() throws Exception {
load("spring.mvc.mediaTypes.yaml:text/yaml");
RequestMappingHandlerAdapter adapter = this.context
.getBean(RequestMappingHandlerAdapter.class);
ContentNegotiationManager actual = (ContentNegotiationManager) ReflectionTestUtils
.getField(adapter, "contentNegotiationManager");
assertThat(actual.getAllFileExtensions().contains("yaml")).isTrue();
}
@Test
public void httpPutFormContentFilterIsAutoConfigured() {
load();
assertThat(this.context.getBeansOfType(OrderedHttpPutFormContentFilter.class))
.hasSize(1);
}
@Test
public void httpPutFormContentFilterCanBeOverridden() {
load(CustomHttpPutFormContentFilter.class);
assertThat(this.context.getBeansOfType(OrderedHttpPutFormContentFilter.class))
.hasSize(0);
assertThat(this.context.getBeansOfType(HttpPutFormContentFilter.class))
.hasSize(1);
}
@Test
public void httpPutFormContentFilterCanBeDisabled() throws Exception {
load((Class<?>) null, "spring.mvc.formcontent.putfilter.enabled=false");
assertThat(this.context.getBeansOfType(HttpPutFormContentFilter.class)).isEmpty();
}
@Test
public void customConfigurableWebBindingInitializer() {
load(CustomConfigurableWebBindingInitializer.class);
assertThat(this.context.getBean(RequestMappingHandlerAdapter.class)
.getWebBindingInitializer())
.isInstanceOf(CustomWebBindingInitializer.class);
}
@Test
public void customRequestMappingHandlerMapping() {
load(CustomRequestMappingHandlerMapping.class);
assertThat(this.context.getBean(RequestMappingHandlerMapping.class))
.isInstanceOf(MyRequestMappingHandlerMapping.class);
}
@Test
public void customRequestMappingHandlerAdapter() {
load(CustomRequestMappingHandlerAdapter.class);
assertThat(this.context.getBean(RequestMappingHandlerAdapter.class))
.isInstanceOf(MyRequestMappingHandlerAdapter.class);
}
@Test
public void multipleWebMvcRegistrations() {
load(MultipleWebMvcRegistrations.class);
assertThat(this.context.getBean(RequestMappingHandlerMapping.class))
.isNotInstanceOf(MyRequestMappingHandlerMapping.class);
assertThat(this.context.getBean(RequestMappingHandlerAdapter.class))
.isNotInstanceOf(MyRequestMappingHandlerAdapter.class);
}
@Test
public void defaultLogResolvedException() {
load();
testLogResolvedExceptionCustomization(false);
}
@Test
public void customLogResolvedException() {
load("spring.mvc.log-resolved-exception:true");
testLogResolvedExceptionCustomization(true);
}
@Test
public void welcomePageMappingProducesNotFoundResponseWhenThereIsNoWelcomePage()
throws Exception {
load("spring.resources.static-locations:classpath:/no-welcome-page/");
assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class))
.hasSize(1);
MockMvcBuilders.webAppContextSetup(this.context).build()
.perform(get("/").accept(MediaType.TEXT_HTML))
.andExpect(status().isNotFound());
}
@Test
public void welcomePageRootHandlerIsNotRegisteredWhenStaticPathPatternIsNotSlashStarStar() {
load("spring.resources.static-locations:classpath:/welcome-page/",
"spring.mvc.static-path-pattern:/foo/**");
WelcomePageHandlerMapping welcomePageHandlerMapping = this.context
.getBean(WelcomePageHandlerMapping.class);
assertThat(welcomePageHandlerMapping.getRootHandler()).isNull();
}
@Test
public void welcomePageMappingHandlesRequestsThatAcceptTextHtml() throws Exception {
load("spring.resources.static-locations:classpath:/welcome-page/");
assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class))
.hasSize(1);
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
mockMvc.perform(get("/").accept(MediaType.TEXT_HTML)).andExpect(status().isOk())
.andExpect(forwardedUrl("index.html"));
mockMvc.perform(get("/").accept("*/*")).andExpect(status().isOk())
.andExpect(forwardedUrl("index.html"));
}
@Test
public void welcomePageMappingDoesNotHandleRequestsThatDoNotAcceptTextHtml()
throws Exception {
load("spring.resources.static-locations:classpath:/welcome-page/");
assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class))
.hasSize(1);
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
}
@Test
public void welcomePageMappingHandlesRequestsWithNoAcceptHeader() throws Exception {
load("spring.resources.static-locations:classpath:/welcome-page/");
assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class))
.hasSize(1);
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
mockMvc.perform(get("/")).andExpect(status().isOk())
.andExpect(forwardedUrl("index.html"));
}
@Test
public void welcomePageMappingHandlesRequestsWithEmptyAcceptHeader()
throws Exception {
load("spring.resources.static-locations:classpath:/welcome-page/");
assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class))
.hasSize(1);
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
mockMvc.perform(get("/").header(HttpHeaders.ACCEPT, ""))
.andExpect(status().isOk()).andExpect(forwardedUrl("index.html"));
}
@Test
public void welcomePageMappingWorksWithNoTrailingSlashOnResourceLocation()
throws Exception {
load("spring.resources.static-locations:classpath:/welcome-page");
assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class))
.hasSize(1);
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
mockMvc.perform(get("/").accept(MediaType.TEXT_HTML)).andExpect(status().isOk())
.andExpect(forwardedUrl("index.html"));
}
private void testLogResolvedExceptionCustomization(final boolean expected) {
HandlerExceptionResolver exceptionResolver = this.context
.getBean(HandlerExceptionResolver.class);
assertThat(exceptionResolver)
.isInstanceOf(HandlerExceptionResolverComposite.class);
List<HandlerExceptionResolver> delegates = ((HandlerExceptionResolverComposite) exceptionResolver)
.getExceptionResolvers();
for (HandlerExceptionResolver delegate : delegates) {
if (delegate instanceof AbstractHandlerMethodAdapter) {
assertThat(
new DirectFieldAccessor(delegate).getPropertyValue("warnLogger"))
.is(new Condition<Object>() {
@Override
public boolean matches(Object value) {
return (expected ? value != null : value == null);
}
});
}
}
}
@Test
public void validatorWhenNoValidatorShouldUseDefault() {
load(null, new Class<?>[] { ValidationAutoConfiguration.class });
assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
.isEmpty();
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(springValidatorBeans).containsExactly("mvcValidator");
}
@Test
public void validatorWhenNoCustomizationShouldUseAutoConfigured() {
load();
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(jsrValidatorBeans).containsExactly("defaultValidator");
assertThat(springValidatorBeans).containsExactly("defaultValidator", "mvcValidator");
Validator validator = this.context.getBean("mvcValidator", Validator.class);
assertThat(validator).isInstanceOf(ValidatorAdapter.class);
Object defaultValidator = this.context.getBean("defaultValidator");
assertThat(((ValidatorAdapter) validator).getTarget()).isSameAs(defaultValidator);
// Primary Spring validator is the one use by MVC behind the scenes
assertThat(this.context.getBean(Validator.class)).isEqualTo(defaultValidator);
}
@Test
public void validatorWithConfigurerShouldUseSpringValidator() {
load(MvcValidator.class, new Class<?>[] { ValidationAutoConfiguration.class });
assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
.isEmpty();
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(springValidatorBeans).containsExactly("mvcValidator");
assertThat(this.context.getBean("mvcValidator"))
.isSameAs(this.context.getBean(MvcValidator.class).validator);
}
@Test
public void validatorWithConfigurerDoesNotExposeJsr303() {
load(MvcJsr303Validator.class, new Class<?>[] { ValidationAutoConfiguration.class });
assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
.isEmpty();
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(springValidatorBeans).containsExactly("mvcValidator");
Validator validator = this.context.getBean("mvcValidator", Validator.class);
assertThat(validator).isInstanceOf(ValidatorAdapter.class);
assertThat(((ValidatorAdapter) validator).getTarget())
.isSameAs(this.context.getBean(MvcJsr303Validator.class).validator);
}
@Test
public void validatorWithConfigurerTakesPrecedence() {
load(MvcValidator.class);
assertThat(this.context.getBeansOfType(ValidatorFactory.class)).hasSize(1);
assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
.hasSize(1);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(springValidatorBeans).containsExactly("defaultValidator", "mvcValidator");
assertThat(this.context.getBean("mvcValidator"))
.isSameAs(this.context.getBean(MvcValidator.class).validator);
// Primary Spring validator is the auto-configured one as the MVC one has been
// customized via a WebMvcConfigurer
assertThat(this.context.getBean(Validator.class))
.isEqualTo(this.context.getBean("defaultValidator"));
}
@Test
public void validatorWithCustomSpringValidatorIgnored() {
load(CustomSpringValidator.class);
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(jsrValidatorBeans).containsExactly("defaultValidator");
assertThat(springValidatorBeans).containsExactly(
"customSpringValidator", "defaultValidator", "mvcValidator");
Validator validator = this.context.getBean("mvcValidator", Validator.class);
assertThat(validator).isInstanceOf(ValidatorAdapter.class);
Object defaultValidator = this.context.getBean("defaultValidator");
assertThat(((ValidatorAdapter) validator).getTarget())
.isSameAs(defaultValidator);
// Primary Spring validator is the one use by MVC behind the scenes
assertThat(this.context.getBean(Validator.class)).isEqualTo(defaultValidator);
}
@Test
public void validatorWithCustomJsr303ValidatorExposedAsSpringValidator() {
load(CustomJsr303Validator.class);
assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(jsrValidatorBeans).containsExactly("customJsr303Validator");
assertThat(springValidatorBeans).containsExactly("mvcValidator");
Validator validator = this.context.getBean(Validator.class);
assertThat(validator).isInstanceOf(ValidatorAdapter.class);
Validator target = ((ValidatorAdapter) validator).getTarget();
assertThat(new DirectFieldAccessor(target).getPropertyValue("targetValidator"))
.isSameAs(this.context.getBean("customJsr303Validator"));
}
private void load(Class<?> config, String... environment) {
load(config, null, environment);
}
private void load(Class<?> config, Class<?>[] exclude, String... environment) {
this.context = new AnnotationConfigServletWebServerApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context, environment);
List<Class<?>> configClasses = new ArrayList<>();
if (config != null) {
configClasses.add(config);
}
configClasses.addAll(Arrays.asList(Config.class,
ValidationAutoConfiguration.class, WebMvcAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class));
if (!ObjectUtils.isEmpty(exclude)) {
configClasses.removeAll(Arrays.asList(exclude));
}
this.context.register(configClasses.toArray(new Class<?>[configClasses.size()]));
this.context.refresh();
}
private void load(String... environment) {
load(null, environment);
}
@Configuration
protected static class ViewConfig {
@Bean
public View jsonView() {
return new AbstractView() {
@Override
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.getOutputStream().write("Hello World".getBytes());
}
};
}
}
@Configuration
protected static class WebJars implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/foo/");
}
}
@Configuration
protected static class AllResources implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/foo/");
}
}
@Configuration
public static class Config {
@Bean
public ServletWebServerFactory webServerFactory() {
return webServerFactory;
}
@Bean
public WebServerFactoryCustomizerBeanPostProcessor ServletWebServerCustomizerBeanPostProcessor() {
return new WebServerFactoryCustomizerBeanPostProcessor();
}
}
@Configuration
public static class CustomViewResolver {
@Bean
public ViewResolver viewResolver() {
return new MyViewResolver();
}
}
@Configuration
public static class CustomContentNegotiatingViewResolver {
@Bean
public ContentNegotiatingViewResolver myViewResolver() {
return new ContentNegotiatingViewResolver();
}
}
private static class MyViewResolver implements ViewResolver {
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
@Configuration
static class CustomConfigurableWebBindingInitializer {
@Bean
public ConfigurableWebBindingInitializer customConfigurableWebBindingInitializer() {
return new CustomWebBindingInitializer();
}
}
private static class CustomWebBindingInitializer
extends ConfigurableWebBindingInitializer {
}
@Configuration
static class CustomHttpPutFormContentFilter {
@Bean
public HttpPutFormContentFilter customHttpPutFormContentFilter() {
return new HttpPutFormContentFilter();
}
}
@Configuration
static class CustomRequestMappingHandlerMapping {
@Bean
public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerMapping() {
return new WebMvcRegistrationsAdapter() {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new MyRequestMappingHandlerMapping();
}
};
}
}
private static class MyRequestMappingHandlerMapping
extends RequestMappingHandlerMapping {
}
@Configuration
static class CustomRequestMappingHandlerAdapter {
@Bean
public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerAdapter() {
return new WebMvcRegistrationsAdapter() {
@Override
public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
return new MyRequestMappingHandlerAdapter();
}
};
}
}
private static class MyRequestMappingHandlerAdapter
extends RequestMappingHandlerAdapter {
}
@Configuration
@Import({ CustomRequestMappingHandlerMapping.class,
CustomRequestMappingHandlerAdapter.class })
static class MultipleWebMvcRegistrations {
}
@Configuration
protected static class MvcValidator implements WebMvcConfigurer {
private final Validator validator = mock(Validator.class);
@Override
public Validator getValidator() {
return this.validator;
}
}
@Configuration
protected static class MvcJsr303Validator implements WebMvcConfigurer {
private final LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
@Override
public Validator getValidator() {
return this.validator;
}
}
@Configuration
static class CustomJsr303Validator {
@Bean
public javax.validation.Validator customJsr303Validator() {
return mock(javax.validation.Validator.class);
}
}
@Configuration
static class CustomSpringValidator {
@Bean
public Validator customSpringValidator() {
return mock(Validator.class);
}
}
}