From 003268fb4eca9dde435a78650d8ad0e387526f04 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 17 Aug 2015 16:45:44 +0100 Subject: [PATCH] Add support for @WebFilter, @WebListener, @WebServlet This commit adds a new annotation, @ServletComponentScan, that can be used to enable scanning for @WebFilter, @WebListener, and @WebServlet annotated classes. Registration beans will be automatically created for any classes that are found, with the configuration derived from the annotation. --- spring-boot-docs/src/main/asciidoc/howto.adoc | 39 ++- .../main/asciidoc/spring-boot-features.adoc | 26 +- .../web/servlet/ServletComponentHandler.java | 87 +++++++ ...vletComponentRegisteringPostProcessor.java | 101 ++++++++ .../web/servlet/ServletComponentScan.java | 80 ++++++ .../ServletComponentScanRegistrar.java | 102 ++++++++ .../boot/web/servlet/WebFilterHandler.java | 79 ++++++ .../boot/web/servlet/WebListenerHandler.java | 49 ++++ .../boot/web/servlet/WebServletHandler.java | 62 +++++ ...omponentRegisteringPostProcessorTests.java | 50 ++++ .../ServletComponentScanIntegrationTests.java | 70 ++++++ .../ServletComponentScanRegistrarTests.java | 111 +++++++++ .../web/servlet/WebFilterHandlerTests.java | 227 ++++++++++++++++++ .../web/servlet/WebListenerHandlerTests.java | 70 ++++++ .../web/servlet/WebServletHandlerTests.java | 179 ++++++++++++++ .../servlet/testcomponents/TestFilter.java | 49 ++++ .../servlet/testcomponents/TestListener.java | 37 +++ .../servlet/testcomponents/TestServlet.java | 39 +++ 18 files changed, 1441 insertions(+), 16 deletions(-) create mode 100644 spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentHandler.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentRegisteringPostProcessor.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentScan.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentScanRegistrar.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/web/servlet/WebFilterHandler.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/web/servlet/WebListenerHandler.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/web/servlet/WebServletHandler.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletComponentRegisteringPostProcessorTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletComponentScanIntegrationTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletComponentScanRegistrarTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/web/servlet/WebFilterHandlerTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/web/servlet/WebListenerHandlerTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/web/servlet/WebServletHandlerTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestFilter.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestListener.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestServlet.java diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index c9ef19d56b8..2f6f6224d78 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -348,15 +348,23 @@ Spring Boot. The definitive list comes from searching the source code for -[[howto-add-a-servlet-filter-or-servletcontextlistener]] -=== Add a Servlet, Filter or ServletContextListener to an application -`Servlet`, `Filter`, `ServletContextListener` and the other listeners supported by the -Servlet spec can be added to your application as `@Bean` definitions. Be very careful that -they don't cause eager initialization of too many other beans because they have to be -installed in the container very early in the application lifecycle (e.g. it's not a good -idea to have them depend on your `DataSource` or JPA configuration). You can work around -restrictions like that by initializing them lazily when first used instead of on -initialization. +[[howto-add-a-servlet-filter-or-listener]] +=== Add a Servlet, Filter or Listener to an application +There are two ways to add `Servlet`, `Filter`, `ServletContextListener` and the other +listeners supported by the Servlet spec to your application. You can either provide +Spring beans for them, or enable scanning for Servlet components. + + + +[[howto-add-a-servlet-filter-or-listener-as-spring-bean]] +==== Add a Servlet, Filter or Listener using a Spring bean +To add a `Servlet`, `Filter`, or Servlet `*Listener` provide a `@Bean` definition for it. +This can be very useful when you want to inject configuration or dependencies. However, +you must be very careful that they don't cause eager initialization of too many other +beans because they have to be installed in the container very early in the application +lifecycle (e.g. it's not a good idea to have them depend on your `DataSource` or JPA +configuration). You can work around restrictions like that by initializing them lazily +when first used instead of on initialization. In the case of `Filters` and `Servlets` you can also add mappings and init parameters by adding a `FilterRegistrationBean` or `ServletRegistrationBean` instead of or as well as @@ -365,7 +373,7 @@ the underlying component. [[howto-disable-registration-of-a-servlet-or-filter]] -=== Disable registration of a Servlet or Filter +===== Disable registration of a Servlet or Filter As <> any `Servlet` or `Filter` beans will be registered with the servlet container automatically. To disable registration of a particular `Filter` or `Servlet` bean create a registration bean for it @@ -382,6 +390,15 @@ and mark it as disabled. For example: ---- +[[howto-add-a-servlet-filter-or-listener-using-scanning]] +==== Add Servlets, Filters, and Listeners using classpath scanning +`@WebServlet`, `@WebFilter`, and `@WebListener` annotated classes can be automatically +registered with an embedded servlet container by annotating a `@Configuration` class +with `@ServletComponentScan` and specifying the package(s) containing the components +that you want to register. By default, `@ServletComponentScan` will scan from the package +of the annotated class. + + [[howto-change-the-http-port]] === Change the HTTP port @@ -1215,7 +1232,7 @@ using the "logging.config" property. [[howto-configure-logback-for-loggin]] === Configure Logback for logging If you put a `logback.xml` in the root of your classpath it will be picked up from -there +there (or `logback-spring.xml` to take advantage of the templating features provided by Boot). Spring Boot provides a default base configuration that you can include if you just want to set levels, e.g. diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 6fcfb97ff9a..504396c5a11 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -1585,12 +1585,18 @@ instance. By default the embedded server will listen for HTTP requests on port ` -[[boot-features-embedded-container-servlets-and-filters]] -==== Servlets and Filters +[[boot-features-embedded-container-servlets-filters-listeners]] +==== Servlets, Filters, and listeners When using an embedded servlet container you can register Servlets, Filters and all the -listeners from the Servlet spec (e.g. `HttpSessionListener`) directly as -Spring beans. This can be particularly convenient if you want to refer to a value from -your `application.properties` during configuration. +listeners from the Servlet spec (e.g. `HttpSessionListener`) either by using Spring beans +or by scanning for Servlet components. + + +[[boot-features-embedded-container-servlets-filters-listeners-beans]] +===== Registering Servlets, Filters, and listeners as Spring beans +Any `Servlet`, `Filter` or Servlet `*Listener` instance that is a Spring bean will be +registered with the embedded container. This can be particularly convenient if you want to +refer to a value from your `application.properties` during configuration. By default, if the context contains only a single Servlet it will be mapped to `/`. In the case of multiple Servlet beans the bean name will be used as a path prefix. Filters will @@ -1603,6 +1609,16 @@ the `ServletContextInitializer` interface. +[[boot-features-embedded-container-servlets-filters-listeners-scanning]] +===== Scanning for Servlets, Filters, and listeners +When using an embedded container, automatic registration of `@WebServlet`, `@WebFilter`, +and `@WebListener` annotated classes can be enabled using `@ServletComponentScan`. + +TIP: `@ServletComponentScan` will have no effect in a standalone container, where the +container's built-in discovery mechanisms will be used instead. + + + [[boot-features-embedded-container-application-context]] ==== The EmbeddedWebApplicationContext Under the hood Spring Boot uses a new type of `ApplicationContext` for embedded servlet diff --git a/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentHandler.java b/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentHandler.java new file mode 100644 index 00000000000..e8dc74f6ef2 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentHandler.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet; + +import java.lang.annotation.Annotation; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ScannedGenericBeanDefinition; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.TypeFilter; + +/** + * Abstract base class for handlers of Servlet components discovered via classpath + * scanning. + * + * @author Andy Wilkinson + */ +abstract class ServletComponentHandler { + + private final Class annotationType; + + private final TypeFilter typeFilter; + + protected ServletComponentHandler(Class annotationType) { + this.typeFilter = new AnnotationTypeFilter(annotationType); + this.annotationType = annotationType; + } + + TypeFilter getTypeFilter() { + return this.typeFilter; + } + + protected String[] extractUrlPatterns(String attribute, Map attributes) { + String[] urlPatterns = (String[]) attributes.get("urlPatterns"); + if (urlPatterns.length > 0) { + if (((String[]) attributes.get("value")).length > 0) { + throw new IllegalStateException("The urlPatterns and value attributes " + + "are mututally exclusive"); + } + return urlPatterns; + } + return (String[]) attributes.get("value"); + } + + protected final Map extractInitParameters( + Map attributes) { + Map initParameters = new HashMap(); + for (AnnotationAttributes initParam : (AnnotationAttributes[]) attributes + .get("initParams")) { + String name = (String) initParam.get("name"); + String value = (String) initParam.get("value"); + initParameters.put(name, value); + } + return initParameters; + } + + void handle(ScannedGenericBeanDefinition beanDefinition, + BeanDefinitionRegistry registry) { + Map attributes = beanDefinition.getMetadata() + .getAnnotationAttributes(this.annotationType.getName()); + if (attributes != null) { + doHandle(attributes, beanDefinition, registry); + } + } + + protected abstract void doHandle(Map attributes, + BeanDefinition beanDefinition, BeanDefinitionRegistry registry); + +} \ No newline at end of file diff --git a/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentRegisteringPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentRegisteringPostProcessor.java new file mode 100644 index 00000000000..d5997ed0820 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentRegisteringPostProcessor.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.context.annotation.ScannedGenericBeanDefinition; + +/** + * {@link BeanFactoryPostProcessor} that registers beans for Servlet components found via + * package scanning. + * + * @see ServletComponentScan + * @see ServletComponentScanRegistrar + * @author Andy Wilkinson + */ +class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, + ApplicationContextAware { + + private final List handlers = Arrays.asList( + new WebServletHandler(), new WebFilterHandler(), new WebListenerHandler()); + + private final Set packagesToScan; + + private ApplicationContext applicationContext; + + public ServletComponentRegisteringPostProcessor(Set packagesToScan) { + this.packagesToScan = packagesToScan; + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + throws BeansException { + if (isRunningInEmbeddedContainer()) { + ClassPathScanningCandidateComponentProvider componentProvider = createComponentProvider(); + for (String packageToScan : this.packagesToScan) { + for (BeanDefinition candidate : componentProvider + .findCandidateComponents(packageToScan)) { + if (candidate instanceof ScannedGenericBeanDefinition) { + for (ServletComponentHandler handler : this.handlers) { + handler.handle(((ScannedGenericBeanDefinition) candidate), + (BeanDefinitionRegistry) this.applicationContext); + } + } + } + } + } + } + + private boolean isRunningInEmbeddedContainer() { + return this.applicationContext instanceof EmbeddedWebApplicationContext + && ((EmbeddedWebApplicationContext) this.applicationContext) + .getServletContext() == null; + } + + private ClassPathScanningCandidateComponentProvider createComponentProvider() { + ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider( + false); + for (ServletComponentHandler handler : this.handlers) { + componentProvider.addIncludeFilter(handler.getTypeFilter()); + } + return componentProvider; + } + + Set getPackagesToScan() { + return Collections.unmodifiableSet(this.packagesToScan); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + this.applicationContext = applicationContext; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentScan.java b/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentScan.java new file mode 100644 index 00000000000..a64d6074c6b --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentScan.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet; + +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 javax.servlet.annotation.WebFilter; +import javax.servlet.annotation.WebListener; +import javax.servlet.annotation.WebServlet; + +import org.springframework.context.annotation.Import; + +/** + * Enables scanning for Servlet components ({@link WebFilter filters}, {@link WebServlet + * servlets}, and {@link WebListener listeners}). Scanning is only performed when using an + * embedded Servlet container. + *

+ * Typically, one of {@code value}, {@code basePackages}, or {@code basePackageClasses} + * should be specified to control the packages to be scanned for components. In their + * absence, scanning will be performed from the package of the class with the annotation. + * + * @author Andy Wilkinson + * @since 1.3.0 + * @see WebServlet + * @see WebFilter + * @see WebListener + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(ServletComponentScanRegistrar.class) +public @interface ServletComponentScan { + + /** + * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation + * declarations e.g.: {@code @ServletComponentScan("org.my.pkg")} instead of + * {@code @ServletComponentScan(basePackages="org.my.pkg")}. + * + * @return the base packages to scan + */ + String[] value() default {}; + + /** + * Base packages to scan for annotated servlet components. {@link #value()} is an + * alias for (and mutually exclusive with) this attribute. + *

+ * Use {@link #basePackageClasses()} for a type-safe alternative to String-based + * package names. + * + * @return the base packages to scan + */ + String[] basePackages() default {}; + + /** + * Type-safe alternative to {@link #basePackages()} for specifying the packages to + * scan for annotated servlet components. The package of each class specified will be + * scanned. + * + * @return classes from the base packages to scan + */ + Class[] basePackageClasses() default {}; +} diff --git a/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentScanRegistrar.java b/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentScanRegistrar.java new file mode 100644 index 00000000000..6ed346dc2d7 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletComponentScanRegistrar.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; + +/** + * {@link ImportBeanDefinitionRegistrar} used by {@link ServletComponentScan}. + * + * @author Andy Wilkinson + */ +class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar { + + private static final String BEAN_NAME = "servletComponentRegisteringPostProcessor"; + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + Set packagesToScan = getPackagesToScan(importingClassMetadata); + if (registry.containsBeanDefinition(BEAN_NAME)) { + updatePostProcessor(registry, packagesToScan); + } + else { + addPostProcessor(registry, packagesToScan); + } + } + + private void addPostProcessor(BeanDefinitionRegistry registry, + Set packagesToScan) { + GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); + beanDefinition.setBeanClass(ServletComponentRegisteringPostProcessor.class); + beanDefinition.getConstructorArgumentValues().addGenericArgumentValue( + packagesToScan); + beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + registry.registerBeanDefinition(BEAN_NAME, beanDefinition); + } + + private void updatePostProcessor(BeanDefinitionRegistry registry, + Set packagesToScan) { + BeanDefinition definition = registry.getBeanDefinition(BEAN_NAME); + ValueHolder constructorArguments = definition.getConstructorArgumentValues() + .getGenericArgumentValue(String[].class); + @SuppressWarnings("unchecked") + Set mergedPackages = new LinkedHashSet( + (Set) constructorArguments.getValue()); + mergedPackages.addAll(packagesToScan); + constructorArguments.setValue(packagesToScan); + } + + private Set getPackagesToScan(AnnotationMetadata metadata) { + AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata + .getAnnotationAttributes(ServletComponentScan.class.getName())); + String[] value = attributes.getStringArray("value"); + String[] basePackages = attributes.getStringArray("basePackages"); + Class[] basePackageClasses = attributes.getClassArray("basePackageClasses"); + if (!ObjectUtils.isEmpty(value)) { + Assert.state(ObjectUtils.isEmpty(basePackages), + "@ServletComponentScan basePackages and value attributes are" + + " mutually exclusive"); + } + Set packagesToScan = new LinkedHashSet(); + packagesToScan.addAll(Arrays.asList(value)); + packagesToScan.addAll(Arrays.asList(basePackages)); + for (Class basePackageClass : basePackageClasses) { + packagesToScan.add(ClassUtils.getPackageName(basePackageClass)); + } + if (packagesToScan.isEmpty()) { + return Collections.singleton(ClassUtils.getPackageName(metadata + .getClassName())); + } + return packagesToScan; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/web/servlet/WebFilterHandler.java b/spring-boot/src/main/java/org/springframework/boot/web/servlet/WebFilterHandler.java new file mode 100644 index 00000000000..ff2cab8aff2 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/servlet/WebFilterHandler.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Map; + +import javax.servlet.DispatcherType; +import javax.servlet.annotation.WebFilter; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.boot.context.embedded.FilterRegistrationBean; +import org.springframework.util.StringUtils; + +/** + * Handler for {@link WebFilter}-annotated classes + * + * @author Andy Wilkinson + */ +class WebFilterHandler extends ServletComponentHandler { + + WebFilterHandler() { + super(WebFilter.class); + } + + @Override + public void doHandle(Map attributes, BeanDefinition beanDefinition, + BeanDefinitionRegistry registry) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .rootBeanDefinition(FilterRegistrationBean.class); + builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported")); + builder.addPropertyValue("dispatcherTypes", extractDispatcherTypes(attributes)); + builder.addPropertyValue("filter", beanDefinition); + builder.addPropertyValue("initParameters", extractInitParameters(attributes)); + String name = determineName(attributes, beanDefinition); + builder.addPropertyValue("name", name); + builder.addPropertyValue("servletNames", attributes.get("servletNames")); + builder.addPropertyValue("urlPatterns", + extractUrlPatterns("urlPatterns", attributes)); + registry.registerBeanDefinition(name, builder.getBeanDefinition()); + } + + private EnumSet extractDispatcherTypes(Map attributes) { + DispatcherType[] dispatcherTypes = (DispatcherType[]) attributes + .get("dispatcherTypes"); + if (dispatcherTypes.length == 0) { + return EnumSet.noneOf(DispatcherType.class); + } + if (dispatcherTypes.length == 1) { + return EnumSet.of(dispatcherTypes[0]); + } + return EnumSet.of(dispatcherTypes[0], + Arrays.copyOfRange(dispatcherTypes, 1, dispatcherTypes.length)); + } + + private String determineName(Map attributes, + BeanDefinition beanDefinition) { + return (String) (StringUtils.hasText((String) attributes.get("filterName")) ? attributes + .get("filterName") : beanDefinition.getBeanClassName()); + } + +} \ No newline at end of file diff --git a/spring-boot/src/main/java/org/springframework/boot/web/servlet/WebListenerHandler.java b/spring-boot/src/main/java/org/springframework/boot/web/servlet/WebListenerHandler.java new file mode 100644 index 00000000000..8e1b7cb3465 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/servlet/WebListenerHandler.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet; + +import java.util.Map; + +import javax.servlet.annotation.WebListener; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.boot.context.embedded.ServletListenerRegistrationBean; + +/** + * Handler for {@link WebListener}-annotated classes + * + * @author Andy Wilkinson + */ +class WebListenerHandler extends ServletComponentHandler { + + WebListenerHandler() { + super(WebListener.class); + } + + @Override + protected void doHandle(Map attributes, + BeanDefinition beanDefinition, BeanDefinitionRegistry registry) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .rootBeanDefinition(ServletListenerRegistrationBean.class); + builder.addPropertyValue("listener", beanDefinition); + registry.registerBeanDefinition(beanDefinition.getBeanClassName(), + builder.getBeanDefinition()); + } + +} \ No newline at end of file diff --git a/spring-boot/src/main/java/org/springframework/boot/web/servlet/WebServletHandler.java b/spring-boot/src/main/java/org/springframework/boot/web/servlet/WebServletHandler.java new file mode 100644 index 00000000000..992af5861f4 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/servlet/WebServletHandler.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet; + +import java.util.Map; + +import javax.servlet.annotation.WebServlet; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.boot.context.embedded.ServletRegistrationBean; +import org.springframework.util.StringUtils; + +/** + * Handler for {@link WebServlet}-annotated classes + * + * @author Andy Wilkinson + */ +class WebServletHandler extends ServletComponentHandler { + + WebServletHandler() { + super(WebServlet.class); + } + + @Override + public void doHandle(Map attributes, BeanDefinition beanDefinition, + BeanDefinitionRegistry registry) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .rootBeanDefinition(ServletRegistrationBean.class); + builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported")); + builder.addPropertyValue("initParameters", extractInitParameters(attributes)); + builder.addPropertyValue("loadOnStartup", attributes.get("loadOnStartup")); + String name = determineName(attributes, beanDefinition); + builder.addPropertyValue("name", name); + builder.addPropertyValue("servlet", beanDefinition); + builder.addPropertyValue("urlMappings", + extractUrlPatterns("urlPatterns", attributes)); + registry.registerBeanDefinition(name, builder.getBeanDefinition()); + } + + private String determineName(Map attributes, + BeanDefinition beanDefinition) { + return (String) (StringUtils.hasText((String) attributes.get("name")) ? attributes + .get("name") : beanDefinition.getBeanClassName()); + } + +} \ No newline at end of file diff --git a/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletComponentRegisteringPostProcessorTests.java b/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletComponentRegisteringPostProcessorTests.java new file mode 100644 index 00000000000..665b519674c --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletComponentRegisteringPostProcessorTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet; + +import java.util.Arrays; +import java.util.HashSet; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext; + +/** + * Tests for {@link ServletComponentRegisteringPostProcessor} + * + * @author Andy Wilkinson + */ +public class ServletComponentRegisteringPostProcessorTests { + + private final ServletComponentRegisteringPostProcessor postProcessor = new ServletComponentRegisteringPostProcessor( + new HashSet(Arrays.asList(getClass().getPackage().getName()))); + + private final EmbeddedWebApplicationContext context = new EmbeddedWebApplicationContext(); + + @Before + public void before() { + this.postProcessor.setApplicationContext(this.context); + } + + @Test + public void test() { + this.postProcessor.postProcessBeanFactory(this.context.getBeanFactory()); + this.context.getBeanDefinition("servletWithName"); + this.context.getBeanDefinition("defaultNameServlet"); + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletComponentScanIntegrationTests.java b/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletComponentScanIntegrationTests.java new file mode 100644 index 00000000000..35d2ebb0a38 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletComponentScanIntegrationTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet; + +import org.junit.After; +import org.junit.Test; +import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; +import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +/** + * Integration tests for {@link ServletComponentScan} + * + * @author Andy Wilkinson + */ +public class ServletComponentScanIntegrationTests { + + private AnnotationConfigEmbeddedWebApplicationContext context; + + @After + public void cleanUp() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void componentsAreRegistered() { + this.context = new AnnotationConfigEmbeddedWebApplicationContext(); + this.context.register(TestConfiguration.class); + new ServerPortInfoApplicationContextInitializer().initialize(this.context); + this.context.refresh(); + String port = this.context.getEnvironment().getProperty("local.server.port"); + String response = new RestTemplate().getForObject("http://localhost:" + port + + "/test", String.class); + assertThat(response, is(equalTo("alpha bravo"))); + } + + @Configuration + @ServletComponentScan(basePackages = "org.springframework.boot.web.servlet.testcomponents") + static class TestConfiguration { + + @Bean + public TomcatEmbeddedServletContainerFactory servletContainerFactory() { + return new TomcatEmbeddedServletContainerFactory(0); + } + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletComponentScanRegistrarTests.java b/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletComponentScanRegistrarTests.java new file mode 100644 index 00000000000..37234110575 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletComponentScanRegistrarTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link ServletComponentScanRegistrar} + * + * @author Andy Wilkinson + */ +public class ServletComponentScanRegistrarTests { + + private AnnotationConfigApplicationContext context; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @After + public void after() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void packagesConfiguredWithValue() { + this.context = new AnnotationConfigApplicationContext(ValuePackages.class); + ServletComponentRegisteringPostProcessor postProcessor = this.context + .getBean(ServletComponentRegisteringPostProcessor.class); + assertThat(postProcessor.getPackagesToScan(), + containsInAnyOrder("com.example.foo", "com.example.bar")); + } + + @Test + public void packagesConfiguredWithBackPackages() { + this.context = new AnnotationConfigApplicationContext(BasePackages.class); + ServletComponentRegisteringPostProcessor postProcessor = this.context + .getBean(ServletComponentRegisteringPostProcessor.class); + assertThat(postProcessor.getPackagesToScan(), + containsInAnyOrder("com.example.foo", "com.example.bar")); + } + + @Test + public void packagesConfiguredWithBasePackageClasses() { + this.context = new AnnotationConfigApplicationContext(BasePackageClasses.class); + ServletComponentRegisteringPostProcessor postProcessor = this.context + .getBean(ServletComponentRegisteringPostProcessor.class); + assertThat(postProcessor.getPackagesToScan(), containsInAnyOrder(getClass() + .getPackage().getName())); + } + + @Test + public void packagesConfiguredWithBothValueAndBasePackages() { + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("@ServletComponentScan basePackages and value" + + " attributes are mutually exclusive"); + this.context = new AnnotationConfigApplicationContext(ValueAndBasePackages.class); + ServletComponentRegisteringPostProcessor postProcessor = this.context + .getBean(ServletComponentRegisteringPostProcessor.class); + assertThat(postProcessor.getPackagesToScan(), containsInAnyOrder(getClass() + .getPackage().getName())); + } + + @Configuration + @ServletComponentScan({ "com.example.foo", "com.example.bar" }) + static class ValuePackages { + + } + + @Configuration + @ServletComponentScan(basePackages = { "com.example.foo", "com.example.bar" }) + static class BasePackages { + + } + + @Configuration + @ServletComponentScan(basePackageClasses = ServletComponentScanRegistrarTests.class) + static class BasePackageClasses { + + } + + @Configuration + @ServletComponentScan(value = "com.example.foo", basePackages = "com.example.bar") + static class ValueAndBasePackages { + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/web/servlet/WebFilterHandlerTests.java b/spring-boot/src/test/java/org/springframework/boot/web/servlet/WebFilterHandlerTests.java new file mode 100644 index 00000000000..cdde42b8db1 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/web/servlet/WebFilterHandlerTests.java @@ -0,0 +1,227 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet; + +import java.io.IOException; +import java.util.EnumSet; +import java.util.Map; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.annotation.WebFilter; +import javax.servlet.annotation.WebInitParam; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry; +import org.springframework.context.annotation.ScannedGenericBeanDefinition; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; + +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link WebFilterHandler} + * + * @author Andy Wilkinson + */ +public class WebFilterHandlerTests { + + private final WebFilterHandler handler = new WebFilterHandler(); + + private final SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @SuppressWarnings("unchecked") + @Test + public void defaultFilterConfiguration() throws IOException { + ScannedGenericBeanDefinition scanned = new ScannedGenericBeanDefinition( + new SimpleMetadataReaderFactory() + .getMetadataReader(DefaultConfigurationFilter.class.getName())); + this.handler.handle(scanned, this.registry); + BeanDefinition filterRegistrationBean = this.registry + .getBeanDefinition(DefaultConfigurationFilter.class.getName()); + MutablePropertyValues propertyValues = filterRegistrationBean.getPropertyValues(); + assertThat(propertyValues.get("asyncSupported"), is((Object) false)); + assertThat((EnumSet) propertyValues.get("dispatcherTypes"), + is(EnumSet.of(DispatcherType.REQUEST))); + assertThat(((Map) propertyValues.get("initParameters")).size(), + is(0)); + assertThat((String[]) propertyValues.get("servletNames"), is(arrayWithSize(0))); + assertThat((String[]) propertyValues.get("urlPatterns"), is(arrayWithSize(0))); + assertThat(propertyValues.get("name"), + is((Object) DefaultConfigurationFilter.class.getName())); + assertThat(propertyValues.get("filter"), is(equalTo((Object) scanned))); + } + + @Test + public void filterWithCustomName() throws IOException { + ScannedGenericBeanDefinition scanned = new ScannedGenericBeanDefinition( + new SimpleMetadataReaderFactory() + .getMetadataReader(CustomNameFilter.class.getName())); + this.handler.handle(scanned, this.registry); + BeanDefinition filterRegistrationBean = this.registry.getBeanDefinition("custom"); + MutablePropertyValues propertyValues = filterRegistrationBean.getPropertyValues(); + assertThat(propertyValues.get("name"), is((Object) "custom")); + } + + @Test + public void asyncSupported() throws IOException { + BeanDefinition filterRegistrationBean = getBeanDefinition(AsyncSupportedFilter.class); + MutablePropertyValues propertyValues = filterRegistrationBean.getPropertyValues(); + assertThat(propertyValues.get("asyncSupported"), is((Object) true)); + } + + @Test + public void dispatcherTypes() throws IOException { + BeanDefinition filterRegistrationBean = getBeanDefinition(DispatcherTypesFilter.class); + MutablePropertyValues propertyValues = filterRegistrationBean.getPropertyValues(); + assertThat(propertyValues.get("dispatcherTypes"), is((Object) EnumSet.of( + DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.REQUEST))); + } + + @SuppressWarnings("unchecked") + @Test + public void initParameters() throws IOException { + BeanDefinition filterRegistrationBean = getBeanDefinition(InitParametersFilter.class); + MutablePropertyValues propertyValues = filterRegistrationBean.getPropertyValues(); + assertThat((Map) propertyValues.get("initParameters"), + hasEntry("a", "alpha")); + assertThat((Map) propertyValues.get("initParameters"), + hasEntry("b", "bravo")); + } + + @Test + public void servletNames() throws IOException { + BeanDefinition filterRegistrationBean = getBeanDefinition(ServletNamesFilter.class); + MutablePropertyValues propertyValues = filterRegistrationBean.getPropertyValues(); + assertThat((String[]) propertyValues.get("servletNames"), + is(arrayContaining("alpha", "bravo"))); + } + + @Test + public void urlPatterns() throws IOException { + BeanDefinition filterRegistrationBean = getBeanDefinition(UrlPatternsFilter.class); + MutablePropertyValues propertyValues = filterRegistrationBean.getPropertyValues(); + assertThat((String[]) propertyValues.get("urlPatterns"), + is(arrayContaining("alpha", "bravo"))); + } + + @Test + public void urlPatternsFromValue() throws IOException { + BeanDefinition filterRegistrationBean = getBeanDefinition(UrlPatternsFromValueFilter.class); + MutablePropertyValues propertyValues = filterRegistrationBean.getPropertyValues(); + assertThat((String[]) propertyValues.get("urlPatterns"), + is(arrayContaining("alpha", "bravo"))); + } + + @Test + public void urlPatternsDeclaredTwice() throws IOException { + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("The urlPatterns and value attributes are mututally " + + "exclusive"); + getBeanDefinition(UrlPatternsDeclaredTwiceFilter.class); + } + + BeanDefinition getBeanDefinition(Class filterClass) throws IOException { + ScannedGenericBeanDefinition scanned = new ScannedGenericBeanDefinition( + new SimpleMetadataReaderFactory().getMetadataReader(filterClass.getName())); + this.handler.handle(scanned, this.registry); + return this.registry.getBeanDefinition(filterClass.getName()); + } + + @WebFilter + class DefaultConfigurationFilter extends BaseFilter { + + } + + @WebFilter(asyncSupported = true) + class AsyncSupportedFilter extends BaseFilter { + + } + + @WebFilter(dispatcherTypes = { DispatcherType.REQUEST, DispatcherType.FORWARD, + DispatcherType.INCLUDE }) + class DispatcherTypesFilter extends BaseFilter { + + } + + @WebFilter(initParams = { @WebInitParam(name = "a", value = "alpha"), + @WebInitParam(name = "b", value = "bravo") }) + class InitParametersFilter extends BaseFilter { + + } + + @WebFilter(servletNames = { "alpha", "bravo" }) + class ServletNamesFilter extends BaseFilter { + + } + + @WebFilter(urlPatterns = { "alpha", "bravo" }) + class UrlPatternsFilter extends BaseFilter { + + } + + @WebFilter({ "alpha", "bravo" }) + class UrlPatternsFromValueFilter extends BaseFilter { + + } + + @WebFilter(value = { "alpha", "bravo" }, urlPatterns = { "alpha", "bravo" }) + class UrlPatternsDeclaredTwiceFilter extends BaseFilter { + + } + + @WebFilter(filterName = "custom") + class CustomNameFilter extends BaseFilter { + + } + + class BaseFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + } + + @Override + public void destroy() { + + } + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/web/servlet/WebListenerHandlerTests.java b/spring-boot/src/test/java/org/springframework/boot/web/servlet/WebListenerHandlerTests.java new file mode 100644 index 00000000000..a2f565416b0 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/web/servlet/WebListenerHandlerTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet; + +import java.io.IOException; + +import javax.servlet.ServletContextAttributeEvent; +import javax.servlet.ServletContextAttributeListener; +import javax.servlet.annotation.WebListener; + +import org.junit.Test; +import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry; +import org.springframework.context.annotation.ScannedGenericBeanDefinition; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; + +/** + * Tests for {@WebListenerHandler} + * + * @author Andy Wilkinson + */ +public class WebListenerHandlerTests { + + private final WebListenerHandler handler = new WebListenerHandler(); + + private final SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); + + @Test + public void listener() throws IOException { + ScannedGenericBeanDefinition scanned = new ScannedGenericBeanDefinition( + new SimpleMetadataReaderFactory().getMetadataReader(TestListener.class + .getName())); + this.handler.handle(scanned, this.registry); + this.registry.getBeanDefinition(TestListener.class.getName()); + } + + @WebListener + static class TestListener implements ServletContextAttributeListener { + + @Override + public void attributeAdded(ServletContextAttributeEvent event) { + + } + + @Override + public void attributeRemoved(ServletContextAttributeEvent event) { + + } + + @Override + public void attributeReplaced(ServletContextAttributeEvent event) { + + } + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/web/servlet/WebServletHandlerTests.java b/spring-boot/src/test/java/org/springframework/boot/web/servlet/WebServletHandlerTests.java new file mode 100644 index 00000000000..ee8b53fa1b0 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/web/servlet/WebServletHandlerTests.java @@ -0,0 +1,179 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet; + +import java.io.IOException; +import java.util.Map; + +import javax.servlet.annotation.WebInitParam; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry; +import org.springframework.context.annotation.ScannedGenericBeanDefinition; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; + +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@WebServletHandler} + * + * @author Andy Wilkinson + */ +public class WebServletHandlerTests { + + private final WebServletHandler handler = new WebServletHandler(); + + private final SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @SuppressWarnings("unchecked") + @Test + public void defaultServletConfiguration() throws IOException { + ScannedGenericBeanDefinition scanned = new ScannedGenericBeanDefinition( + new SimpleMetadataReaderFactory() + .getMetadataReader(DefaultConfigurationServlet.class.getName())); + this.handler.handle(scanned, this.registry); + BeanDefinition servletRegistrationBean = this.registry + .getBeanDefinition(DefaultConfigurationServlet.class.getName()); + MutablePropertyValues propertyValues = servletRegistrationBean + .getPropertyValues(); + assertThat(propertyValues.get("asyncSupported"), is((Object) false)); + assertThat(((Map) propertyValues.get("initParameters")).size(), + is(0)); + assertThat((Integer) propertyValues.get("loadOnStartup"), is(-1)); + assertThat(propertyValues.get("name"), + is((Object) DefaultConfigurationServlet.class.getName())); + assertThat((String[]) propertyValues.get("urlMappings"), is(arrayWithSize(0))); + assertThat(propertyValues.get("servlet"), is(equalTo((Object) scanned))); + } + + @Test + public void servletWithCustomName() throws IOException { + ScannedGenericBeanDefinition scanned = new ScannedGenericBeanDefinition( + new SimpleMetadataReaderFactory() + .getMetadataReader(CustomNameServlet.class.getName())); + this.handler.handle(scanned, this.registry); + BeanDefinition servletRegistrationBean = this.registry + .getBeanDefinition("custom"); + MutablePropertyValues propertyValues = servletRegistrationBean + .getPropertyValues(); + assertThat(propertyValues.get("name"), is((Object) "custom")); + } + + @Test + public void asyncSupported() throws IOException { + BeanDefinition servletRegistrationBean = getBeanDefinition(AsyncSupportedServlet.class); + MutablePropertyValues propertyValues = servletRegistrationBean + .getPropertyValues(); + assertThat(propertyValues.get("asyncSupported"), is((Object) true)); + } + + @SuppressWarnings("unchecked") + @Test + public void initParameters() throws IOException { + BeanDefinition servletRegistrationBean = getBeanDefinition(InitParametersServlet.class); + MutablePropertyValues propertyValues = servletRegistrationBean + .getPropertyValues(); + assertThat((Map) propertyValues.get("initParameters"), + hasEntry("a", "alpha")); + assertThat((Map) propertyValues.get("initParameters"), + hasEntry("b", "bravo")); + } + + @Test + public void urlMappings() throws IOException { + BeanDefinition servletRegistrationBean = getBeanDefinition(UrlPatternsServlet.class); + MutablePropertyValues propertyValues = servletRegistrationBean + .getPropertyValues(); + assertThat((String[]) propertyValues.get("urlMappings"), + is(arrayContaining("alpha", "bravo"))); + } + + @Test + public void urlMappingsFromValue() throws IOException { + BeanDefinition servletRegistrationBean = getBeanDefinition(UrlPatternsFromValueServlet.class); + MutablePropertyValues propertyValues = servletRegistrationBean + .getPropertyValues(); + assertThat((String[]) propertyValues.get("urlMappings"), + is(arrayContaining("alpha", "bravo"))); + } + + @Test + public void urlPatternsDeclaredTwice() throws IOException { + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("The urlPatterns and value attributes are mututally " + + "exclusive"); + getBeanDefinition(UrlPatternsDeclaredTwiceServlet.class); + } + + BeanDefinition getBeanDefinition(Class filterClass) throws IOException { + ScannedGenericBeanDefinition scanned = new ScannedGenericBeanDefinition( + new SimpleMetadataReaderFactory().getMetadataReader(filterClass.getName())); + this.handler.handle(scanned, this.registry); + return this.registry.getBeanDefinition(filterClass.getName()); + } + + @WebServlet + class DefaultConfigurationServlet extends HttpServlet { + + } + + @WebServlet(asyncSupported = true) + class AsyncSupportedServlet extends HttpServlet { + + } + + @WebServlet(initParams = { @WebInitParam(name = "a", value = "alpha"), + @WebInitParam(name = "b", value = "bravo") }) + class InitParametersServlet extends HttpServlet { + + } + + @WebServlet(urlPatterns = { "alpha", "bravo" }) + class UrlPatternsServlet extends HttpServlet { + + } + + @WebServlet({ "alpha", "bravo" }) + class UrlPatternsFromValueServlet extends HttpServlet { + + } + + @WebServlet(value = { "alpha", "bravo" }, urlPatterns = { "alpha", "bravo" }) + class UrlPatternsDeclaredTwiceServlet extends HttpServlet { + + } + + @WebServlet(name = "custom") + class CustomNameServlet extends HttpServlet { + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestFilter.java b/spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestFilter.java new file mode 100644 index 00000000000..0baed2bed26 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestFilter.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet.testcomponents; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.annotation.WebFilter; + +@WebFilter("/*") +class TestFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + request.setAttribute("filterAttribute", "bravo"); + chain.doFilter(request, response); + } + + @Override + public void destroy() { + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestListener.java b/spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestListener.java new file mode 100644 index 00000000000..705730fec86 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestListener.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet.testcomponents; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; + +@WebListener +class TestListener implements ServletContextListener { + + @Override + public void contextInitialized(ServletContextEvent sce) { + sce.getServletContext().setAttribute("listenerAttribute", "alpha"); + + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestServlet.java b/spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestServlet.java new file mode 100644 index 00000000000..9703c1e5b4c --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestServlet.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.servlet.testcomponents; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/test") +public class TestServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.getWriter().print( + ((String) req.getServletContext().getAttribute("listenerAttribute")) + + " " + req.getAttribute("filterAttribute")); + resp.getWriter().flush(); + } + +}