001/*
002 * Copyright 2012-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 *      http://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.boot.web.servlet.support;
018
019import java.util.Collections;
020
021import javax.servlet.Filter;
022import javax.servlet.Servlet;
023import javax.servlet.ServletContext;
024import javax.servlet.ServletContextEvent;
025import javax.servlet.ServletException;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029
030import org.springframework.boot.SpringApplication;
031import org.springframework.boot.builder.ParentContextApplicationContextInitializer;
032import org.springframework.boot.builder.SpringApplicationBuilder;
033import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
034import org.springframework.boot.web.servlet.ServletContextInitializer;
035import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
036import org.springframework.context.ApplicationContext;
037import org.springframework.context.ApplicationListener;
038import org.springframework.context.annotation.Configuration;
039import org.springframework.core.Ordered;
040import org.springframework.core.annotation.AnnotationUtils;
041import org.springframework.core.env.ConfigurableEnvironment;
042import org.springframework.util.Assert;
043import org.springframework.web.WebApplicationInitializer;
044import org.springframework.web.context.ConfigurableWebEnvironment;
045import org.springframework.web.context.ContextLoaderListener;
046import org.springframework.web.context.WebApplicationContext;
047
048/**
049 * An opinionated {@link WebApplicationInitializer} to run a {@link SpringApplication}
050 * from a traditional WAR deployment. Binds {@link Servlet}, {@link Filter} and
051 * {@link ServletContextInitializer} beans from the application context to the server.
052 * <p>
053 * To configure the application either override the
054 * {@link #configure(SpringApplicationBuilder)} method (calling
055 * {@link SpringApplicationBuilder#sources(Class...)}) or make the initializer itself a
056 * {@code @Configuration}. If you are using {@link SpringBootServletInitializer} in
057 * combination with other {@link WebApplicationInitializer WebApplicationInitializers} you
058 * might also want to add an {@code @Ordered} annotation to configure a specific startup
059 * order.
060 * <p>
061 * Note that a WebApplicationInitializer is only needed if you are building a war file and
062 * deploying it. If you prefer to run an embedded web server then you won't need this at
063 * all.
064 *
065 * @author Dave Syer
066 * @author Phillip Webb
067 * @author Andy Wilkinson
068 * @since 2.0.0
069 * @see #configure(SpringApplicationBuilder)
070 */
071public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
072
073        protected Log logger; // Don't initialize early
074
075        private boolean registerErrorPageFilter = true;
076
077        /**
078         * Set if the {@link ErrorPageFilter} should be registered. Set to {@code false} if
079         * error page mappings should be handled via the server and not Spring Boot.
080         * @param registerErrorPageFilter if the {@link ErrorPageFilter} should be registered.
081         */
082        protected final void setRegisterErrorPageFilter(boolean registerErrorPageFilter) {
083                this.registerErrorPageFilter = registerErrorPageFilter;
084        }
085
086        @Override
087        public void onStartup(ServletContext servletContext) throws ServletException {
088                // Logger initialization is deferred in case an ordered
089                // LogServletContextInitializer is being used
090                this.logger = LogFactory.getLog(getClass());
091                WebApplicationContext rootAppContext = createRootApplicationContext(
092                                servletContext);
093                if (rootAppContext != null) {
094                        servletContext.addListener(new ContextLoaderListener(rootAppContext) {
095                                @Override
096                                public void contextInitialized(ServletContextEvent event) {
097                                        // no-op because the application context is already initialized
098                                }
099                        });
100                }
101                else {
102                        this.logger.debug("No ContextLoaderListener registered, as "
103                                        + "createRootApplicationContext() did not "
104                                        + "return an application context");
105                }
106        }
107
108        protected WebApplicationContext createRootApplicationContext(
109                        ServletContext servletContext) {
110                SpringApplicationBuilder builder = createSpringApplicationBuilder();
111                builder.main(getClass());
112                ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
113                if (parent != null) {
114                        this.logger.info("Root context already created (using as parent).");
115                        servletContext.setAttribute(
116                                        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
117                        builder.initializers(new ParentContextApplicationContextInitializer(parent));
118                }
119                builder.initializers(
120                                new ServletContextApplicationContextInitializer(servletContext));
121                builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
122                builder = configure(builder);
123                builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
124                SpringApplication application = builder.build();
125                if (application.getAllSources().isEmpty() && AnnotationUtils
126                                .findAnnotation(getClass(), Configuration.class) != null) {
127                        application.addPrimarySources(Collections.singleton(getClass()));
128                }
129                Assert.state(!application.getAllSources().isEmpty(),
130                                "No SpringApplication sources have been defined. Either override the "
131                                                + "configure method or add an @Configuration annotation");
132                // Ensure error pages are registered
133                if (this.registerErrorPageFilter) {
134                        application.addPrimarySources(
135                                        Collections.singleton(ErrorPageFilterConfiguration.class));
136                }
137                return run(application);
138        }
139
140        /**
141         * Returns the {@code SpringApplicationBuilder} that is used to configure and create
142         * the {@link SpringApplication}. The default implementation returns a new
143         * {@code SpringApplicationBuilder} in its default state.
144         * @return the {@code SpringApplicationBuilder}.
145         * @since 1.3.0
146         */
147        protected SpringApplicationBuilder createSpringApplicationBuilder() {
148                return new SpringApplicationBuilder();
149        }
150
151        /**
152         * Called to run a fully configured {@link SpringApplication}.
153         * @param application the application to run
154         * @return the {@link WebApplicationContext}
155         */
156        protected WebApplicationContext run(SpringApplication application) {
157                return (WebApplicationContext) application.run();
158        }
159
160        private ApplicationContext getExistingRootWebApplicationContext(
161                        ServletContext servletContext) {
162                Object context = servletContext.getAttribute(
163                                WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
164                if (context instanceof ApplicationContext) {
165                        return (ApplicationContext) context;
166                }
167                return null;
168        }
169
170        /**
171         * Configure the application. Normally all you would need to do is to add sources
172         * (e.g. config classes) because other settings have sensible defaults. You might
173         * choose (for instance) to add default command line arguments, or set an active
174         * Spring profile.
175         * @param builder a builder for the application context
176         * @return the application builder
177         * @see SpringApplicationBuilder
178         */
179        protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
180                return builder;
181        }
182
183        private static final class WebEnvironmentPropertySourceInitializer
184                        implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
185
186                private final ServletContext servletContext;
187
188                private WebEnvironmentPropertySourceInitializer(ServletContext servletContext) {
189                        this.servletContext = servletContext;
190                }
191
192                @Override
193                public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
194                        ConfigurableEnvironment environment = event.getEnvironment();
195                        if (environment instanceof ConfigurableWebEnvironment) {
196                                ((ConfigurableWebEnvironment) environment)
197                                                .initPropertySources(this.servletContext, null);
198                        }
199                }
200
201                @Override
202                public int getOrder() {
203                        return Ordered.HIGHEST_PRECEDENCE;
204                }
205
206        }
207
208}