diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java index ebfb201dc32..405317c394a 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java @@ -18,8 +18,6 @@ package org.springframework.web.servlet.config; import java.util.List; -import org.w3c.dom.Element; - import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -50,11 +48,12 @@ import org.springframework.web.bind.support.WebArgumentResolver; import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor; import org.springframework.web.servlet.handler.MappedInterceptor; import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver; +import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodExceptionResolver; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodMapping; import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebArgumentResolverAdapter; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; +import org.w3c.dom.Element; /** * {@link BeanDefinitionParser} that parses the {@code annotation-driven} element to configure a Spring MVC web @@ -146,7 +145,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef); String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedName(mappedCsInterceptorDef); - RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(RequestMappingHandlerMethodExceptionResolver.class); + RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class); methodExceptionResolver.setSource(source); methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters); diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/DefaultServletHandlerConfigurer.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/DefaultServletHandlerConfigurer.java new file mode 100644 index 00000000000..d4f8f78ace6 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/DefaultServletHandlerConfigurer.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2011 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.annotation; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletContext; + +import org.springframework.web.HttpRequestHandler; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; +import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler; + +/** + * Helps with configuring a handler for serving static resources by forwarding to the Servlet container's default + * Servlet. This is commonly used when the {@link DispatcherServlet} is mapped to "/", which results in cleaner + * URLs (without a servlet prefix) but may need to still allow some requests (e.g. static resources) to be handled + * by the Servlet container's default servlet. + * + *

It is important the configured handler remains last in the order of all {@link HandlerMapping} instances in + * the Spring MVC web application context. That is is the case if relying on @{@link EnableMvcConfiguration}. + * However, if you register your own HandlerMapping instance sure to set its "order" property to a value lower + * than that of the {@link DefaultServletHttpRequestHandler}, which is {@link Integer#MAX_VALUE}. + * + * @author Rossen Stoyanchev + * @since 3.1 + * + * @see ResourceConfigurer + */ +public class DefaultServletHandlerConfigurer { + + private DefaultServletHttpRequestHandler requestHandler; + + private final ServletContext servletContext; + + public DefaultServletHandlerConfigurer(ServletContext servletContext) { + this.servletContext = servletContext; + } + + /** + * Enable forwarding to the Servlet container default servlet. The {@link DefaultServletHttpRequestHandler} + * will try to auto-detect the default Servlet at startup using a list of known names. Alternatively, you can + * specify the name of the default Servlet, see {@link #enable(String)}. + */ + public void enable() { + enable(null); + } + + /** + * Enable forwarding to the Servlet container default servlet specifying explicitly the name of the default + * Servlet to forward static resource requests to. This is useful when the default Servlet cannot be detected + * (e.g. when using an unknown container or when it has been manually configured). + */ + public void enable(String defaultServletName) { + requestHandler = new DefaultServletHttpRequestHandler(); + requestHandler.setDefaultServletName(defaultServletName); + requestHandler.setServletContext(servletContext); + } + + /** + * Return a {@link SimpleUrlHandlerMapping} instance ordered at {@link Integer#MAX_VALUE} containing a + * {@link DefaultServletHttpRequestHandler} mapped to {@code /**}. + */ + protected SimpleUrlHandlerMapping getHandlerMapping() { + SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(); + handlerMapping.setOrder(Integer.MAX_VALUE); + handlerMapping.setUrlMap(getUrlMap()); + return handlerMapping; + } + + private Map getUrlMap() { + Map urlMap = new HashMap(); + if (requestHandler != null) { + urlMap.put("/**", requestHandler); + } + return urlMap ; + } + +} \ No newline at end of file diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/EnableMvcConfiguration.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/EnableMvcConfiguration.java new file mode 100644 index 00000000000..705705314f9 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/EnableMvcConfiguration.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2010 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.stereotype.Controller; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * Enables default Spring MVC configuration and registers Spring MVC infrastructure components expected by the + * {@link DispatcherServlet}. To use this annotation simply place it on an application @{@link Configuration} class + * and that will in turn import default Spring MVC configuration including support for annotated methods + * in @{@link Controller} classes. + *

+ * @Configuration
+ * @EnableMvcConfiguration
+ * @ComponentScan(
+ *	basePackageClasses = { MyMvcConfiguration.class },
+ * 	excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = Configuration.class) }
+ * )
+ * public class MyMvcConfiguration {
+ *
+ * }
+ * 
+ *

To customize the imported configuration you simply implement {@link MvcConfigurer}, or more likely extend + * {@link MvcConfigurerSupport} overriding selected methods only. The most obvious place to do this is + * the @{@link Configuration} class that enabled the Spring MVC configuration via @{@link EnableMvcConfiguration}. + * However any @{@link Configuration} class and more generally any Spring bean can implement {@link MvcConfigurer} + * to be detected and given an opportunity to customize Spring MVC configuration at startup. + *

+ * @Configuration
+ * @EnableMvcConfiguration
+ * @ComponentScan(
+ * 	basePackageClasses = { MyMvcConfiguration.class },
+ * 	excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = Configuration.class) }
+ * )
+ * public class MyMvcConfiguration extends MvcConfigurerSupport {
+ *
+ * 	@Override
+ * 	public void registerFormatters(FormatterRegistry formatterRegistry) {
+ * 		formatterRegistry.addConverter(new MyConverter());
+ * 	}
+ *
+ * 	@Override
+ * 	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
+ * 		converters.add(new MyHttpMessageConverter());
+ * 	}
+ *
+ * 	...
+ *
+ * }
+ * 
+ * + * @see MvcConfigurer + * @see MvcConfigurerSupport + * + * @author Dave Syer + * @author Rossen Stoyanchev + * @since 3.1 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@Import(MvcConfiguration.class) +public @interface EnableMvcConfiguration { +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorConfigurer.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorConfigurer.java new file mode 100644 index 00000000000..e52c7b4e2a7 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorConfigurer.java @@ -0,0 +1,126 @@ +/* + * Copyright 2002-2011 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.annotation; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.util.Assert; +import org.springframework.web.context.request.WebRequestInterceptor; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.handler.MappedInterceptor; +import org.springframework.web.servlet.handler.MappedInterceptors; +import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter; + +/** + * Helps with configuring an ordered set of Spring MVC interceptors of type {@link HandlerInterceptor} or + * {@link WebRequestInterceptor}. Registered interceptors will generally be detected by all {@link HandlerMapping} + * instances in a Spring MVC web application context. Interceptors can be added with a set of path patterns to + * which they should apply. + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +public class InterceptorConfigurer { + + private final List mappedInterceptors = new ArrayList(); + + /** + * Add a {@link HandlerInterceptor} that should apply to any request. + */ + public void addInterceptor(HandlerInterceptor interceptor) { + register(null, interceptor); + } + + /** + * Add a {@link WebRequestInterceptor} that should apply to any request. + */ + public void addInterceptor(WebRequestInterceptor interceptor) { + register(null, asHandlerInterceptorArray(interceptor)); + } + + /** + * Add {@link HandlerInterceptor}s that should apply to any request. + */ + public void addInterceptors(HandlerInterceptor... interceptors) { + register(null, interceptors); + } + + /** + * Add {@link WebRequestInterceptor}s that should apply to any request. + */ + public void addInterceptors(WebRequestInterceptor... interceptors) { + register(null, asHandlerInterceptorArray(interceptors)); + } + + /** + * Add a {@link HandlerInterceptor} with a set of URL path patterns it should apply to. + */ + public void mapInterceptor(String[] pathPatterns, HandlerInterceptor interceptor) { + register(pathPatterns, interceptor); + } + + /** + * Add a {@link WebRequestInterceptor} with a set of URL path patterns it should apply to. + */ + public void mapInterceptor(String[] pathPatterns, WebRequestInterceptor interceptors) { + register(pathPatterns, asHandlerInterceptorArray(interceptors)); + } + + /** + * Add {@link HandlerInterceptor}s with a set of URL path patterns they should apply to. + */ + public void mapInterceptors(String[] pathPatterns, HandlerInterceptor... interceptors) { + register(pathPatterns, interceptors); + } + + /** + * Add {@link WebRequestInterceptor}s with a set of URL path patterns they should apply to. + */ + public void mapInterceptors(String[] pathPatterns, WebRequestInterceptor... interceptors) { + register(pathPatterns, asHandlerInterceptorArray(interceptors)); + } + + private static HandlerInterceptor[] asHandlerInterceptorArray(WebRequestInterceptor...interceptors) { + HandlerInterceptor[] result = new HandlerInterceptor[interceptors.length]; + for (int i = 0; i < result.length; i++) { + result[i] = new WebRequestHandlerInterceptorAdapter(interceptors[i]); + } + return result; + } + + /** + * Stores the given set of {@link HandlerInterceptor}s and path patterns internally. + * @param pathPatterns path patterns or {@code null} + * @param interceptors one or more interceptors to be stored + */ + protected void register(String[] pathPatterns, HandlerInterceptor...interceptors) { + Assert.notEmpty(interceptors, "At least one interceptor must be provided"); + for (HandlerInterceptor interceptor : interceptors) { + mappedInterceptors.add(new MappedInterceptor(pathPatterns, interceptor)); + } + } + + /** + * Returns a {@link MappedInterceptors} instance with all registered interceptors. + */ + protected MappedInterceptors getMappedInterceptors() { + return new MappedInterceptors(mappedInterceptors.toArray(new MappedInterceptor[mappedInterceptors.size()])); + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/MvcConfiguration.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/MvcConfiguration.java new file mode 100644 index 00000000000..888ab7089d3 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/MvcConfiguration.java @@ -0,0 +1,309 @@ +/* + * Copyright 2002-2011 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.annotation; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletContext; +import javax.xml.transform.Source; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.format.support.FormattingConversionService; +import org.springframework.http.converter.ByteArrayHttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.ResourceHttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter; +import org.springframework.http.converter.feed.RssChannelHttpMessageConverter; +import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; +import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; +import org.springframework.http.converter.xml.SourceHttpMessageConverter; +import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter; +import org.springframework.util.ClassUtils; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; +import org.springframework.web.HttpRequestHandler; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; +import org.springframework.web.context.ServletContextAware; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; +import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor; +import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite; +import org.springframework.web.servlet.handler.MappedInterceptors; +import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; +import org.springframework.web.servlet.mvc.Controller; +import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; +import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter; +import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver; +import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodMapping; +import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; + +/** + * Provides default configuration for Spring MVC applications. Registers Spring MVC infrastructure components to be + * detected by the {@link DispatcherServlet}. Further below is a list of registered instances. This configuration is + * enabled through the {@link EnableMvcConfiguration} annotation. + * + *

A number of options are available for customizing the default configuration provided by this class. + * See {@link EnableMvcConfiguration} and {@link MvcConfigurer} for details. + * + *

Registers these handler mappings: + *

+ * + *

Note: that the SimpleUrlHandlerMapping instances above will have empty URL maps and + * hence no effect until explicitly configured via {@link MvcConfigurer}. + * + *

Registers these handler adapters: + *

+ * + *

Registers a {@link HandlerExceptionResolverComposite} with this chain of exception resolvers: + *

+ * + *

Registers the following others: + *

+ * + * @see EnableMvcConfiguration + * @see MvcConfigurer + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +@Configuration +class MvcConfiguration implements ApplicationContextAware, ServletContextAware { + + private final MvcConfigurerComposite configurers = new MvcConfigurerComposite(); + + private ServletContext servletContext; + + private ApplicationContext applicationContext; + + @Autowired(required = false) + public void setConfigurers(List configurers) { + this.configurers.addConfigurers(configurers); + } + + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Bean + RequestMappingHandlerMethodMapping requestMappingHandlerMapping() { + RequestMappingHandlerMethodMapping mapping = new RequestMappingHandlerMethodMapping(); + mapping.setOrder(0); + return mapping; + } + + @Bean + HandlerMapping viewControllerHandlerMapping() { + ViewControllerConfigurer configurer = new ViewControllerConfigurer(); + configurer.setOrder(1); + configurers.addViewControllers(configurer); + return configurer.getHandlerMapping(); + } + + @Bean + BeanNameUrlHandlerMapping beanNameHandlerMapping() { + BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping(); + mapping.setOrder(2); + return mapping; + } + + @Bean + HandlerMapping resourceHandlerMapping() { + ResourceConfigurer configurer = new ResourceConfigurer(applicationContext, servletContext); + configurer.setOrder(Integer.MAX_VALUE-1); + configurers.configureResourceHandling(configurer); + return configurer.getHandlerMapping(); + } + + @Bean + HandlerMapping defaultServletHandlerMapping() { + DefaultServletHandlerConfigurer configurer = new DefaultServletHandlerConfigurer(servletContext); + configurers.configureDefaultServletHandling(configurer); + return configurer.getHandlerMapping(); + } + + @Bean + RequestMappingHandlerMethodAdapter requestMappingHandlerAdapter() { + RequestMappingHandlerMethodAdapter adapter = new RequestMappingHandlerMethodAdapter(); + + ConfigurableWebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer(); + bindingInitializer.setConversionService(conversionService()); + bindingInitializer.setValidator(validator()); + adapter.setWebBindingInitializer(bindingInitializer); + + List argumentResolvers = new ArrayList(); + configurers.addCustomArgumentResolvers(argumentResolvers); + adapter.setCustomArgumentResolvers(argumentResolvers); + + List returnValueHandlers = new ArrayList(); + configurers.addCustomReturnValueHandlers(returnValueHandlers); + adapter.setCustomReturnValueHandlers(returnValueHandlers); + + List> converters = getDefaultHttpMessageConverters(); + configurers.configureMessageConverters(converters); + adapter.setMessageConverters(converters); + + return adapter; + } + + @Bean(name="mvcConversionService") + FormattingConversionService conversionService() { + FormattingConversionService conversionService = new DefaultFormattingConversionService(); + configurers.registerFormatters(conversionService); + return conversionService; + } + + @Bean(name="mvcValidator") + Validator validator() { + Validator validator = configurers.getValidator(); + if (validator != null) { + return validator; + } + else if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) { + Class clazz; + try { + String className = "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"; + clazz = ClassUtils.forName(className, MvcConfiguration.class.getClassLoader()); + } catch (ClassNotFoundException e) { + throw new BeanInitializationException("Could not find default validator"); + } catch (LinkageError e) { + throw new BeanInitializationException("Could not find default validator"); + } + return (Validator) BeanUtils.instantiate(clazz); + } + else { + return new Validator() { + public void validate(Object target, Errors errors) { + } + + public boolean supports(Class clazz) { + return false; + } + }; + } + } + + private List> getDefaultHttpMessageConverters() { + StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(); + stringConverter.setWriteAcceptCharset(false); + + List> converters = new ArrayList>(); + converters.add(new ByteArrayHttpMessageConverter()); + converters.add(stringConverter); + converters.add(new ResourceHttpMessageConverter()); + converters.add(new SourceHttpMessageConverter()); + converters.add(new XmlAwareFormHttpMessageConverter()); + + ClassLoader classLoader = getClass().getClassLoader(); + if (ClassUtils.isPresent("javax.xml.bind.Binder", classLoader)) { + converters.add(new Jaxb2RootElementHttpMessageConverter()); + } + if (ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", classLoader)) { + converters.add(new MappingJacksonHttpMessageConverter()); + } + if (ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", classLoader)) { + converters.add(new AtomFeedHttpMessageConverter()); + converters.add(new RssChannelHttpMessageConverter()); + } + + return converters; + } + + @Bean + HttpRequestHandlerAdapter httpRequestHandlerAdapter() { + return new HttpRequestHandlerAdapter(); + } + + @Bean + SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() { + return new SimpleControllerHandlerAdapter(); + } + + @Bean + HandlerExceptionResolver handlerExceptionResolver() throws Exception { + List resolvers = new ArrayList(); + resolvers.add(createExceptionHandlerExceptionResolver()); + resolvers.add(new ResponseStatusExceptionResolver()); + resolvers.add(new DefaultHandlerExceptionResolver()); + configurers.configureHandlerExceptionResolvers(resolvers); + + HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite(); + composite.setOrder(0); + composite.setExceptionResolvers(resolvers); + return composite; + } + + private HandlerExceptionResolver createExceptionHandlerExceptionResolver() throws Exception { + ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver(); + + List> converters = getDefaultHttpMessageConverters(); + configurers.configureMessageConverters(converters); + resolver.setMessageConverters(converters); + resolver.setOrder(0); + + resolver.afterPropertiesSet(); + return resolver; + } + + @Bean + MappedInterceptors mappedInterceptors() { + InterceptorConfigurer configurer = new InterceptorConfigurer(); + configurer.addInterceptor(new ConversionServiceExposingInterceptor(conversionService())); + configurers.addInterceptors(configurer); + return configurer.getMappedInterceptors(); + } + +} \ No newline at end of file diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/MvcConfigurer.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/MvcConfigurer.java new file mode 100644 index 00000000000..8537d713066 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/MvcConfigurer.java @@ -0,0 +1,121 @@ +/* + * Copyright 2002-2011 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.annotation; + +import java.util.List; + +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; +import org.springframework.format.Formatter; +import org.springframework.format.FormatterRegistry; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.validation.Validator; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.request.WebRequestInterceptor; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.HandlerInterceptor; + +import com.sun.corba.se.impl.presentation.rmi.ExceptionHandler; + +/** + * Defines options for customizing or adding to the default Spring MVC configuration enabled through the use + * of @{@link EnableMvcConfiguration}. The @{@link Configuration} class annotated with @{@link EnableMvcConfiguration} + * is the most obvious place to implement this interface. However all @{@link Configuration} classes and more generally + * all Spring beans that implement this interface will be detected at startup and given a chance to customize Spring + * MVC configuration provided it is enabled through @{@link EnableMvcConfiguration}. + * + *

Implementations of this interface will find it convenient to extend {@link MvcConfigurerSupport} that + * provides default method implementations and allows overriding only methods of interest. + * + * @author Rossen Stoyanchev + * @author Keith Donald + * @author David Syer + * @since 3.1 + */ +public interface MvcConfigurer { + + /** + * Register application-specific {@link Converter}s and {@link Formatter}s for use in Spring MVC. + */ + void registerFormatters(FormatterRegistry formatterRegistry); + + /** + * Customize the list of {@link HttpMessageConverter}s to use when resolving method arguments or handling + * return values from @{@link RequestMapping} and @{@link ExceptionHandler} methods. + * @param converters the list of converters, initially populated with the default set of converters + */ + void configureMessageConverters(List> converters); + + /** + * Provide a custom {@link Validator} type replacing the one that would be created by default otherwise. If this + * method returns {@code null}, and assuming a JSR-303 implementation is available on the classpath, a validator + * of type {@link org.springframework.validation.beanvalidation.LocalValidatorFactoryBean} is created by default. + */ + Validator getValidator(); + + /** + * Add custom {@link HandlerMethodArgumentResolver}s to use for resolving argument values + * on @{@link RequestMapping} and @{@link ExceptionHandler} methods. + * @param argumentResolvers the list of custom converters, initially empty + */ + void addCustomArgumentResolvers(List argumentResolvers); + + /** + * Add custom {@link HandlerMethodReturnValueHandler}s to use for handling return values + * from @{@link RequestMapping} and @{@link ExceptionHandler} methods. + * @param returnValueHandlers the list of custom handlers, initially empty + */ + void addCustomReturnValueHandlers(List returnValueHandlers); + + /** + * Customize the list of {@link HandlerExceptionResolver}s to use for handling controller exceptions. + * @param exceptionResolvers the list of resolvers, initially populated with the default set of resolvers + */ + void configureHandlerExceptionResolvers(List exceptionResolvers); + + /** + * Add Spring MVC interceptors. Interceptors can be of type {@link HandlerInterceptor} or + * {@link WebRequestInterceptor}. They allow requests to be pre/post processed before/after controller + * invocation. Interceptors can be registered to apply to all requests or limited to a set of path patterns. + * @see InterceptorConfigurer + */ + void addInterceptors(InterceptorConfigurer interceptorConfigurer); + + /** + * Map URL paths to view names. This is convenient when a request can be rendered without a controller. + */ + void addViewControllers(ViewControllerConfigurer viewControllerConfigurer); + + /** + * Configure a handler for serving static resources such as images, js, and, css files through Spring MVC + * including setting cache headers optimized for efficient loading in a web browser. Resources can be served + * out of locations under web application root, from the classpath, and others. + */ + void configureResourceHandling(ResourceConfigurer resourceConfigurer); + + /** + * Configure a handler for delegating unhandled requests by forwarding to the Servlet container's default + * servlet. This is commonly used when the {@link DispatcherServlet} is mapped to "/", which results in + * cleaner URLs (without a servlet prefix) but may need to still allow some requests (e.g. static resources) + * to be handled by the Servlet container's default servlet. + */ + void configureDefaultServletHandling(DefaultServletHandlerConfigurer handlerConfigurer); + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/MvcConfigurerComposite.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/MvcConfigurerComposite.java new file mode 100644 index 00000000000..1d90bdf9ee2 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/MvcConfigurerComposite.java @@ -0,0 +1,121 @@ +/* + * Copyright 2002-2011 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.annotation; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.format.FormatterRegistry; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.validation.Validator; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.servlet.HandlerExceptionResolver; + +/** + * An {@link MvcConfigurer} implementation that delegates to other {@link MvcConfigurer} instances. + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +class MvcConfigurerComposite implements MvcConfigurer { + + private final List configurers = new ArrayList(); + + void addConfigurers(List configurers) { + if (configurers != null) { + this.configurers.addAll(configurers); + } + } + + public void registerFormatters(FormatterRegistry formatterRegistry) { + for (MvcConfigurer configurer : configurers) { + configurer.registerFormatters(formatterRegistry); + } + } + + public void configureMessageConverters(List> converters) { + for (MvcConfigurer configurer : configurers) { + configurer.configureMessageConverters(converters); + } + } + + public void addCustomArgumentResolvers(List argumentResolvers) { + for (MvcConfigurer configurer : configurers) { + configurer.addCustomArgumentResolvers(argumentResolvers); + } + } + + public void addCustomReturnValueHandlers(List returnValueHandlers) { + for (MvcConfigurer configurer : configurers) { + configurer.addCustomReturnValueHandlers(returnValueHandlers); + } + } + + public void configureHandlerExceptionResolvers(List exceptionResolvers) { + for (MvcConfigurer configurer : configurers) { + configurer.configureHandlerExceptionResolvers(exceptionResolvers); + } + } + + public void addInterceptors(InterceptorConfigurer interceptorRegistry) { + for (MvcConfigurer configurer : configurers) { + configurer.addInterceptors(interceptorRegistry); + } + } + + public void addViewControllers(ViewControllerConfigurer viewControllerConfigurer) { + for (MvcConfigurer configurer : configurers) { + configurer.addViewControllers(viewControllerConfigurer); + } + } + + public void configureResourceHandling(ResourceConfigurer resourceConfigurer) { + for (MvcConfigurer configurer : configurers) { + configurer.configureResourceHandling(resourceConfigurer); + } + } + + public void configureDefaultServletHandling(DefaultServletHandlerConfigurer handlerConfigurer) { + for (MvcConfigurer configurer : configurers) { + configurer.configureDefaultServletHandling(handlerConfigurer); + } + } + + public Validator getValidator() { + Map validators = new HashMap(); + for (MvcConfigurer configurer : configurers) { + Validator validator = configurer.getValidator(); + if (validator != null) { + validators.put(configurer, validator); + } + } + if (validators.size() == 0) { + return null; + } + else if (validators.size() == 1) { + return validators.values().iterator().next(); + } + else { + throw new IllegalStateException( + "Multiple custom validators provided from [" + validators.keySet() + "]"); + } + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/MvcConfigurerSupport.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/MvcConfigurerSupport.java new file mode 100644 index 00000000000..37569518c36 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/MvcConfigurerSupport.java @@ -0,0 +1,107 @@ +/* + * Copyright 2002-2011 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.annotation; + +import java.util.List; + +import org.springframework.format.FormatterRegistry; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.validation.Validator; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.HandlerExceptionResolver; + +/** + * An abstract class with empty method implementations of the {@link MvcConfigurer} interface for a simplified + * implementation of {@link MvcConfigurer} so that subclasses can override selected methods only. + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +public abstract class MvcConfigurerSupport implements MvcConfigurer { + + /** + * {@inheritDoc} + *

This implementation is empty. + */ + public void registerFormatters(FormatterRegistry formatterRegistry) { + } + + /** + * {@inheritDoc} + *

This implementation is empty. + */ + public void configureMessageConverters(List> converters) { + } + + /** + * {@inheritDoc} + *

This implementation is empty. + */ + public void configureValidator(Validator validator) { + } + + /** + * {@inheritDoc} + *

This implementation returns {@code null} + */ + public Validator getValidator() { + return null; + } + + /** + * {@inheritDoc} + *

This implementation is empty. + */ + public void addCustomArgumentResolvers(List argumentResolvers) { + } + + /** + * {@inheritDoc} + *

This implementation is empty. + */ + public void configureHandlerExceptionResolvers(List exceptionResolvers) { + } + + /** + * {@inheritDoc} + *

This implementation is empty. + */ + public void addInterceptors(InterceptorConfigurer interceptorConfigurer) { + } + + /** + * {@inheritDoc} + *

This implementation is empty. + */ + public void addViewControllers(ViewControllerConfigurer viewControllerConfigurer) { + } + + /** + * {@inheritDoc} + *

This implementation is empty. + */ + public void configureResourceHandling(ResourceConfigurer resourceConfigurer) { + } + + /** + * {@inheritDoc} + *

This implementation is empty. + */ + public void configureDefaultServletHandling(DefaultServletHandlerConfigurer handlerConfigurer) { + } + +} \ No newline at end of file diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/ResourceConfigurer.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/ResourceConfigurer.java new file mode 100644 index 00000000000..576f91f457b --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/ResourceConfigurer.java @@ -0,0 +1,197 @@ +/* + * Copyright 2002-2011 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.annotation; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletContext; + +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.web.HttpRequestHandler; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; +import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; + +/** + * Helps with configuring a handler for serving static resources such as images, css files and others through + * Spring MVC including setting cache headers optimized for efficient loading in a web browser. Resources can + * be served out of locations under web application root, from the classpath, and others. + * + *

To configure resource handling, use {@link #addPathMappings(String...)} to add one or more URL path patterns + * within the current Servlet context, to use for serving resources from the handler, such as {@code "/resources/**"}. + * + *

Then use {@link #addResourceLocations(String...)} to add one or more locations from which to serve + * static content. For example, {{@code "/"}, {@code "classpath:/META-INF/public-web-resources/"}} allows resources + * to be served both from the web application root and from any JAR on the classpath that contains a + * {@code /META-INF/public-web-resources/} directory, with resources in the web application root taking precedence. + * + *

Optionally use {@link #setCachePeriod(Integer)} to specify the cache period for the resources served by the + * handler and {@link #setOrder(int)} to set the order in which to serve requests relative to other + * {@link HandlerMapping} instances in the Spring MVC web application context. + * + * @author Rossen Stoyanchev + * @since 3.1 + * + * @see DefaultServletHandlerConfigurer + */ +public class ResourceConfigurer { + + private final List pathPatterns = new ArrayList(); + + private final List locations = new ArrayList(); + + private Integer cachePeriod; + + private int order = Integer.MAX_VALUE -1; + + private final ServletContext servletContext; + + private final ApplicationContext applicationContext; + + public ResourceConfigurer(ApplicationContext applicationContext, ServletContext servletContext) { + Assert.notNull(applicationContext, "ApplicationContext is required"); + this.applicationContext = applicationContext; + this.servletContext = servletContext; + } + + /** + * Add a URL path pattern within the current Servlet context to use for serving static resources + * using the Spring MVC {@link ResourceHttpRequestHandler}, for example {@code "/resources/**"}. + * @return the same {@link ResourceConfigurer} instance for chained method invocation + */ + public ResourceConfigurer addPathMapping(String pathPattern) { + return addPathMappings(pathPattern); + } + + /** + * Add several URL path patterns within the current Servlet context to use for serving static resources + * using the Spring MVC {@link ResourceHttpRequestHandler}, for example {@code "/resources/**"}. + * @return the same {@link ResourceConfigurer} instance for chained method invocation + */ + public ResourceConfigurer addPathMappings(String...pathPatterns) { + for (String path : pathPatterns) { + this.pathPatterns.add(path); + } + return this; + } + + /** + * Add resource location from which to serve static content. The location must point to a valid + * directory.

For example, a value of {@code "/"} will allow resources to be served both from the web + * application root. Also see {@link #addResourceLocations(String...)} for mapping several resource locations. + * @return the same {@link ResourceConfigurer} instance for chained method invocation + */ + public ResourceConfigurer addResourceLocation(String resourceLocation) { + return addResourceLocations(resourceLocation); + } + + /** + * Add one or more resource locations from which to serve static content. Each location must point to a valid + * directory. Multiple locations may be specified as a comma-separated list, and the locations will be checked + * for a given resource in the order specified. + *

For example, {{@code "/"}, {@code "classpath:/META-INF/public-web-resources/"}} allows resources to + * be served both from the web application root and from any JAR on the classpath that contains a + * {@code /META-INF/public-web-resources/} directory, with resources in the web application root taking precedence. + * @return the same {@link ResourceConfigurer} instance for chained method invocation + */ + public ResourceConfigurer addResourceLocations(String...resourceLocations) { + for (String location : resourceLocations) { + this.locations.add(applicationContext.getResource(location)); + } + return this; + } + + /** + * Specify the cache period for the resources served by the resource handler, in seconds. The default is to not + * send any cache headers but to rely on last-modified timestamps only. Set to 0 in order to send cache headers + * that prevent caching, or to a positive number of seconds to send cache headers with the given max-age value. + * @param cachePeriod the time to cache resources in seconds + * @return the same {@link ResourceConfigurer} instance for chained method invocation + */ + public ResourceConfigurer setCachePeriod(Integer cachePeriod) { + this.cachePeriod = cachePeriod; + return this; + } + + /** + * Get the cache period for static resources served by the resource handler. + */ + public Integer getCachePeriod() { + return cachePeriod; + } + + /** + * Specify the order in which to serve static resources relative to other {@link HandlerMapping} instances in the + * Spring MVC web application context. The default value is {@code Integer.MAX_VALUE-1}. + */ + public ResourceConfigurer setOrder(int order) { + this.order = order; + return this; + } + + /** + * Get the order in which to serve static resources relative other {@link HandlerMapping} instances. + * @return the same {@link ResourceConfigurer} instance for chained method invocation + */ + public Integer getOrder() { + return order; + } + + /** + * Return a {@link SimpleUrlHandlerMapping} with a {@link ResourceHttpRequestHandler} mapped to one or more + * URL path patterns. If the no path patterns were specified, the HandlerMapping returned contains an empty map. + */ + protected SimpleUrlHandlerMapping getHandlerMapping() { + SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(); + handlerMapping.setOrder(order); + handlerMapping.setUrlMap(getUrlMap()); + return handlerMapping; + } + + private Map getUrlMap() { + Map urlMap = new LinkedHashMap(); + if (!pathPatterns.isEmpty()) { + ResourceHttpRequestHandler requestHandler = createRequestHandler(); + for (String pathPattern : pathPatterns) { + urlMap.put(pathPattern, requestHandler); + } + } + return urlMap; + } + + /** + * Create a {@link ResourceHttpRequestHandler} instance. + */ + protected ResourceHttpRequestHandler createRequestHandler() { + Assert.isTrue(!CollectionUtils.isEmpty(locations), "Path patterns specified but not resource locations."); + ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler(); + requestHandler.setApplicationContext(applicationContext); + requestHandler.setServletContext(servletContext); + requestHandler.setLocations(locations); + if (cachePeriod != null) { + requestHandler.setCacheSeconds(cachePeriod); + } + return requestHandler; + } + +} \ No newline at end of file diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/ViewControllerConfigurer.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/ViewControllerConfigurer.java new file mode 100644 index 00000000000..a5946b63c26 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/ViewControllerConfigurer.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2011 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.annotation; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.RequestToViewNameTranslator; +import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; +import org.springframework.web.servlet.mvc.Controller; +import org.springframework.web.servlet.mvc.ParameterizableViewController; + +/** + * Helps with view controllers. View controllers provide a direct mapping between a URL path and view name. This is + * useful when serving requests that don't require application-specific controller logic and can be forwarded + * directly to a view for rendering. + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +public class ViewControllerConfigurer { + + private final Map urlMap = new LinkedHashMap(); + + private int order = 1; + + /** + * Map the URL path to a view name derived by convention through the DispatcherServlet's + * {@link RequestToViewNameTranslator}. + * @return the same {@link ViewControllerConfigurer} instance for convenient chained method invocation + */ + public ViewControllerConfigurer mapViewNameByConvention(String urlPath) { + return mapViewName(urlPath, null); + } + + /** + * Map the URL path to the specified view name. + * @return the same {@link ViewControllerConfigurer} instance for convenient chained method invocation + */ + public ViewControllerConfigurer mapViewName(String urlPath, String viewName) { + ParameterizableViewController controller = new ParameterizableViewController(); + controller.setViewName(viewName); + urlMap.put(urlPath, controller); + return this; + } + + /** + * Specify the order in which to check view controller path mappings relative to other {@link HandlerMapping} + * instances in the Spring MVC web application context. The default value is 1. + */ + public void setOrder(int order) { + this.order = order; + } + + /** + * Get the order in which to check view controller path mappings relative to other {@link HandlerMapping}s. + */ + public int getOrder() { + return order; + } + + /** + * Return a {@link SimpleUrlHandlerMapping} with URL path to view controllers mappings. + */ + protected SimpleUrlHandlerMapping getHandlerMapping() { + SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(); + handlerMapping.setOrder(order); + handlerMapping.setUrlMap(urlMap); + return handlerMapping; + } + +} \ No newline at end of file diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/HandlerExceptionResolverComposite.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/HandlerExceptionResolverComposite.java new file mode 100644 index 00000000000..9d3504f1831 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/HandlerExceptionResolverComposite.java @@ -0,0 +1,51 @@ +package org.springframework.web.servlet.handler; + +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.core.Ordered; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.ModelAndView; + +/** + * A {@link HandlerExceptionResolver} that delegates to a list of {@link HandlerExceptionResolver}s. + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered { + + private List resolvers; + + private int order = Ordered.LOWEST_PRECEDENCE; + + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return this.order; + } + + public void setExceptionResolvers(List exceptionResolvers) { + this.resolvers = exceptionResolvers; + } + + public ModelAndView resolveException(HttpServletRequest request, + HttpServletResponse response, + Object handler, + Exception ex) { + if (resolvers != null) { + for (HandlerExceptionResolver handlerExceptionResolver : resolvers) { + ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex); + if (mav != null) { + return mav; + } + } + } + return null; + } + +} \ No newline at end of file diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInitBinderMethodDataBinderFactory.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInitBinderMethodDataBinderFactory.java index 4c19f4d218f..f0305eb346c 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInitBinderMethodDataBinderFactory.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInitBinderMethodDataBinderFactory.java @@ -30,8 +30,10 @@ import org.springframework.web.method.support.InvocableHandlerMethod; import org.springframework.web.servlet.HandlerMapping; /** - * An {@link InitBinderMethodDataBinderFactory} that creates a {@link ServletRequestDataBinder}. - * + * An {@link InitBinderMethodDataBinderFactory} variation instantiating a data binder of type + * {@link ServletRequestDataBinder} and further extending it with the ability to add URI template variables + * to the values used in data binding. + * * @author Rossen Stoyanchev * @since 3.1 */ @@ -39,7 +41,7 @@ public class ServletInitBinderMethodDataBinderFactory extends InitBinderMethodDa /** * Create an {@link ServletInitBinderMethodDataBinderFactory} instance. - * @param initBinderMethods init binder methods to use to initialize new data binders. + * @param initBinderMethods init binder methods to use to initialize new data binders. * @param bindingInitializer a WebBindingInitializer to use to initialize created data binder instances. */ public ServletInitBinderMethodDataBinderFactory(List initBinderMethods, @@ -48,34 +50,44 @@ public class ServletInitBinderMethodDataBinderFactory extends InitBinderMethodDa } /** - * Creates a Servlet data binder. + * {@inheritDoc} + *

This method creates a {@link ServletRequestDataBinder} instance that also adds URI template variables to + * the values used in data binding. + *

Subclasses wishing to override this method to provide their own ServletRequestDataBinder type can use the + * {@link #addUriTemplateVariables(MutablePropertyValues)} method to include URI template variables as follows: + *

+	 * return new CustomServletRequestDataBinder(target, objectName) {
+	 *    protected void doBind(MutablePropertyValues mpvs) {
+	 *        addUriTemplateVariables(mpvs);
+	 *        super.doBind(mpvs);
+	 *    }
+	 * };
+	 * 
*/ @Override protected WebDataBinder createBinderInstance(Object target, String objectName) { - return new ServletRequestPathVarDataBinder(target, objectName); + return new ServletRequestDataBinder(target, objectName) { + + protected void doBind(MutablePropertyValues mpvs) { + addUriTemplateVariables(mpvs); + super.doBind(mpvs); + } + }; } /** - * Adds URI template variables to the map of request values used to do data binding. + * Adds URI template variables to the given property values. + * @param mpvs the PropertyValues to add URI template variables to */ - private static class ServletRequestPathVarDataBinder extends ServletRequestDataBinder { - - public ServletRequestPathVarDataBinder(Object target, String objectName) { - super(target, objectName); - } - - @SuppressWarnings("unchecked") - @Override - protected void doBind(MutablePropertyValues mpvs) { - RequestAttributes requestAttrs = RequestContextHolder.getRequestAttributes(); - if (requestAttrs != null) { - String key = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; - int scope = RequestAttributes.SCOPE_REQUEST; - Map uriTemplateVars = (Map) requestAttrs.getAttribute(key, scope); - mpvs.addPropertyValues(uriTemplateVars); - } - super.doBind(mpvs); + @SuppressWarnings("unchecked") + protected void addUriTemplateVariables(MutablePropertyValues mpvs) { + RequestAttributes requestAttrs = RequestContextHolder.getRequestAttributes(); + if (requestAttrs != null) { + String key = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; + int scope = RequestAttributes.SCOPE_REQUEST; + Map uriTemplateVars = (Map) requestAttrs.getAttribute(key, scope); + mpvs.addPropertyValues(uriTemplateVars); } } - + } \ No newline at end of file diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java index 21454916519..b1db82f2a63 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java @@ -30,16 +30,15 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandlerCom import org.springframework.web.method.support.InvocableHandlerMethod; import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.servlet.View; -import org.springframework.web.servlet.mvc.method.annotation.support.ServletResponseMethodArgumentResolver; /** - * Extends {@link InvocableHandlerMethod} with the ability to handle the value returned from the method through - * a registered {@link HandlerMethodArgumentResolver} that supports the given return value type. + * Extends {@link InvocableHandlerMethod} with the ability to handle the value returned from the method through + * a registered {@link HandlerMethodArgumentResolver} that supports the given return value type. * Return value handling may include writing to the response or updating the {@link ModelAndViewContainer} structure. - * - *

If the underlying method has a {@link ResponseStatus} instruction, the status on the response is set + * + *

If the underlying method has a {@link ResponseStatus} instruction, the status on the response is set * accordingly after the method is invoked but before the return value is handled. - * + * * @author Rossen Stoyanchev * @since 3.1 * @see #invokeAndHandle(NativeWebRequest, ModelAndViewContainer, Object...) @@ -47,9 +46,9 @@ import org.springframework.web.servlet.mvc.method.annotation.support.ServletResp public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { private HttpStatus responseStatus; - + private String responseReason; - + private HandlerMethodReturnValueHandlerComposite returnValueHandlers; public void setHandlerMethodReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) { @@ -72,26 +71,26 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { } /** - * Invokes the method and handles the return value through a registered {@link HandlerMethodReturnValueHandler}. + * Invokes the method and handles the return value through a registered {@link HandlerMethodReturnValueHandler}. *

Return value handling may be skipped entirely when the method returns {@code null} (also possibly due * to a {@code void} return type) and one of the following additional conditions is true: *

    - *
  • A {@link HandlerMethodArgumentResolver} has set the {@link ModelAndViewContainer#setResolveView(boolean)} + *
  • A {@link HandlerMethodArgumentResolver} has set the {@link ModelAndViewContainer#setResolveView(boolean)} * flag to {@code false} -- e.g. method arguments providing access to the response. *
  • The request qualifies as "not modified" as defined in {@link ServletWebRequest#checkNotModified(long)} - * and {@link ServletWebRequest#checkNotModified(String)}. In this case a response with "not modified" response - * headers will be automatically generated without the need for return value handling. + * and {@link ServletWebRequest#checkNotModified(String)}. In this case a response with "not modified" response + * headers will be automatically generated without the need for return value handling. *
  • The status on the response is set due to a @{@link ResponseStatus} instruction. *
- *

After the return value is handled, callers of this method can use the {@link ModelAndViewContainer} + *

After the return value is handled, callers of this method can use the {@link ModelAndViewContainer} * to gain access to model attributes, view selection choices, and to check if view resolution is even needed. - * + * * @param request the current request * @param mavContainer the {@link ModelAndViewContainer} for the current request * @param providedArgs argument values to try to use without the need for view resolution */ - public final void invokeAndHandle(NativeWebRequest request, - ModelAndViewContainer mavContainer, + public final void invokeAndHandle(NativeWebRequest request, + ModelAndViewContainer mavContainer, Object...providedArgs) throws Exception { if (!returnValueHandlers.supportsReturnType(getReturnType())) { @@ -108,7 +107,7 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { return; } } - + mavContainer.setResolveView(true); returnValueHandlers.handleReturnValue(returnValue, getReturnType(), mavContainer, request); diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletModelAttributeMethodProcessor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletModelAttributeMethodProcessor.java index 92ce76483c4..7d5c900ff67 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletModelAttributeMethodProcessor.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletModelAttributeMethodProcessor.java @@ -26,9 +26,9 @@ import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.annotation.support.ModelAttributeMethodProcessor; /** - * A Servlet-specific {@link ModelAttributeMethodProcessor} variant that casts the {@link WebDataBinder} + * A Servlet-specific {@link ModelAttributeMethodProcessor} variant that casts the {@link WebDataBinder} * instance to {@link ServletRequestDataBinder} prior to invoking data binding. - * + * * @author Rossen Stoyanchev * @since 3.1 */ @@ -36,7 +36,7 @@ public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodPr /** * @param useDefaultResolution in default resolution mode a method argument that isn't a simple type, as - * defined in {@link BeanUtils#isSimpleProperty(Class)}, is treated as a model attribute even if it doesn't + * defined in {@link BeanUtils#isSimpleProperty(Class)}, is treated as a model attribute even if it doesn't * have an @{@link ModelAttribute} annotation with its name derived from the model attribute type. */ public ServletModelAttributeMethodProcessor(boolean useDefaultResolution) { @@ -44,12 +44,15 @@ public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodPr } /** - * Expects the data binder to be an instance of {@link ServletRequestDataBinder}. + * {@inheritDoc} + *

This method downcasts the binder instance to {@link ServletRequestDataBinder} and invokes + * its bind method passing a {@link ServletRequest} to it. */ @Override protected void doBind(WebDataBinder binder, NativeWebRequest request) { ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class); - ((ServletRequestDataBinder) binder).bind(servletRequest); + ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder; + servletBinder.bind(servletRequest); } } \ No newline at end of file diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java index 76efe6fee5e..eb563cb5a76 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java @@ -17,31 +17,39 @@ package org.springframework.web.servlet.mvc.method.annotation.support; import org.springframework.core.MethodParameter; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.method.annotation.support.ModelAttributeMethodProcessor; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.RequestToViewNameTranslator; import org.springframework.web.servlet.View; +import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator; /** - * Handles return values that are of type {@link View} or {@link String} (i.e. a logical view name). - *

Since a {@link String} return value may handled in different ways, especially in combination with method - * annotations, this handler should be registered after return value handlers that look for method annotations - * such as the {@link ModelAttributeMethodProcessor} and the {@link RequestResponseBodyMethodProcessor}. - * + * Handles return values that are of type {@code void}, {@code String} (i.e. logical view name), or {@link View}. + * + *

A {@code null} return value, either due to a void return type or as the actual value returned from a + * method is left unhandled, leaving it to the configured {@link RequestToViewNameTranslator} to resolve the + * request to an actual view name. By default it is the {@link DefaultRequestToViewNameTranslator}. + * + *

Since a {@link String} return value may handled in different ways, especially in combination with method + * annotations such as @{@link ModelAttribute} and @{@link ResponseBody}, this handler should be ordered after + * return value handlers that support method annotations. + * * @author Rossen Stoyanchev * @since 3.1 */ public class ViewMethodReturnValueHandler implements HandlerMethodReturnValueHandler { - + public boolean supportsReturnType(MethodParameter returnType) { - Class paramType = returnType.getParameterType(); - return (View.class.isAssignableFrom(paramType) || (String.class.equals(paramType))); + Class type = returnType.getParameterType(); + return (void.class.equals(type) || String.class.equals(type) || View.class.isAssignableFrom(type)); } - public void handleReturnValue(Object returnValue, - MethodParameter returnType, - ModelAndViewContainer mavContainer, + public void handleReturnValue(Object returnValue, + MethodParameter returnType, + ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue == null) { return; diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/DefaultServletHandlerConfigurerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/DefaultServletHandlerConfigurerTests.java new file mode 100644 index 00000000000..874ef655d99 --- /dev/null +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/DefaultServletHandlerConfigurerTests.java @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2011 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.annotation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import javax.servlet.RequestDispatcher; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockRequestDispatcher; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; +import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler; + +/** + * Test fixture with a {@link DefaultServletHandlerConfigurer}. + * + * @author Rossen Stoyanchev + */ +public class DefaultServletHandlerConfigurerTests { + + private DefaultServletHandlerConfigurer configurer; + + private DispatchingMockServletContext servletContext; + + private MockHttpServletResponse response; + + @Before + public void setUp() { + response = new MockHttpServletResponse(); + servletContext = new DispatchingMockServletContext(); + configurer = new DefaultServletHandlerConfigurer(servletContext); + } + + @Test + public void notEnabled() { + assertTrue(configurer.getHandlerMapping().getUrlMap().isEmpty()); + } + + @Test + public void enable() throws Exception { + configurer.enable(); + SimpleUrlHandlerMapping handlerMapping = configurer.getHandlerMapping(); + DefaultServletHttpRequestHandler handler = (DefaultServletHttpRequestHandler) handlerMapping.getUrlMap().get("/**"); + + assertNotNull(handler); + assertEquals(Integer.MAX_VALUE, handlerMapping.getOrder()); + + handler.handleRequest(new MockHttpServletRequest(), response); + + String expected = "default"; + assertEquals("The ServletContext was not called with the default servlet name", expected, servletContext.url); + assertEquals("The request was not forwarded", expected, response.getForwardedUrl()); + } + + @Test + public void enableWithServletName() throws Exception { + configurer.enable("defaultServlet"); + SimpleUrlHandlerMapping handlerMapping = configurer.getHandlerMapping(); + DefaultServletHttpRequestHandler handler = (DefaultServletHttpRequestHandler) handlerMapping.getUrlMap().get("/**"); + + assertNotNull(handler); + assertEquals(Integer.MAX_VALUE, handlerMapping.getOrder()); + + handler.handleRequest(new MockHttpServletRequest(), response); + + String expected = "defaultServlet"; + assertEquals("The ServletContext was not called with the default servlet name", expected, servletContext.url); + assertEquals("The request was not forwarded", expected, response.getForwardedUrl()); + } + + private static class DispatchingMockServletContext extends MockServletContext { + + private String url; + + @Override + public RequestDispatcher getNamedDispatcher(String url) { + this.url = url; + return new MockRequestDispatcher(url); + } + } + +} diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorConfigurerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorConfigurerTests.java new file mode 100644 index 00000000000..1cf2590f00b --- /dev/null +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorConfigurerTests.java @@ -0,0 +1,168 @@ +/* + * Copyright 2002-2011 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.annotation; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.ui.ModelMap; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.context.request.WebRequestInterceptor; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import org.springframework.web.servlet.theme.ThemeChangeInterceptor; + +/** + * Test fixture with a {@link InterceptorConfigurer}, two {@link HandlerInterceptor}s and two + * {@link WebRequestInterceptor}s. + * + * @author Rossen Stoyanchev + */ +public class InterceptorConfigurerTests { + + private InterceptorConfigurer configurer; + + private final HandlerInterceptor interceptor1 = new LocaleChangeInterceptor(); + + private final HandlerInterceptor interceptor2 = new ThemeChangeInterceptor(); + + private TestWebRequestInterceptor webRequestInterceptor1; + + private TestWebRequestInterceptor webRequestInterceptor2; + + private final MockHttpServletRequest request = new MockHttpServletRequest(); + + private final MockHttpServletResponse response = new MockHttpServletResponse(); + + @Before + public void setUp() { + configurer = new InterceptorConfigurer(); + webRequestInterceptor1 = new TestWebRequestInterceptor(); + webRequestInterceptor2 = new TestWebRequestInterceptor(); + } + + @Test + public void addInterceptor() { + configurer.addInterceptor(interceptor1); + HandlerInterceptor[] interceptors = getInterceptorsForPath(null); + assertArrayEquals(new HandlerInterceptor[] {interceptor1}, interceptors); + } + + @Test + public void addInterceptors() { + configurer.addInterceptors(interceptor1, interceptor2); + HandlerInterceptor[] interceptors = getInterceptorsForPath(null); + assertArrayEquals(new HandlerInterceptor[] {interceptor1, interceptor2}, interceptors); + } + + @Test + public void mapInterceptor() { + configurer.mapInterceptor(new String[] {"/path1"}, interceptor1); + configurer.mapInterceptor(new String[] {"/path2"}, interceptor2); + + assertArrayEquals(new HandlerInterceptor[] {interceptor1}, getInterceptorsForPath("/path1")); + assertArrayEquals(new HandlerInterceptor[] {interceptor2}, getInterceptorsForPath("/path2")); + } + + @Test + public void mapInterceptors() { + configurer.mapInterceptors(new String[] {"/path1"}, interceptor1, interceptor2); + + assertArrayEquals(new HandlerInterceptor[] {interceptor1, interceptor2}, getInterceptorsForPath("/path1")); + assertArrayEquals(new HandlerInterceptor[] {}, getInterceptorsForPath("/path2")); + } + + @Test + public void addWebRequestInterceptor() throws Exception { + configurer.addInterceptor(webRequestInterceptor1); + HandlerInterceptor[] interceptors = getInterceptorsForPath(null); + + assertEquals(1, interceptors.length); + verifyAdaptedInterceptor(interceptors[0], webRequestInterceptor1); + } + + @Test + public void addWebRequestInterceptors() throws Exception { + configurer.addInterceptors(webRequestInterceptor1, webRequestInterceptor2); + HandlerInterceptor[] interceptors = getInterceptorsForPath(null); + + assertEquals(2, interceptors.length); + verifyAdaptedInterceptor(interceptors[0], webRequestInterceptor1); + verifyAdaptedInterceptor(interceptors[1], webRequestInterceptor2); + } + + @Test + public void mapWebRequestInterceptor() throws Exception { + configurer.mapInterceptor(new String[] {"/path1"}, webRequestInterceptor1); + configurer.mapInterceptor(new String[] {"/path2"}, webRequestInterceptor2); + + HandlerInterceptor[] interceptors = getInterceptorsForPath("/path1"); + assertEquals(1, interceptors.length); + verifyAdaptedInterceptor(interceptors[0], webRequestInterceptor1); + + interceptors = getInterceptorsForPath("/path2"); + assertEquals(1, interceptors.length); + verifyAdaptedInterceptor(interceptors[0], webRequestInterceptor2); + } + + @Test + public void mapWebRequestInterceptor2() throws Exception { + configurer.mapInterceptors(new String[] {"/path1"}, webRequestInterceptor1, webRequestInterceptor2); + + HandlerInterceptor[] interceptors = getInterceptorsForPath("/path1"); + assertEquals(2, interceptors.length); + verifyAdaptedInterceptor(interceptors[0], webRequestInterceptor1); + verifyAdaptedInterceptor(interceptors[1], webRequestInterceptor2); + + assertEquals(0, getInterceptorsForPath("/path2").length); + } + + private HandlerInterceptor[] getInterceptorsForPath(String lookupPath) { + return configurer.getMappedInterceptors().getInterceptors(lookupPath, new AntPathMatcher()); + } + + private void verifyAdaptedInterceptor(HandlerInterceptor interceptor, TestWebRequestInterceptor webInterceptor) + throws Exception { + assertTrue(interceptor instanceof WebRequestHandlerInterceptorAdapter); + interceptor.preHandle(request, response, null); + assertTrue(webInterceptor.preHandleInvoked); + } + + private static class TestWebRequestInterceptor implements WebRequestInterceptor { + + private boolean preHandleInvoked = false; + + public void preHandle(WebRequest request) throws Exception { + preHandleInvoked = true; + } + + public void postHandle(WebRequest request, ModelMap model) throws Exception { + } + + public void afterCompletion(WebRequest request, Exception ex) throws Exception { + } + + } + +} diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/MvcConfigurationTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/MvcConfigurationTests.java new file mode 100644 index 00000000000..96716547228 --- /dev/null +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/MvcConfigurationTests.java @@ -0,0 +1,132 @@ +/* + * Copyright 2002-2011 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.annotation; + +import static org.easymock.EasyMock.capture; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.easymock.Capture; +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; +import org.springframework.format.support.FormattingConversionService; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; +import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver; +import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter; +import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; + +/** + * A test fixture with an {@link MvcConfiguration} and a mock {@link MvcConfigurer} for verifying delegation. + * + * @author Rossen Stoyanchev + */ +public class MvcConfigurationTests { + + private MvcConfiguration mvcConfiguration; + + private MvcConfigurer configurer; + + @Before + public void setUp() { + configurer = EasyMock.createMock(MvcConfigurer.class); + mvcConfiguration = new MvcConfiguration(); + mvcConfiguration.setConfigurers(Arrays.asList(configurer)); + } + + @Test + public void annotationHandlerAdapter() { + Capture conversionService = new Capture(); + Capture> resolvers = new Capture>(); + Capture> handlers = new Capture>(); + Capture>> converters = new Capture>>(); + + expect(configurer.getValidator()).andReturn(null); + configurer.registerFormatters(capture(conversionService)); + configurer.addCustomArgumentResolvers(capture(resolvers)); + configurer.addCustomReturnValueHandlers(capture(handlers)); + configurer.configureMessageConverters(capture(converters)); + replay(configurer); + + RequestMappingHandlerMethodAdapter adapter = mvcConfiguration.requestMappingHandlerAdapter(); + + ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer(); + assertSame(conversionService.getValue(), initializer.getConversionService()); + assertTrue(initializer.getValidator() instanceof LocalValidatorFactoryBean); + + assertEquals(0, resolvers.getValue().size()); + assertEquals(0, handlers.getValue().size()); + assertTrue(converters.getValue().size() > 0); + assertEquals(converters.getValue(), adapter.getMessageConverters()); + + verify(configurer); + } + + @Test + public void getCustomValidator() { + expect(configurer.getValidator()).andReturn(new LocalValidatorFactoryBean()); + replay(configurer); + + mvcConfiguration.validator(); + + verify(configurer); + } + + @Test + public void configureValidator() { + expect(configurer.getValidator()).andReturn(null); + replay(configurer); + + mvcConfiguration.validator(); + + verify(configurer); + } + + @Test + public void handlerExceptionResolver() throws Exception { + Capture>> converters = new Capture>>(); + Capture> exceptionResolvers = new Capture>(); + + configurer.configureMessageConverters(capture(converters)); + configurer.configureHandlerExceptionResolvers(capture(exceptionResolvers)); + replay(configurer); + + mvcConfiguration.handlerExceptionResolver(); + + assertEquals(3, exceptionResolvers.getValue().size()); + assertTrue(exceptionResolvers.getValue().get(0) instanceof ExceptionHandlerExceptionResolver); + assertTrue(exceptionResolvers.getValue().get(1) instanceof ResponseStatusExceptionResolver); + assertTrue(exceptionResolvers.getValue().get(2) instanceof DefaultHandlerExceptionResolver); + assertTrue(converters.getValue().size() > 0); + + verify(configurer); + } + +} diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/ResourceConfigurerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/ResourceConfigurerTests.java new file mode 100644 index 00000000000..ba8d59488a7 --- /dev/null +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/ResourceConfigurerTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2011 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.annotation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.support.GenericWebApplicationContext; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; + +/** + * Test fixture with a {@link ResourceConfigurer}. + * + * @author Rossen Stoyanchev + */ +public class ResourceConfigurerTests { + + private ResourceConfigurer configurer; + + private MockHttpServletResponse response; + + @Before + public void setUp() { + configurer = new ResourceConfigurer(new GenericWebApplicationContext(), new MockServletContext()); + configurer.addPathMapping("/resources/**"); + configurer.addResourceLocation("classpath:org/springframework/web/servlet/config/annotation/"); + + response = new MockHttpServletResponse(); + } + + @Test + public void noMappings() throws Exception { + configurer = new ResourceConfigurer(new GenericWebApplicationContext(), new MockServletContext()); + assertTrue(configurer.getHandlerMapping().getUrlMap().isEmpty()); + } + + @Test + public void mapPathToLocation() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("GET"); + request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/testStylesheet.css"); + + ResourceHttpRequestHandler handler = getResourceHandler("/resources/**"); + handler.handleRequest(request, response); + + assertEquals("test stylesheet content", response.getContentAsString()); + } + + @Test + public void cachePeriod() { + assertEquals(-1, getResourceHandler("/resources/**").getCacheSeconds()); + + configurer.setCachePeriod(0); + assertEquals(0, getResourceHandler("/resources/**").getCacheSeconds()); + } + + @Test + public void order() { + assertEquals(Integer.MAX_VALUE -1, configurer.getHandlerMapping().getOrder()); + + configurer.setOrder(0); + assertEquals(0, configurer.getHandlerMapping().getOrder()); + } + + private ResourceHttpRequestHandler getResourceHandler(String pathPattern) { + return (ResourceHttpRequestHandler) configurer.getHandlerMapping().getUrlMap().get(pathPattern); + } + +} diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/ViewControllerConfigurerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/ViewControllerConfigurerTests.java new file mode 100644 index 00000000000..f0edd47decf --- /dev/null +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/ViewControllerConfigurerTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2011 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.annotation; + +import static org.junit.Assert.*; + +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; +import org.springframework.web.servlet.mvc.ParameterizableViewController; + +/** + * Test fixture with a {@link ViewControllerConfigurer}. + * + * @author Rossen Stoyanchev + */ +public class ViewControllerConfigurerTests { + + private ViewControllerConfigurer configurer; + + @Before + public void setUp() { + configurer = new ViewControllerConfigurer(); + } + + @Test + public void noMappings() throws Exception { + Map urlMap = configurer.getHandlerMapping().getUrlMap(); + assertTrue(urlMap.isEmpty()); + } + + @Test + public void mapViewName() { + configurer.mapViewName("/path", "viewName"); + Map urlMap = configurer.getHandlerMapping().getUrlMap(); + ParameterizableViewController controller = (ParameterizableViewController) urlMap.get("/path"); + assertNotNull(controller); + assertEquals("viewName", controller.getViewName()); + } + + @Test + public void mapViewNameByConvention() { + configurer.mapViewNameByConvention("/path"); + Map urlMap = configurer.getHandlerMapping().getUrlMap(); + ParameterizableViewController controller = (ParameterizableViewController) urlMap.get("/path"); + assertNotNull(controller); + assertNull(controller.getViewName()); + } + + @Test + public void order() { + SimpleUrlHandlerMapping handlerMapping = configurer.getHandlerMapping(); + assertEquals(1, handlerMapping.getOrder()); + + configurer.setOrder(2); + handlerMapping = configurer.getHandlerMapping(); + assertEquals(2, handlerMapping.getOrder()); + } +} diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/annotation/testStylesheet.css b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/annotation/testStylesheet.css new file mode 100644 index 00000000000..e15d46f06b1 --- /dev/null +++ b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/annotation/testStylesheet.css @@ -0,0 +1 @@ +test stylesheet content \ No newline at end of file