001/*
002 * Copyright 2012-2016 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.context.embedded;
018
019import java.util.Collection;
020import java.util.Collections;
021import java.util.EventListener;
022import java.util.HashMap;
023import java.util.LinkedHashSet;
024import java.util.Map;
025import java.util.Set;
026
027import javax.servlet.Filter;
028import javax.servlet.Servlet;
029import javax.servlet.ServletConfig;
030import javax.servlet.ServletContext;
031import javax.servlet.ServletException;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035
036import org.springframework.beans.BeansException;
037import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
038import org.springframework.beans.factory.config.Scope;
039import org.springframework.boot.web.servlet.FilterRegistrationBean;
040import org.springframework.boot.web.servlet.ServletContextInitializer;
041import org.springframework.boot.web.servlet.ServletContextInitializerBeans;
042import org.springframework.boot.web.servlet.ServletRegistrationBean;
043import org.springframework.context.ApplicationContext;
044import org.springframework.context.ApplicationContextException;
045import org.springframework.core.io.Resource;
046import org.springframework.util.StringUtils;
047import org.springframework.web.context.ContextLoader;
048import org.springframework.web.context.ContextLoaderListener;
049import org.springframework.web.context.ServletContextAware;
050import org.springframework.web.context.WebApplicationContext;
051import org.springframework.web.context.support.GenericWebApplicationContext;
052import org.springframework.web.context.support.ServletContextAwareProcessor;
053import org.springframework.web.context.support.ServletContextResource;
054import org.springframework.web.context.support.WebApplicationContextUtils;
055
056/**
057 * A {@link WebApplicationContext} that can be used to bootstrap itself from a contained
058 * {@link EmbeddedServletContainerFactory} bean.
059 * <p>
060 * This context will create, initialize and run an {@link EmbeddedServletContainer} by
061 * searching for a single {@link EmbeddedServletContainerFactory} bean within the
062 * {@link ApplicationContext} itself. The {@link EmbeddedServletContainerFactory} is free
063 * to use standard Spring concepts (such as dependency injection, lifecycle callbacks and
064 * property placeholder variables).
065 * <p>
066 * In addition, any {@link Servlet} or {@link Filter} beans defined in the context will be
067 * automatically registered with the embedded Servlet container. In the case of a single
068 * Servlet bean, the '/' mapping will be used. If multiple Servlet beans are found then
069 * the lowercase bean name will be used as a mapping prefix. Any Servlet named
070 * 'dispatcherServlet' will always be mapped to '/'. Filter beans will be mapped to all
071 * URLs ('/*').
072 * <p>
073 * For more advanced configuration, the context can instead define beans that implement
074 * the {@link ServletContextInitializer} interface (most often
075 * {@link ServletRegistrationBean}s and/or {@link FilterRegistrationBean}s). To prevent
076 * double registration, the use of {@link ServletContextInitializer} beans will disable
077 * automatic Servlet and Filter bean registration.
078 * <p>
079 * Although this context can be used directly, most developers should consider using the
080 * {@link AnnotationConfigEmbeddedWebApplicationContext} or
081 * {@link XmlEmbeddedWebApplicationContext} variants.
082 *
083 * @author Phillip Webb
084 * @author Dave Syer
085 * @see AnnotationConfigEmbeddedWebApplicationContext
086 * @see XmlEmbeddedWebApplicationContext
087 * @see EmbeddedServletContainerFactory
088 */
089public class EmbeddedWebApplicationContext extends GenericWebApplicationContext {
090
091        private static final Log logger = LogFactory
092                        .getLog(EmbeddedWebApplicationContext.class);
093
094        /**
095         * Constant value for the DispatcherServlet bean name. A Servlet bean with this name
096         * is deemed to be the "main" servlet and is automatically given a mapping of "/" by
097         * default. To change the default behaviour you can use a
098         * {@link ServletRegistrationBean} or a different bean name.
099         */
100        public static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet";
101
102        private volatile EmbeddedServletContainer embeddedServletContainer;
103
104        private ServletConfig servletConfig;
105
106        private String namespace;
107
108        /**
109         * Register ServletContextAwareProcessor.
110         * @see ServletContextAwareProcessor
111         */
112        @Override
113        protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
114                beanFactory.addBeanPostProcessor(
115                                new WebApplicationContextServletContextAwareProcessor(this));
116                beanFactory.ignoreDependencyInterface(ServletContextAware.class);
117        }
118
119        @Override
120        public final void refresh() throws BeansException, IllegalStateException {
121                try {
122                        super.refresh();
123                }
124                catch (RuntimeException ex) {
125                        stopAndReleaseEmbeddedServletContainer();
126                        throw ex;
127                }
128        }
129
130        @Override
131        protected void onRefresh() {
132                super.onRefresh();
133                try {
134                        createEmbeddedServletContainer();
135                }
136                catch (Throwable ex) {
137                        throw new ApplicationContextException("Unable to start embedded container",
138                                        ex);
139                }
140        }
141
142        @Override
143        protected void finishRefresh() {
144                super.finishRefresh();
145                EmbeddedServletContainer localContainer = startEmbeddedServletContainer();
146                if (localContainer != null) {
147                        publishEvent(
148                                        new EmbeddedServletContainerInitializedEvent(this, localContainer));
149                }
150        }
151
152        @Override
153        protected void onClose() {
154                super.onClose();
155                stopAndReleaseEmbeddedServletContainer();
156        }
157
158        private void createEmbeddedServletContainer() {
159                EmbeddedServletContainer localContainer = this.embeddedServletContainer;
160                ServletContext localServletContext = getServletContext();
161                if (localContainer == null && localServletContext == null) {
162                        EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
163                        this.embeddedServletContainer = containerFactory
164                                        .getEmbeddedServletContainer(getSelfInitializer());
165                }
166                else if (localServletContext != null) {
167                        try {
168                                getSelfInitializer().onStartup(localServletContext);
169                        }
170                        catch (ServletException ex) {
171                                throw new ApplicationContextException("Cannot initialize servlet context",
172                                                ex);
173                        }
174                }
175                initPropertySources();
176        }
177
178        /**
179         * Returns the {@link EmbeddedServletContainerFactory} that should be used to create
180         * the embedded servlet container. By default this method searches for a suitable bean
181         * in the context itself.
182         * @return a {@link EmbeddedServletContainerFactory} (never {@code null})
183         */
184        protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
185                // Use bean names so that we don't consider the hierarchy
186                String[] beanNames = getBeanFactory()
187                                .getBeanNamesForType(EmbeddedServletContainerFactory.class);
188                if (beanNames.length == 0) {
189                        throw new ApplicationContextException(
190                                        "Unable to start EmbeddedWebApplicationContext due to missing "
191                                                        + "EmbeddedServletContainerFactory bean.");
192                }
193                if (beanNames.length > 1) {
194                        throw new ApplicationContextException(
195                                        "Unable to start EmbeddedWebApplicationContext due to multiple "
196                                                        + "EmbeddedServletContainerFactory beans : "
197                                                        + StringUtils.arrayToCommaDelimitedString(beanNames));
198                }
199                return getBeanFactory().getBean(beanNames[0],
200                                EmbeddedServletContainerFactory.class);
201        }
202
203        /**
204         * Returns the {@link ServletContextInitializer} that will be used to complete the
205         * setup of this {@link WebApplicationContext}.
206         * @return the self initializer
207         * @see #prepareEmbeddedWebApplicationContext(ServletContext)
208         */
209        private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
210                return new ServletContextInitializer() {
211                        @Override
212                        public void onStartup(ServletContext servletContext) throws ServletException {
213                                selfInitialize(servletContext);
214                        }
215                };
216        }
217
218        private void selfInitialize(ServletContext servletContext) throws ServletException {
219                prepareEmbeddedWebApplicationContext(servletContext);
220                ConfigurableListableBeanFactory beanFactory = getBeanFactory();
221                ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
222                                beanFactory);
223                WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
224                                getServletContext());
225                existingScopes.restore();
226                WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
227                                getServletContext());
228                for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
229                        beans.onStartup(servletContext);
230                }
231        }
232
233        /**
234         * Returns {@link ServletContextInitializer}s that should be used with the embedded
235         * Servlet context. By default this method will first attempt to find
236         * {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain
237         * {@link EventListener} beans.
238         * @return the servlet initializer beans
239         */
240        protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
241                return new ServletContextInitializerBeans(getBeanFactory());
242        }
243
244        /**
245         * Prepare the {@link WebApplicationContext} with the given fully loaded
246         * {@link ServletContext}. This method is usually called from
247         * {@link ServletContextInitializer#onStartup(ServletContext)} and is similar to the
248         * functionality usually provided by a {@link ContextLoaderListener}.
249         * @param servletContext the operational servlet context
250         */
251        protected void prepareEmbeddedWebApplicationContext(ServletContext servletContext) {
252                Object rootContext = servletContext.getAttribute(
253                                WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
254                if (rootContext != null) {
255                        if (rootContext == this) {
256                                throw new IllegalStateException(
257                                                "Cannot initialize context because there is already a root application context present - "
258                                                                + "check whether you have multiple ServletContextInitializers!");
259                        }
260                        return;
261                }
262                Log logger = LogFactory.getLog(ContextLoader.class);
263                servletContext.log("Initializing Spring embedded WebApplicationContext");
264                try {
265                        servletContext.setAttribute(
266                                        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
267                        if (logger.isDebugEnabled()) {
268                                logger.debug(
269                                                "Published root WebApplicationContext as ServletContext attribute with name ["
270                                                                + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
271                                                                + "]");
272                        }
273                        setServletContext(servletContext);
274                        if (logger.isInfoEnabled()) {
275                                long elapsedTime = System.currentTimeMillis() - getStartupDate();
276                                logger.info("Root WebApplicationContext: initialization completed in "
277                                                + elapsedTime + " ms");
278                        }
279                }
280                catch (RuntimeException ex) {
281                        logger.error("Context initialization failed", ex);
282                        servletContext.setAttribute(
283                                        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
284                        throw ex;
285                }
286                catch (Error ex) {
287                        logger.error("Context initialization failed", ex);
288                        servletContext.setAttribute(
289                                        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
290                        throw ex;
291                }
292        }
293
294        private EmbeddedServletContainer startEmbeddedServletContainer() {
295                EmbeddedServletContainer localContainer = this.embeddedServletContainer;
296                if (localContainer != null) {
297                        localContainer.start();
298                }
299                return localContainer;
300        }
301
302        private void stopAndReleaseEmbeddedServletContainer() {
303                EmbeddedServletContainer localContainer = this.embeddedServletContainer;
304                if (localContainer != null) {
305                        try {
306                                localContainer.stop();
307                                this.embeddedServletContainer = null;
308                        }
309                        catch (Exception ex) {
310                                throw new IllegalStateException(ex);
311                        }
312                }
313        }
314
315        @Override
316        protected Resource getResourceByPath(String path) {
317                if (getServletContext() == null) {
318                        return new ClassPathContextResource(path, getClassLoader());
319                }
320                return new ServletContextResource(getServletContext(), path);
321        }
322
323        @Override
324        public void setNamespace(String namespace) {
325                this.namespace = namespace;
326        }
327
328        @Override
329        public String getNamespace() {
330                return this.namespace;
331        }
332
333        @Override
334        public void setServletConfig(ServletConfig servletConfig) {
335                this.servletConfig = servletConfig;
336        }
337
338        @Override
339        public ServletConfig getServletConfig() {
340                return this.servletConfig;
341        }
342
343        /**
344         * Returns the {@link EmbeddedServletContainer} that was created by the context or
345         * {@code null} if the container has not yet been created.
346         * @return the embedded servlet container
347         */
348        public EmbeddedServletContainer getEmbeddedServletContainer() {
349                return this.embeddedServletContainer;
350        }
351
352        /**
353         * Utility class to store and restore any user defined scopes. This allow scopes to be
354         * registered in an ApplicationContextInitializer in the same way as they would in a
355         * classic non-embedded web application context.
356         */
357        public static class ExistingWebApplicationScopes {
358
359                private static final Set<String> SCOPES;
360
361                static {
362                        Set<String> scopes = new LinkedHashSet<String>();
363                        scopes.add(WebApplicationContext.SCOPE_REQUEST);
364                        scopes.add(WebApplicationContext.SCOPE_SESSION);
365                        scopes.add(WebApplicationContext.SCOPE_GLOBAL_SESSION);
366                        SCOPES = Collections.unmodifiableSet(scopes);
367                }
368
369                private final ConfigurableListableBeanFactory beanFactory;
370
371                private final Map<String, Scope> scopes = new HashMap<String, Scope>();
372
373                public ExistingWebApplicationScopes(ConfigurableListableBeanFactory beanFactory) {
374                        this.beanFactory = beanFactory;
375                        for (String scopeName : SCOPES) {
376                                Scope scope = beanFactory.getRegisteredScope(scopeName);
377                                if (scope != null) {
378                                        this.scopes.put(scopeName, scope);
379                                }
380                        }
381                }
382
383                public void restore() {
384                        for (Map.Entry<String, Scope> entry : this.scopes.entrySet()) {
385                                if (logger.isInfoEnabled()) {
386                                        logger.info("Restoring user defined scope " + entry.getKey());
387                                }
388                                this.beanFactory.registerScope(entry.getKey(), entry.getValue());
389                        }
390                }
391
392        }
393
394}