001/*
002 * Copyright 2002-2015 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.servlet.support;
018
019import java.util.EnumSet;
020import javax.servlet.DispatcherType;
021import javax.servlet.Filter;
022import javax.servlet.FilterRegistration;
023import javax.servlet.FilterRegistration.Dynamic;
024import javax.servlet.ServletContext;
025import javax.servlet.ServletException;
026import javax.servlet.ServletRegistration;
027
028import org.springframework.context.ApplicationContextInitializer;
029import org.springframework.core.Conventions;
030import org.springframework.util.Assert;
031import org.springframework.util.ObjectUtils;
032import org.springframework.web.context.AbstractContextLoaderInitializer;
033import org.springframework.web.context.WebApplicationContext;
034import org.springframework.web.servlet.DispatcherServlet;
035import org.springframework.web.servlet.FrameworkServlet;
036
037/**
038 * Base class for {@link org.springframework.web.WebApplicationInitializer}
039 * implementations that register a {@link DispatcherServlet} in the servlet context.
040 *
041 * <p>Concrete implementations are required to implement
042 * {@link #createServletApplicationContext()}, as well as {@link #getServletMappings()},
043 * both of which get invoked from {@link #registerDispatcherServlet(ServletContext)}.
044 * Further customization can be achieved by overriding
045 * {@link #customizeRegistration(ServletRegistration.Dynamic)}.
046 *
047 * <p>Because this class extends from {@link AbstractContextLoaderInitializer}, concrete
048 * implementations are also required to implement {@link #createRootApplicationContext()}
049 * to set up a parent "<strong>root</strong>" application context. If a root context is
050 * not desired, implementations can simply return {@code null} in the
051 * {@code createRootApplicationContext()} implementation.
052 *
053 * @author Arjen Poutsma
054 * @author Chris Beams
055 * @author Rossen Stoyanchev
056 * @author Juergen Hoeller
057 * @author Stephane Nicoll
058 * @since 3.2
059 */
060public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
061
062        /**
063         * The default servlet name. Can be customized by overriding {@link #getServletName}.
064         */
065        public static final String DEFAULT_SERVLET_NAME = "dispatcher";
066
067
068        @Override
069        public void onStartup(ServletContext servletContext) throws ServletException {
070                super.onStartup(servletContext);
071                registerDispatcherServlet(servletContext);
072        }
073
074        /**
075         * Register a {@link DispatcherServlet} against the given servlet context.
076         * <p>This method will create a {@code DispatcherServlet} with the name returned by
077         * {@link #getServletName()}, initializing it with the application context returned
078         * from {@link #createServletApplicationContext()}, and mapping it to the patterns
079         * returned from {@link #getServletMappings()}.
080         * <p>Further customization can be achieved by overriding {@link
081         * #customizeRegistration(ServletRegistration.Dynamic)} or
082         * {@link #createDispatcherServlet(WebApplicationContext)}.
083         * @param servletContext the context to register the servlet against
084         */
085        protected void registerDispatcherServlet(ServletContext servletContext) {
086                String servletName = getServletName();
087                Assert.hasLength(servletName, "getServletName() must not return empty or null");
088
089                WebApplicationContext servletAppContext = createServletApplicationContext();
090                Assert.notNull(servletAppContext,
091                                "createServletApplicationContext() did not return an application " +
092                                "context for servlet [" + servletName + "]");
093
094                FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
095                dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
096
097                ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
098                Assert.notNull(registration,
099                                "Failed to register servlet with name '" + servletName + "'." +
100                                "Check if there is another servlet registered under the same name.");
101
102                registration.setLoadOnStartup(1);
103                registration.addMapping(getServletMappings());
104                registration.setAsyncSupported(isAsyncSupported());
105
106                Filter[] filters = getServletFilters();
107                if (!ObjectUtils.isEmpty(filters)) {
108                        for (Filter filter : filters) {
109                                registerServletFilter(servletContext, filter);
110                        }
111                }
112
113                customizeRegistration(registration);
114        }
115
116        /**
117         * Return the name under which the {@link DispatcherServlet} will be registered.
118         * Defaults to {@link #DEFAULT_SERVLET_NAME}.
119         * @see #registerDispatcherServlet(ServletContext)
120         */
121        protected String getServletName() {
122                return DEFAULT_SERVLET_NAME;
123        }
124
125        /**
126         * Create a servlet application context to be provided to the {@code DispatcherServlet}.
127         * <p>The returned context is delegated to Spring's
128         * {@link DispatcherServlet#DispatcherServlet(WebApplicationContext)}. As such,
129         * it typically contains controllers, view resolvers, locale resolvers, and other
130         * web-related beans.
131         * @see #registerDispatcherServlet(ServletContext)
132         */
133        protected abstract WebApplicationContext createServletApplicationContext();
134
135        /**
136         * Create a {@link DispatcherServlet} (or other kind of {@link FrameworkServlet}-derived
137         * dispatcher) with the specified {@link WebApplicationContext}.
138         * <p>Note: This allows for any {@link FrameworkServlet} subclass as of 4.2.3.
139         * Previously, it insisted on returning a {@link DispatcherServlet} or subclass thereof.
140         */
141        protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
142                return new DispatcherServlet(servletAppContext);
143        }
144
145        /**
146         * Specify application context initializers to be applied to the servlet-specific
147         * application context that the {@code DispatcherServlet} is being created with.
148         * @since 4.2
149         * @see #createServletApplicationContext()
150         * @see DispatcherServlet#setContextInitializers
151         * @see #getRootApplicationContextInitializers()
152         */
153        protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
154                return null;
155        }
156
157        /**
158         * Specify the servlet mapping(s) for the {@code DispatcherServlet} &mdash;
159         * for example {@code "/"}, {@code "/app"}, etc.
160         * @see #registerDispatcherServlet(ServletContext)
161         */
162        protected abstract String[] getServletMappings();
163
164        /**
165         * Specify filters to add and map to the {@code DispatcherServlet}.
166         * @return an array of filters or {@code null}
167         * @see #registerServletFilter(ServletContext, Filter)
168         */
169        protected Filter[] getServletFilters() {
170                return null;
171        }
172
173        /**
174         * Add the given filter to the ServletContext and map it to the
175         * {@code DispatcherServlet} as follows:
176         * <ul>
177         * <li>a default filter name is chosen based on its concrete type
178         * <li>the {@code asyncSupported} flag is set depending on the
179         * return value of {@link #isAsyncSupported() asyncSupported}
180         * <li>a filter mapping is created with dispatcher types {@code REQUEST},
181         * {@code FORWARD}, {@code INCLUDE}, and conditionally {@code ASYNC} depending
182         * on the return value of {@link #isAsyncSupported() asyncSupported}
183         * </ul>
184         * <p>If the above defaults are not suitable or insufficient, override this
185         * method and register filters directly with the {@code ServletContext}.
186         * @param servletContext the servlet context to register filters with
187         * @param filter the filter to be registered
188         * @return the filter registration
189         */
190        protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
191                String filterName = Conventions.getVariableName(filter);
192                Dynamic registration = servletContext.addFilter(filterName, filter);
193                if (registration == null) {
194                        int counter = -1;
195                        while (counter == -1 || registration == null) {
196                                counter++;
197                                registration = servletContext.addFilter(filterName + "#" + counter, filter);
198                                Assert.isTrue(counter < 100,
199                                                "Failed to register filter '" + filter + "'." +
200                                                "Could the same Filter instance have been registered already?");
201                        }
202                }
203                registration.setAsyncSupported(isAsyncSupported());
204                registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
205                return registration;
206        }
207
208        private EnumSet<DispatcherType> getDispatcherTypes() {
209                return (isAsyncSupported() ?
210                                EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
211                                EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
212        }
213
214        /**
215         * A single place to control the {@code asyncSupported} flag for the
216         * {@code DispatcherServlet} and all filters added via {@link #getServletFilters()}.
217         * <p>The default value is "true".
218         */
219        protected boolean isAsyncSupported() {
220                return true;
221        }
222
223        /**
224         * Optionally perform further registration customization once
225         * {@link #registerDispatcherServlet(ServletContext)} has completed.
226         * @param registration the {@code DispatcherServlet} registration to be customized
227         * @see #registerDispatcherServlet(ServletContext)
228         */
229        protected void customizeRegistration(ServletRegistration.Dynamic registration) {
230        }
231
232}