From f64c13ad2e986e6ae26e5759654a4bfb5ca6b937 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 3 Apr 2012 13:19:47 +0200 Subject: [PATCH] Add convenient WebAppInitializer base classes This commit introduces three abstract WebApplicationInitializers, to be used in the typical setup of a Spring-based web application. - AbstractContextLoaderInitializer provides an abstract base class for registering a ContextLoaderListener. - AbstractDispatcherServletInitializer provides an abstract base class for registering a DispatcherServlet, with an optional root context. - AbstractAnnotationConfigDispatcherServletInitializer provides an abstract base class for registering a DispatcherServlet and optional ContextLoaderListener based on annotated (e.g. @Configuration) classes. Issue: SPR-9300 --- .../web/WebApplicationInitializer.java | 13 +- .../AbstractContextLoaderInitializer.java | 78 +++++++++ .../ContextLoaderInitializerTests.java | 93 +++++++++++ ...ionConfigDispatcherServletInitializer.java | 101 ++++++++++++ .../AbstractDispatcherServletInitializer.java | 129 +++++++++++++++ ...nfigDispatcherServletInitializerTests.java | 151 ++++++++++++++++++ .../DispatcherServletInitializerTests.java | 134 ++++++++++++++++ .../web/servlet/support/MockDynamic.java | 96 +++++++++++ src/dist/changelog.txt | 1 + 9 files changed, 794 insertions(+), 2 deletions(-) create mode 100644 spring-web/src/main/java/org/springframework/web/context/AbstractContextLoaderInitializer.java create mode 100644 spring-web/src/test/java/org/springframework/web/context/ContextLoaderInitializerTests.java create mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.java create mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java create mode 100644 spring-webmvc/src/test/java/org/springframework/web/servlet/support/AnnotationConfigDispatcherServletInitializerTests.java create mode 100644 spring-webmvc/src/test/java/org/springframework/web/servlet/support/DispatcherServletInitializerTests.java create mode 100644 spring-webmvc/src/test/java/org/springframework/web/servlet/support/MockDynamic.java diff --git a/spring-web/src/main/java/org/springframework/web/WebApplicationInitializer.java b/spring-web/src/main/java/org/springframework/web/WebApplicationInitializer.java index 6cea24fc865..7e7c92eda22 100644 --- a/spring-web/src/main/java/org/springframework/web/WebApplicationInitializer.java +++ b/spring-web/src/main/java/org/springframework/web/WebApplicationInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -72,6 +72,9 @@ import javax.servlet.ServletException; * * } * + * As an alternative to the above, you can also extend from {@link + * org.springframework.web.servlet.support.AbstractDispatcherServletInitializer}. + * * As you can see, thanks to Servlet 3.0's new {@link ServletContext#addServlet} method * we're actually registering an instance of the {@code DispatcherServlet}, and * this means that the {@code DispatcherServlet} can now be treated like any other object @@ -82,7 +85,7 @@ import javax.servlet.ServletException; * are free to create and work with your Spring application contexts as necessary before * injecting them into the {@code DispatcherServlet}. * - *

Most major Spring Web componentry has been updated to support this style of + *

Most major Spring Web components have been updated to support this style of * registration. You'll find that {@code DispatcherServlet}, {@code FrameworkServlet}, * {@code ContextLoaderListener} and {@code DelegatingFilterProxy} all now support * constructor arguments. Even if a component (e.g. non-Spring, other third party) has not @@ -131,6 +134,9 @@ import javax.servlet.ServletException; * * } * + * As an alternative to the above, you can also extend from {@link + * org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer}. + * * Remember that {@code WebApplicationInitializer} implementations are detected * automatically -- so you are free to package them within your application as you * see fit. @@ -165,6 +171,9 @@ import javax.servlet.ServletException; * @author Chris Beams * @since 3.1 * @see SpringServletContainerInitializer + * @see org.springframework.web.context.AbstractContextLoaderInitializer + * @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer + * @see org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer */ public interface WebApplicationInitializer { diff --git a/spring-web/src/main/java/org/springframework/web/context/AbstractContextLoaderInitializer.java b/spring-web/src/main/java/org/springframework/web/context/AbstractContextLoaderInitializer.java new file mode 100644 index 00000000000..54defc908f7 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/context/AbstractContextLoaderInitializer.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2012 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.context; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.web.WebApplicationInitializer; + +/** + * Convenient base class for {@link WebApplicationInitializer} implementations that + * register a {@link ContextLoaderListener} in the servlet context. + * + *

The only method required to be implemented by subclasses is {@link + * #createRootApplicationContext()}, which gets invoked from {@link + * #registerContextLoaderListener(javax.servlet.ServletContext)}. + * + * @author Arjen Poutsma + * @author Chris Beams + * @since 3.2 + */ +public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer { + + /** Logger available to subclasses. */ + protected final Log logger = LogFactory.getLog(getClass()); + + public void onStartup(ServletContext servletContext) throws ServletException { + this.registerContextLoaderListener(servletContext); + } + + /** + * Register a {@link ContextLoaderListener} against the given servlet context. The + * {@code ContextLoaderListener} is initialized with the application context returned + * from the {@link #createRootApplicationContext()} template method. + * @param servletContext the servlet context to register the listener against + */ + protected void registerContextLoaderListener(ServletContext servletContext) { + WebApplicationContext rootAppContext = this.createRootApplicationContext(); + if (rootAppContext != null) { + servletContext.addListener(new ContextLoaderListener(rootAppContext)); + } + else { + logger.debug("No ContextLoaderListener registered, as " + + "createRootApplicationContext() did not return an application context"); + } + } + + /** + * Create the "root" application context to be provided to the + * {@code ContextLoaderListener}. + *

The returned context is delegated to + * {@link ContextLoaderListener#ContextLoaderListener(WebApplicationContext)} and will + * be established as the parent context for any {@code DispatcherServlet} application + * contexts. As such, it typically contains middle-tier services, data sources, etc. + * @return the root application context, or {@code null} if a root context is not + * desired + * @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer + */ + protected abstract WebApplicationContext createRootApplicationContext(); + +} diff --git a/spring-web/src/test/java/org/springframework/web/context/ContextLoaderInitializerTests.java b/spring-web/src/test/java/org/springframework/web/context/ContextLoaderInitializerTests.java new file mode 100644 index 00000000000..998df405994 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/web/context/ContextLoaderInitializerTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2012 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.context; + +import java.util.EventListener; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletException; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.support.StaticWebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +import static org.junit.Assert.*; + +/** + * Test case for {@link AbstractContextLoaderInitializer}. + * + * @author Arjen Poutsma + */ +public class ContextLoaderInitializerTests { + + private static final String BEAN_NAME = "myBean"; + + private AbstractContextLoaderInitializer initializer; + + private MockServletContext servletContext; + + private EventListener eventListener; + + @Before + public void setUp() throws Exception { + servletContext = new MyMockServletContext(); + initializer = new MyContextLoaderInitializer(); + eventListener = null; + } + + @Test + public void register() throws ServletException { + initializer.onStartup(servletContext); + + assertTrue(eventListener instanceof ContextLoaderListener); + ContextLoaderListener cll = (ContextLoaderListener) eventListener; + cll.contextInitialized(new ServletContextEvent(servletContext)); + + WebApplicationContext applicationContext = WebApplicationContextUtils + .getRequiredWebApplicationContext(servletContext); + + assertTrue(applicationContext.containsBean(BEAN_NAME)); + assertTrue(applicationContext.getBean(BEAN_NAME) instanceof MyBean); + } + + private class MyMockServletContext extends MockServletContext { + + @Override + public void addListener(T listener) { + eventListener = listener; + } + + } + + private static class MyContextLoaderInitializer + extends AbstractContextLoaderInitializer { + + @Override + protected WebApplicationContext createRootApplicationContext() { + StaticWebApplicationContext rootContext = new StaticWebApplicationContext(); + rootContext.registerSingleton(BEAN_NAME, MyBean.class); + return rootContext; + } + } + + private static class MyBean { + + } +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.java new file mode 100644 index 00000000000..70bdd82689d --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2012 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.support; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; + +/** + * Base class for {@link org.springframework.web.WebApplicationInitializer + * WebApplicationInitializer} implementations that register a {@link + * org.springframework.web.servlet.DispatcherServlet DispatcherServlet} + * configured with annotated classes, e.g. Spring's {@link + * org.springframework.context.annotation.Configuration @Configuration} classes. + * + *

Concrete implementations are required to implement {@link #getRootConfigClasses()}, + * {@link #getServletConfigClasses()}, as well as {@link #getServletMappings()}. Further + * template and customization methods are provided by {@link + * AbstractDispatcherServletInitializer}. + * + * @author Arjen Poutsma + * @author Chris Beams + * @since 3.2 + */ +public abstract class AbstractAnnotationConfigDispatcherServletInitializer + extends AbstractDispatcherServletInitializer { + + /** + * {@inheritDoc} + *

This implementation creates an {@link AnnotationConfigWebApplicationContext}, + * providing it the annotated classes returned by {@link #getRootConfigClasses()}. + * Returns {@code null} if {@link #getRootConfigClasses()} returns {@code null}. + */ + @Override + protected WebApplicationContext createRootApplicationContext() { + Class[] rootConfigClasses = this.getRootConfigClasses(); + if (!ObjectUtils.isEmpty(rootConfigClasses)) { + AnnotationConfigWebApplicationContext rootAppContext = + new AnnotationConfigWebApplicationContext(); + rootAppContext.register(rootConfigClasses); + return rootAppContext; + } + else { + return null; + } + } + + /** + * {@inheritDoc} + *

This implementation creates an {@link AnnotationConfigWebApplicationContext}, + * providing it the annotated classes returned by {@link #getServletConfigClasses()}. + * @throws IllegalArgumentException if {@link #getServletConfigClasses()} returns + * empty or {@code null} + */ + @Override + protected WebApplicationContext createServletApplicationContext() { + AnnotationConfigWebApplicationContext servletAppContext = + new AnnotationConfigWebApplicationContext(); + Class[] servletConfigClasses = this.getServletConfigClasses(); + Assert.notEmpty(servletConfigClasses, + "getServletConfigClasses() did not return any configuration classes"); + + servletAppContext.register(servletConfigClasses); + return servletAppContext; + } + + /** + * Specify {@link org.springframework.context.annotation.Configuration @Configuration} + * and/or {@link org.springframework.stereotype.Component @Component} classes to be + * provided to the {@linkplain #createRootApplicationContext() root application context}. + * @return the configuration classes for the root application context, or {@code null} + * if creation and registration of a root context is not desired + */ + protected abstract Class[] getRootConfigClasses(); + + /** + * Specify {@link org.springframework.context.annotation.Configuration @Configuration} + * and/or {@link org.springframework.stereotype.Component @Component} classes to be + * provided to the {@linkplain #createServletApplicationContext() dispatcher servlet + * application context}. + * @return the configuration classes for the dispatcher servlet application context + * (may not be empty or {@code null}). + */ + protected abstract Class[] getServletConfigClasses(); + +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java new file mode 100644 index 00000000000..5f661d5b652 --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java @@ -0,0 +1,129 @@ +/* + * Copyright 2002-2012 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.support; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; + +import org.springframework.util.Assert; +import org.springframework.web.context.AbstractContextLoaderInitializer; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * Base class for {@link org.springframework.web.WebApplicationInitializer + * WebApplicationInitializer} implementations that register a {@link DispatcherServlet} in + * the servlet context. + * + *

Concrete implementations are required to implement {@link + * #createServletApplicationContext()}, as well as {@link #getServletMappings()}, both of + * which gets invoked from {@link #registerDispatcherServlet(ServletContext)}. Further + * customization can be achieved by overriding + * {@link #customizeRegistration(ServletRegistration.Dynamic)}. + * + *

Because this class extends from {@link AbstractContextLoaderInitializer}, concrete + * implementations are also required to implement {@link #createRootApplicationContext()} + * to set up a parent "root" application context. If a root context is + * not desired, implementations can simply return {@code null} in the + * {@code createRootApplicationContext()} implementation. + * + * @author Arjen Poutsma + * @author Chris Beams + * @since 3.2 + */ +public abstract class AbstractDispatcherServletInitializer + extends AbstractContextLoaderInitializer { + + /** + * The default servlet name. Can be customized by overriding {@link #getServletName}. + */ + public static final String DEFAULT_SERVLET_NAME = "dispatcher"; + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + super.onStartup(servletContext); + + this.registerDispatcherServlet(servletContext); + } + + /** + * Register a {@link DispatcherServlet} against the given servlet context. + *

This method will create a {@code DispatcherServlet} with the name returned by + * {@link #getServletName()}, initializing it with the application context returned + * from {@link #createServletApplicationContext()}, and mapping it to the patterns + * returned from {@link #getServletMappings()}. + *

Further customization can be achieved by overriding {@link + * #customizeRegistration(ServletRegistration.Dynamic)}. + * @param servletContext the context to register the servlet against + */ + protected void registerDispatcherServlet(ServletContext servletContext) { + String servletName = this.getServletName(); + Assert.hasLength(servletName, + "getServletName() may not return empty or null"); + + WebApplicationContext servletAppContext = this.createServletApplicationContext(); + Assert.notNull(servletAppContext, + "createServletApplicationContext() did not return an application " + + "context for servlet [" + servletName + "]"); + + DispatcherServlet dispatcherServlet = new DispatcherServlet(servletAppContext); + + ServletRegistration.Dynamic registration = + servletContext.addServlet(servletName, dispatcherServlet); + registration.setLoadOnStartup(1); + registration.addMapping(getServletMappings()); + + this.customizeRegistration(registration); + } + + /** + * Return the name under which the {@link DispatcherServlet} will be registered. + * Defaults to {@link #DEFAULT_SERVLET_NAME}. + * @see #registerDispatcherServlet(ServletContext) + */ + protected String getServletName() { + return DEFAULT_SERVLET_NAME; + } + + /** + * Create a servlet application context to be provided to the {@code DispatcherServlet}. + *

The returned context is delegated to Spring's + * {@link DispatcherServlet#DispatcherServlet(WebApplicationContext)} As such, it + * typically contains controllers, view resolvers, locale resolvers, and other + * web-related beans. + * @see #registerDispatcherServlet(ServletContext) + */ + protected abstract WebApplicationContext createServletApplicationContext(); + + /** + * Specify the servlet mapping(s) for the {@code DispatcherServlet}, e.g. '/', '/app', + * etc. + * @see #registerDispatcherServlet(ServletContext) + */ + protected abstract String[] getServletMappings(); + + /** + * Optionally perform further registration customization once + * {@link #registerDispatcherServlet(ServletContext)} has completed. + * @param registration the {@code DispatcherServlet} registration to be customized + * @see #registerDispatcherServlet(ServletContext) + */ + protected void customizeRegistration(ServletRegistration.Dynamic registration) { + } + +} diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/AnnotationConfigDispatcherServletInitializerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/AnnotationConfigDispatcherServletInitializerTests.java new file mode 100644 index 00000000000..a6cf3bdbfc7 --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/AnnotationConfigDispatcherServletInitializerTests.java @@ -0,0 +1,151 @@ +/* + * Copyright 2002-2012 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.support; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; + +import static org.junit.Assert.*; + +/** + * Test case for {@link AbstractAnnotationConfigDispatcherServletInitializer}. + * + * @author Arjen Poutsma + */ +public class AnnotationConfigDispatcherServletInitializerTests { + + private static final String SERVLET_NAME = "myservlet"; + + private static final String ROLE_NAME = "role"; + + private static final String SERVLET_MAPPING = "/myservlet"; + + private AbstractDispatcherServletInitializer initializer; + + private MockServletContext servletContext; + + private Map servlets; + + private Map registrations; + + @Before + public void setUp() throws Exception { + servletContext = new MyMockServletContext(); + initializer = new MyAnnotationConfigDispatcherServletInitializer(); + servlets = new LinkedHashMap(2); + registrations = new LinkedHashMap(2); + } + + @Test + public void register() throws ServletException { + initializer.onStartup(servletContext); + + assertEquals(1, servlets.size()); + assertNotNull(servlets.get(SERVLET_NAME)); + + DispatcherServlet servlet = (DispatcherServlet) servlets.get(SERVLET_NAME); + WebApplicationContext servletContext = servlet.getWebApplicationContext(); + ((AnnotationConfigWebApplicationContext) servletContext).refresh(); + + assertTrue(servletContext.containsBean("bean")); + assertTrue(servletContext.getBean("bean") instanceof MyBean); + + assertEquals(1, registrations.size()); + assertNotNull(registrations.get(SERVLET_NAME)); + + MockDynamic registration = registrations.get(SERVLET_NAME); + assertEquals(Collections.singleton(SERVLET_MAPPING), registration.getMappings()); + assertEquals(1, registration.getLoadOnStartup()); + assertEquals(ROLE_NAME, registration.getRunAsRole()); + } + + private class MyMockServletContext extends MockServletContext { + + @Override + public ServletRegistration.Dynamic addServlet(String servletName, + Servlet servlet) { + servlets.put(servletName, servlet); + MockDynamic registration = new MockDynamic(); + registrations.put(servletName, registration); + return registration; + } + } + + private static class MyAnnotationConfigDispatcherServletInitializer + extends AbstractAnnotationConfigDispatcherServletInitializer { + + @Override + protected String getServletName() { + return SERVLET_NAME; + } + + @Override + protected Class[] getServletConfigClasses() { + return new Class[]{MyConfiguration.class}; + } + + @Override + protected String[] getServletMappings() { + return new String[]{"/myservlet"}; + } + + @Override + protected void customizeRegistration(ServletRegistration.Dynamic registration) { + registration.setRunAsRole("role"); + } + + @Override + protected Class[] getRootConfigClasses() { + return null; + } + + } + + private static class MyBean { + + } + + @Configuration + @SuppressWarnings("unused") + private static class MyConfiguration { + + public MyConfiguration() { + } + + @Bean + public MyBean bean() { + return new MyBean(); + } + + } + +} diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/DispatcherServletInitializerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/DispatcherServletInitializerTests.java new file mode 100644 index 00000000000..9fcb14d5244 --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/DispatcherServletInitializerTests.java @@ -0,0 +1,134 @@ +/* + * Copyright 2002-2012 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.support; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; + +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.StaticWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * Test case for {@link AbstractDispatcherServletInitializer}. + * + * @author Arjen Poutsma + */ +public class DispatcherServletInitializerTests { + + private static final String SERVLET_NAME = "myservlet"; + + private static final String ROLE_NAME = "role"; + + private static final String SERVLET_MAPPING = "/myservlet"; + + private AbstractDispatcherServletInitializer initializer; + + private MockServletContext servletContext; + + private Map servlets; + + private Map registrations; + + @Before + public void setUp() throws Exception { + servletContext = new MyMockServletContext(); + initializer = new MyDispatcherServletInitializer(); + servlets = new LinkedHashMap(2); + registrations = new LinkedHashMap(2); + } + + @Test + public void register() throws ServletException { + initializer.onStartup(servletContext); + + assertEquals(1, servlets.size()); + assertNotNull(servlets.get(SERVLET_NAME)); + + DispatcherServlet servlet = (DispatcherServlet) servlets.get(SERVLET_NAME); + WebApplicationContext servletContext = servlet.getWebApplicationContext(); + + assertTrue(servletContext.containsBean("bean")); + assertTrue(servletContext.getBean("bean") instanceof MyBean); + + assertEquals(1, registrations.size()); + assertNotNull(registrations.get(SERVLET_NAME)); + + MockDynamic registration = registrations.get(SERVLET_NAME); + assertEquals(Collections.singleton(SERVLET_MAPPING), registration.getMappings()); + assertEquals(1, registration.getLoadOnStartup()); + assertEquals(ROLE_NAME, registration.getRunAsRole()); + } + + private class MyMockServletContext extends MockServletContext { + + @Override + public ServletRegistration.Dynamic addServlet(String servletName, + Servlet servlet) { + servlets.put(servletName, servlet); + MockDynamic registration = new MockDynamic(); + registrations.put(servletName, registration); + return registration; + } + } + + private static class MyDispatcherServletInitializer + extends AbstractDispatcherServletInitializer { + + @Override + protected String getServletName() { + return SERVLET_NAME; + } + + @Override + protected WebApplicationContext createServletApplicationContext() { + StaticWebApplicationContext servletContext = + new StaticWebApplicationContext(); + servletContext.registerSingleton("bean", MyBean.class); + return servletContext; + } + + @Override + protected String[] getServletMappings() { + return new String[]{"/myservlet"}; + } + + @Override + protected void customizeRegistration(ServletRegistration.Dynamic registration) { + registration.setRunAsRole("role"); + } + + @Override + protected WebApplicationContext createRootApplicationContext() { + return null; + } + } + + private static class MyBean { + + } + +} diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/MockDynamic.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/MockDynamic.java new file mode 100644 index 00000000000..cb43a1228b8 --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/MockDynamic.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2012 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.support; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import javax.servlet.MultipartConfigElement; +import javax.servlet.ServletRegistration; +import javax.servlet.ServletSecurityElement; + +class MockDynamic implements ServletRegistration.Dynamic { + + private int loadOnStartup; + + private Set mappings = new LinkedHashSet(); + + private String roleName; + + public int getLoadOnStartup() { + return loadOnStartup; + } + + public void setLoadOnStartup(int loadOnStartup) { + this.loadOnStartup = loadOnStartup; + } + + public void setRunAsRole(String roleName) { + this.roleName = roleName; + } + + public Set addMapping(String... urlPatterns) { + mappings.addAll(Arrays.asList(urlPatterns)); + return mappings; + } + + public Collection getMappings() { + return mappings; + } + + public String getRunAsRole() { + return roleName; + } + + // not implemented + public void setAsyncSupported(boolean isAsyncSupported) { + } + + public String getName() { + return null; + } + + public void setMultipartConfig(MultipartConfigElement multipartConfig) { + } + + public Set setServletSecurity(ServletSecurityElement constraint) { + return null; + } + + public String getClassName() { + return null; + } + + public boolean setInitParameter(String name, String value) { + return false; + } + + public String getInitParameter(String name) { + return null; + } + + public Set setInitParameters(Map initParameters) { + return null; + } + + public Map getInitParameters() { + return null; + } +} diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index 5a9f5059eaf..92ce99c761d 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -30,6 +30,7 @@ Changes in version 3.2 M1 * decode path variables when url decoding is turned off in AbstractHandlerMapping * add required flag to @RequestBody annotation * support executor qualification with @Async#value (SPR-6847) +* add convenient WebAppInitializer base classes (SPR-9300) Changes in version 3.1.1 (2012-02-16) -------------------------------------