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