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.context;
018
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Map;
023import java.util.Properties;
024import java.util.concurrent.ConcurrentHashMap;
025
026import javax.servlet.ServletContext;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030
031import org.springframework.beans.BeanUtils;
032import org.springframework.context.ApplicationContext;
033import org.springframework.context.ApplicationContextException;
034import org.springframework.context.ApplicationContextInitializer;
035import org.springframework.context.ConfigurableApplicationContext;
036import org.springframework.core.GenericTypeResolver;
037import org.springframework.core.annotation.AnnotationAwareOrderComparator;
038import org.springframework.core.env.ConfigurableEnvironment;
039import org.springframework.core.io.ClassPathResource;
040import org.springframework.core.io.support.PropertiesLoaderUtils;
041import org.springframework.lang.Nullable;
042import org.springframework.util.ClassUtils;
043import org.springframework.util.ObjectUtils;
044import org.springframework.util.StringUtils;
045
046/**
047 * Performs the actual initialization work for the root application context.
048 * Called by {@link ContextLoaderListener}.
049 *
050 * <p>Looks for a {@link #CONTEXT_CLASS_PARAM "contextClass"} parameter at the
051 * {@code web.xml} context-param level to specify the context class type, falling
052 * back to {@link org.springframework.web.context.support.XmlWebApplicationContext}
053 * if not found. With the default ContextLoader implementation, any context class
054 * specified needs to implement the {@link ConfigurableWebApplicationContext} interface.
055 *
056 * <p>Processes a {@link #CONFIG_LOCATION_PARAM "contextConfigLocation"} context-param
057 * and passes its value to the context instance, parsing it into potentially multiple
058 * file paths which can be separated by any number of commas and spaces, e.g.
059 * "WEB-INF/applicationContext1.xml, WEB-INF/applicationContext2.xml".
060 * Ant-style path patterns are supported as well, e.g.
061 * "WEB-INF/*Context.xml,WEB-INF/spring*.xml" or "WEB-INF/&#42;&#42;/*Context.xml".
062 * If not explicitly specified, the context implementation is supposed to use a
063 * default location (with XmlWebApplicationContext: "/WEB-INF/applicationContext.xml").
064 *
065 * <p>Note: In case of multiple config locations, later bean definitions will
066 * override ones defined in previously loaded files, at least when using one of
067 * Spring's default ApplicationContext implementations. This can be leveraged
068 * to deliberately override certain bean definitions via an extra XML file.
069 *
070 * <p>Above and beyond loading the root application context, this class can optionally
071 * load or obtain and hook up a shared parent context to the root application context.
072 * See the {@link #loadParentContext(ServletContext)} method for more information.
073 *
074 * <p>As of Spring 3.1, {@code ContextLoader} supports injecting the root web
075 * application context via the {@link #ContextLoader(WebApplicationContext)}
076 * constructor, allowing for programmatic configuration in Servlet 3.0+ environments.
077 * See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
078 *
079 * @author Juergen Hoeller
080 * @author Colin Sampaleanu
081 * @author Sam Brannen
082 * @since 17.02.2003
083 * @see ContextLoaderListener
084 * @see ConfigurableWebApplicationContext
085 * @see org.springframework.web.context.support.XmlWebApplicationContext
086 */
087public class ContextLoader {
088
089        /**
090         * Config param for the root WebApplicationContext id,
091         * to be used as serialization id for the underlying BeanFactory: {@value}.
092         */
093        public static final String CONTEXT_ID_PARAM = "contextId";
094
095        /**
096         * Name of servlet context parameter (i.e., {@value}) that can specify the
097         * config location for the root context, falling back to the implementation's
098         * default otherwise.
099         * @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION
100         */
101        public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
102
103        /**
104         * Config param for the root WebApplicationContext implementation class to use: {@value}.
105         * @see #determineContextClass(ServletContext)
106         */
107        public static final String CONTEXT_CLASS_PARAM = "contextClass";
108
109        /**
110         * Config param for {@link ApplicationContextInitializer} classes to use
111         * for initializing the root web application context: {@value}.
112         * @see #customizeContext(ServletContext, ConfigurableWebApplicationContext)
113         */
114        public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
115
116        /**
117         * Config param for global {@link ApplicationContextInitializer} classes to use
118         * for initializing all web application contexts in the current application: {@value}.
119         * @see #customizeContext(ServletContext, ConfigurableWebApplicationContext)
120         */
121        public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";
122
123        /**
124         * Any number of these characters are considered delimiters between
125         * multiple values in a single init-param String value.
126         */
127        private static final String INIT_PARAM_DELIMITERS = ",; \t\n";
128
129        /**
130         * Name of the class path resource (relative to the ContextLoader class)
131         * that defines ContextLoader's default strategy names.
132         */
133        private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
134
135
136        private static final Properties defaultStrategies;
137
138        static {
139                // Load default strategy implementations from properties file.
140                // This is currently strictly internal and not meant to be customized
141                // by application developers.
142                try {
143                        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
144                        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
145                }
146                catch (IOException ex) {
147                        throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
148                }
149        }
150
151
152        /**
153         * Map from (thread context) ClassLoader to corresponding 'current' WebApplicationContext.
154         */
155        private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread =
156                        new ConcurrentHashMap<>(1);
157
158        /**
159         * The 'current' WebApplicationContext, if the ContextLoader class is
160         * deployed in the web app ClassLoader itself.
161         */
162        @Nullable
163        private static volatile WebApplicationContext currentContext;
164
165
166        /**
167         * The root WebApplicationContext instance that this loader manages.
168         */
169        @Nullable
170        private WebApplicationContext context;
171
172        /** Actual ApplicationContextInitializer instances to apply to the context. */
173        private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers =
174                        new ArrayList<>();
175
176
177        /**
178         * Create a new {@code ContextLoader} that will create a web application context
179         * based on the "contextClass" and "contextConfigLocation" servlet context-params.
180         * See class-level documentation for details on default values for each.
181         * <p>This constructor is typically used when declaring the {@code
182         * ContextLoaderListener} subclass as a {@code <listener>} within {@code web.xml}, as
183         * a no-arg constructor is required.
184         * <p>The created application context will be registered into the ServletContext under
185         * the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
186         * and subclasses are free to call the {@link #closeWebApplicationContext} method on
187         * container shutdown to close the application context.
188         * @see #ContextLoader(WebApplicationContext)
189         * @see #initWebApplicationContext(ServletContext)
190         * @see #closeWebApplicationContext(ServletContext)
191         */
192        public ContextLoader() {
193        }
194
195        /**
196         * Create a new {@code ContextLoader} with the given application context. This
197         * constructor is useful in Servlet 3.0+ environments where instance-based
198         * registration of listeners is possible through the {@link ServletContext#addListener}
199         * API.
200         * <p>The context may or may not yet be {@linkplain
201         * ConfigurableApplicationContext#refresh() refreshed}. If it (a) is an implementation
202         * of {@link ConfigurableWebApplicationContext} and (b) has <strong>not</strong>
203         * already been refreshed (the recommended approach), then the following will occur:
204         * <ul>
205         * <li>If the given context has not already been assigned an {@linkplain
206         * ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
207         * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
208         * the application context</li>
209         * <li>{@link #customizeContext} will be called</li>
210         * <li>Any {@link ApplicationContextInitializer ApplicationContextInitializers} specified through the
211         * "contextInitializerClasses" init-param will be applied.</li>
212         * <li>{@link ConfigurableApplicationContext#refresh refresh()} will be called</li>
213         * </ul>
214         * If the context has already been refreshed or does not implement
215         * {@code ConfigurableWebApplicationContext}, none of the above will occur under the
216         * assumption that the user has performed these actions (or not) per his or her
217         * specific needs.
218         * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
219         * <p>In any case, the given application context will be registered into the
220         * ServletContext under the attribute name {@link
221         * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and subclasses are
222         * free to call the {@link #closeWebApplicationContext} method on container shutdown
223         * to close the application context.
224         * @param context the application context to manage
225         * @see #initWebApplicationContext(ServletContext)
226         * @see #closeWebApplicationContext(ServletContext)
227         */
228        public ContextLoader(WebApplicationContext context) {
229                this.context = context;
230        }
231
232
233        /**
234         * Specify which {@link ApplicationContextInitializer} instances should be used
235         * to initialize the application context used by this {@code ContextLoader}.
236         * @since 4.2
237         * @see #configureAndRefreshWebApplicationContext
238         * @see #customizeContext
239         */
240        @SuppressWarnings("unchecked")
241        public void setContextInitializers(@Nullable ApplicationContextInitializer<?>... initializers) {
242                if (initializers != null) {
243                        for (ApplicationContextInitializer<?> initializer : initializers) {
244                                this.contextInitializers.add((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer);
245                        }
246                }
247        }
248
249
250        /**
251         * Initialize Spring's web application context for the given servlet context,
252         * using the application context provided at construction time, or creating a new one
253         * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
254         * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
255         * @param servletContext current servlet context
256         * @return the new WebApplicationContext
257         * @see #ContextLoader(WebApplicationContext)
258         * @see #CONTEXT_CLASS_PARAM
259         * @see #CONFIG_LOCATION_PARAM
260         */
261        public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
262                if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
263                        throw new IllegalStateException(
264                                        "Cannot initialize context because there is already a root application context present - " +
265                                        "check whether you have multiple ContextLoader* definitions in your web.xml!");
266                }
267
268                servletContext.log("Initializing Spring root WebApplicationContext");
269                Log logger = LogFactory.getLog(ContextLoader.class);
270                if (logger.isInfoEnabled()) {
271                        logger.info("Root WebApplicationContext: initialization started");
272                }
273                long startTime = System.currentTimeMillis();
274
275                try {
276                        // Store context in local instance variable, to guarantee that
277                        // it is available on ServletContext shutdown.
278                        if (this.context == null) {
279                                this.context = createWebApplicationContext(servletContext);
280                        }
281                        if (this.context instanceof ConfigurableWebApplicationContext) {
282                                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
283                                if (!cwac.isActive()) {
284                                        // The context has not yet been refreshed -> provide services such as
285                                        // setting the parent context, setting the application context id, etc
286                                        if (cwac.getParent() == null) {
287                                                // The context instance was injected without an explicit parent ->
288                                                // determine parent for root web application context, if any.
289                                                ApplicationContext parent = loadParentContext(servletContext);
290                                                cwac.setParent(parent);
291                                        }
292                                        configureAndRefreshWebApplicationContext(cwac, servletContext);
293                                }
294                        }
295                        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
296
297                        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
298                        if (ccl == ContextLoader.class.getClassLoader()) {
299                                currentContext = this.context;
300                        }
301                        else if (ccl != null) {
302                                currentContextPerThread.put(ccl, this.context);
303                        }
304
305                        if (logger.isInfoEnabled()) {
306                                long elapsedTime = System.currentTimeMillis() - startTime;
307                                logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
308                        }
309
310                        return this.context;
311                }
312                catch (RuntimeException | Error ex) {
313                        logger.error("Context initialization failed", ex);
314                        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
315                        throw ex;
316                }
317        }
318
319        /**
320         * Instantiate the root WebApplicationContext for this loader, either the
321         * default context class or a custom context class if specified.
322         * <p>This implementation expects custom contexts to implement the
323         * {@link ConfigurableWebApplicationContext} interface.
324         * Can be overridden in subclasses.
325         * <p>In addition, {@link #customizeContext} gets called prior to refreshing the
326         * context, allowing subclasses to perform custom modifications to the context.
327         * @param sc current servlet context
328         * @return the root WebApplicationContext
329         * @see ConfigurableWebApplicationContext
330         */
331        protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
332                Class<?> contextClass = determineContextClass(sc);
333                if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
334                        throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
335                                        "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
336                }
337                return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
338        }
339
340        /**
341         * Return the WebApplicationContext implementation class to use, either the
342         * default XmlWebApplicationContext or a custom context class if specified.
343         * @param servletContext current servlet context
344         * @return the WebApplicationContext implementation class to use
345         * @see #CONTEXT_CLASS_PARAM
346         * @see org.springframework.web.context.support.XmlWebApplicationContext
347         */
348        protected Class<?> determineContextClass(ServletContext servletContext) {
349                String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
350                if (contextClassName != null) {
351                        try {
352                                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
353                        }
354                        catch (ClassNotFoundException ex) {
355                                throw new ApplicationContextException(
356                                                "Failed to load custom context class [" + contextClassName + "]", ex);
357                        }
358                }
359                else {
360                        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
361                        try {
362                                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
363                        }
364                        catch (ClassNotFoundException ex) {
365                                throw new ApplicationContextException(
366                                                "Failed to load default context class [" + contextClassName + "]", ex);
367                        }
368                }
369        }
370
371        protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
372                if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
373                        // The application context id is still set to its original default value
374                        // -> assign a more useful id based on available information
375                        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
376                        if (idParam != null) {
377                                wac.setId(idParam);
378                        }
379                        else {
380                                // Generate default id...
381                                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
382                                                ObjectUtils.getDisplayString(sc.getContextPath()));
383                        }
384                }
385
386                wac.setServletContext(sc);
387                String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
388                if (configLocationParam != null) {
389                        wac.setConfigLocation(configLocationParam);
390                }
391
392                // The wac environment's #initPropertySources will be called in any case when the context
393                // is refreshed; do it eagerly here to ensure servlet property sources are in place for
394                // use in any post-processing or initialization that occurs below prior to #refresh
395                ConfigurableEnvironment env = wac.getEnvironment();
396                if (env instanceof ConfigurableWebEnvironment) {
397                        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
398                }
399
400                customizeContext(sc, wac);
401                wac.refresh();
402        }
403
404        /**
405         * Customize the {@link ConfigurableWebApplicationContext} created by this
406         * ContextLoader after config locations have been supplied to the context
407         * but before the context is <em>refreshed</em>.
408         * <p>The default implementation {@linkplain #determineContextInitializerClasses(ServletContext)
409         * determines} what (if any) context initializer classes have been specified through
410         * {@linkplain #CONTEXT_INITIALIZER_CLASSES_PARAM context init parameters} and
411         * {@linkplain ApplicationContextInitializer#initialize invokes each} with the
412         * given web application context.
413         * <p>Any {@code ApplicationContextInitializers} implementing
414         * {@link org.springframework.core.Ordered Ordered} or marked with @{@link
415         * org.springframework.core.annotation.Order Order} will be sorted appropriately.
416         * @param sc the current servlet context
417         * @param wac the newly created application context
418         * @see #CONTEXT_INITIALIZER_CLASSES_PARAM
419         * @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext)
420         */
421        protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
422                List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
423                                determineContextInitializerClasses(sc);
424
425                for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
426                        Class<?> initializerContextClass =
427                                        GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
428                        if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {
429                                throw new ApplicationContextException(String.format(
430                                                "Could not apply context initializer [%s] since its generic parameter [%s] " +
431                                                "is not assignable from the type of application context used by this " +
432                                                "context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(),
433                                                wac.getClass().getName()));
434                        }
435                        this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
436                }
437
438                AnnotationAwareOrderComparator.sort(this.contextInitializers);
439                for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
440                        initializer.initialize(wac);
441                }
442        }
443
444        /**
445         * Return the {@link ApplicationContextInitializer} implementation classes to use
446         * if any have been specified by {@link #CONTEXT_INITIALIZER_CLASSES_PARAM}.
447         * @param servletContext current servlet context
448         * @see #CONTEXT_INITIALIZER_CLASSES_PARAM
449         */
450        protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
451                        determineContextInitializerClasses(ServletContext servletContext) {
452
453                List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
454                                new ArrayList<>();
455
456                String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
457                if (globalClassNames != null) {
458                        for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
459                                classes.add(loadInitializerClass(className));
460                        }
461                }
462
463                String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
464                if (localClassNames != null) {
465                        for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
466                                classes.add(loadInitializerClass(className));
467                        }
468                }
469
470                return classes;
471        }
472
473        @SuppressWarnings("unchecked")
474        private Class<ApplicationContextInitializer<ConfigurableApplicationContext>> loadInitializerClass(String className) {
475                try {
476                        Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
477                        if (!ApplicationContextInitializer.class.isAssignableFrom(clazz)) {
478                                throw new ApplicationContextException(
479                                                "Initializer class does not implement ApplicationContextInitializer interface: " + clazz);
480                        }
481                        return (Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz;
482                }
483                catch (ClassNotFoundException ex) {
484                        throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);
485                }
486        }
487
488        /**
489         * Template method with default implementation (which may be overridden by a
490         * subclass), to load or obtain an ApplicationContext instance which will be
491         * used as the parent context of the root WebApplicationContext. If the
492         * return value from the method is null, no parent context is set.
493         * <p>The main reason to load a parent context here is to allow multiple root
494         * web application contexts to all be children of a shared EAR context, or
495         * alternately to also share the same parent context that is visible to
496         * EJBs. For pure web applications, there is usually no need to worry about
497         * having a parent context to the root web application context.
498         * <p>The default implementation simply returns {@code null}, as of 5.0.
499         * @param servletContext current servlet context
500         * @return the parent application context, or {@code null} if none
501         */
502        @Nullable
503        protected ApplicationContext loadParentContext(ServletContext servletContext) {
504                return null;
505        }
506
507        /**
508         * Close Spring's web application context for the given servlet context.
509         * <p>If overriding {@link #loadParentContext(ServletContext)}, you may have
510         * to override this method as well.
511         * @param servletContext the ServletContext that the WebApplicationContext runs in
512         */
513        public void closeWebApplicationContext(ServletContext servletContext) {
514                servletContext.log("Closing Spring root WebApplicationContext");
515                try {
516                        if (this.context instanceof ConfigurableWebApplicationContext) {
517                                ((ConfigurableWebApplicationContext) this.context).close();
518                        }
519                }
520                finally {
521                        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
522                        if (ccl == ContextLoader.class.getClassLoader()) {
523                                currentContext = null;
524                        }
525                        else if (ccl != null) {
526                                currentContextPerThread.remove(ccl);
527                        }
528                        servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
529                }
530        }
531
532
533        /**
534         * Obtain the Spring root web application context for the current thread
535         * (i.e. for the current thread's context ClassLoader, which needs to be
536         * the web application's ClassLoader).
537         * @return the current root web application context, or {@code null}
538         * if none found
539         * @see org.springframework.web.context.support.SpringBeanAutowiringSupport
540         */
541        @Nullable
542        public static WebApplicationContext getCurrentWebApplicationContext() {
543                ClassLoader ccl = Thread.currentThread().getContextClassLoader();
544                if (ccl != null) {
545                        WebApplicationContext ccpt = currentContextPerThread.get(ccl);
546                        if (ccpt != null) {
547                                return ccpt;
548                        }
549                }
550                return currentContext;
551        }
552
553}