1 /*
2 * Copyright 2002-2008 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.springframework.web.context;
18
19 import java.io.IOException;
20 import java.util.Map;
21 import java.util.Properties;
22
23 import javax.servlet.ServletContext;
24
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27
28 import org.springframework.beans.BeanUtils;
29 import org.springframework.beans.BeansException;
30 import org.springframework.beans.factory.access.BeanFactoryLocator;
31 import org.springframework.beans.factory.access.BeanFactoryReference;
32 import org.springframework.context.ApplicationContext;
33 import org.springframework.context.ApplicationContextException;
34 import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
35 import org.springframework.core.CollectionFactory;
36 import org.springframework.core.io.ClassPathResource;
37 import org.springframework.core.io.support.PropertiesLoaderUtils;
38 import org.springframework.util.ClassUtils;
39
40 /**
41 * Performs the actual initialization work for the root application context.
42 * Called by {@link ContextLoaderListener} and {@link ContextLoaderServlet}.
43 *
44 * <p>Looks for a {@link #CONTEXT_CLASS_PARAM "contextClass"} parameter
45 * at the <code>web.xml</code> context-param level to specify the context
46 * class type, falling back to the default of
47 * {@link org.springframework.web.context.support.XmlWebApplicationContext}
48 * if not found. With the default ContextLoader implementation, any context class
49 * specified needs to implement the ConfigurableWebApplicationContext interface.
50 *
51 * <p>Processes a {@link #CONFIG_LOCATION_PARAM "contextConfigLocation"}
52 * context-param and passes its value to the context instance, parsing it into
53 * potentially multiple file paths which can be separated by any number of
54 * commas and spaces, e.g. "WEB-INF/applicationContext1.xml,
55 * WEB-INF/applicationContext2.xml". Ant-style path patterns are supported as well,
56 * e.g. "WEB-INF/*Context.xml,WEB-INF/spring*.xml" or "WEB-INF/**/*Context.xml".
57 * If not explicitly specified, the context implementation is supposed to use a
58 * default location (with XmlWebApplicationContext: "/WEB-INF/applicationContext.xml").
59 *
60 * <p>Note: In case of multiple config locations, later bean definitions will
61 * override ones defined in previously loaded files, at least when using one of
62 * Spring's default ApplicationContext implementations. This can be leveraged
63 * to deliberately override certain bean definitions via an extra XML file.
64 *
65 * <p>Above and beyond loading the root application context, this class
66 * can optionally load or obtain and hook up a shared parent context to
67 * the root application context. See the
68 * {@link #loadParentContext(ServletContext)} method for more information.
69 *
70 * @author Juergen Hoeller
71 * @author Colin Sampaleanu
72 * @author Sam Brannen
73 * @since 17.02.2003
74 * @see ContextLoaderListener
75 * @see ContextLoaderServlet
76 * @see ConfigurableWebApplicationContext
77 * @see org.springframework.web.context.support.XmlWebApplicationContext
78 */
79 public class ContextLoader {
80
81 /**
82 * Config param for the root WebApplicationContext implementation class to
83 * use: "<code>contextClass</code>"
84 */
85 public static final String CONTEXT_CLASS_PARAM = "contextClass";
86
87 /**
88 * Name of servlet context parameter (i.e., "<code>contextConfigLocation</code>")
89 * that can specify the config location for the root context, falling back
90 * to the implementation's default otherwise.
91 * @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION
92 */
93 public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
94
95 /**
96 * Optional servlet context parameter (i.e., "<code>locatorFactorySelector</code>")
97 * used only when obtaining a parent context using the default implementation
98 * of {@link #loadParentContext(ServletContext servletContext)}.
99 * Specifies the 'selector' used in the
100 * {@link ContextSingletonBeanFactoryLocator#getInstance(String selector)}
101 * method call, which is used to obtain the BeanFactoryLocator instance from
102 * which the parent context is obtained.
103 * <p>The default is <code>classpath*:beanRefContext.xml</code>,
104 * matching the default applied for the
105 * {@link ContextSingletonBeanFactoryLocator#getInstance()} method.
106 * Supplying the "parentContextKey" parameter is sufficient in this case.
107 */
108 public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";
109
110 /**
111 * Optional servlet context parameter (i.e., "<code>parentContextKey</code>")
112 * used only when obtaining a parent context using the default implementation
113 * of {@link #loadParentContext(ServletContext servletContext)}.
114 * Specifies the 'factoryKey' used in the
115 * {@link BeanFactoryLocator#useBeanFactory(String factoryKey)} method call,
116 * obtaining the parent application context from the BeanFactoryLocator instance.
117 * <p>Supplying this "parentContextKey" parameter is sufficient when relying
118 * on the default <code>classpath*:beanRefContext.xml</code> selector for
119 * candidate factory references.
120 */
121 public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";
122
123 /**
124 * Name of the class path resource (relative to the ContextLoader class)
125 * that defines ContextLoader's default strategy names.
126 */
127 private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
128
129
130 private static final Properties defaultStrategies;
131
132 static {
133 // Load default strategy implementations from properties file.
134 // This is currently strictly internal and not meant to be customized
135 // by application developers.
136 try {
137 ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
138 defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
139 }
140 catch (IOException ex) {
141 throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
142 }
143 }
144
145
146 private static final Log logger = LogFactory.getLog(ContextLoader.class);
147
148 /**
149 * Map from (thread context) ClassLoader to WebApplicationContext.
150 * Often just holding one reference - if the ContextLoader class is
151 * deployed in the web app ClassLoader itself!
152 */
153 private static final Map currentContextPerThread = CollectionFactory.createConcurrentMapIfPossible(1);
154
155 /**
156 * The root WebApplicationContext instance that this loader manages.
157 */
158 private WebApplicationContext context;
159
160 /**
161 * Holds BeanFactoryReference when loading parent factory via
162 * ContextSingletonBeanFactoryLocator.
163 */
164 private BeanFactoryReference parentContextRef;
165
166
167 /**
168 * Initialize Spring's web application context for the given servlet context,
169 * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
170 * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
171 * @param servletContext current servlet context
172 * @return the new WebApplicationContext
173 * @throws IllegalStateException if there is already a root application context present
174 * @throws BeansException if the context failed to initialize
175 * @see #CONTEXT_CLASS_PARAM
176 * @see #CONFIG_LOCATION_PARAM
177 */
178 public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
179 throws IllegalStateException, BeansException {
180
181 if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
182 throw new IllegalStateException(
183 "Cannot initialize context because there is already a root application context present - " +
184 "check whether you have multiple ContextLoader* definitions in your web.xml!");
185 }
186
187 servletContext.log("Initializing Spring root WebApplicationContext");
188 if (logger.isInfoEnabled()) {
189 logger.info("Root WebApplicationContext: initialization started");
190 }
191 long startTime = System.currentTimeMillis();
192
193 try {
194 // Determine parent for root web application context, if any.
195 ApplicationContext parent = loadParentContext(servletContext);
196
197 // Store context in local instance variable, to guarantee that
198 // it is available on ServletContext shutdown.
199 this.context = createWebApplicationContext(servletContext, parent);
200 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
201 currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this.context);
202
203 if (logger.isDebugEnabled()) {
204 logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
205 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
206 }
207 if (logger.isInfoEnabled()) {
208 long elapsedTime = System.currentTimeMillis() - startTime;
209 logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
210 }
211
212 return this.context;
213 }
214 catch (RuntimeException ex) {
215 logger.error("Context initialization failed", ex);
216 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
217 throw ex;
218 }
219 catch (Error err) {
220 logger.error("Context initialization failed", err);
221 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
222 throw err;
223 }
224 }
225
226 /**
227 * Instantiate the root WebApplicationContext for this loader, either the
228 * default context class or a custom context class if specified.
229 * <p>This implementation expects custom contexts to implement the
230 * {@link ConfigurableWebApplicationContext} interface.
231 * Can be overridden in subclasses.
232 * <p>In addition, {@link #customizeContext} gets called prior to refreshing the
233 * context, allowing subclasses to perform custom modifications to the context.
234 * @param servletContext current servlet context
235 * @param parent the parent ApplicationContext to use, or <code>null</code> if none
236 * @return the root WebApplicationContext
237 * @throws BeansException if the context couldn't be initialized
238 * @see ConfigurableWebApplicationContext
239 */
240 protected WebApplicationContext createWebApplicationContext(
241 ServletContext servletContext, ApplicationContext parent) throws BeansException {
242
243 Class contextClass = determineContextClass(servletContext);
244 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
245 throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
246 "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
247 }
248
249 ConfigurableWebApplicationContext wac =
250 (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
251 wac.setParent(parent);
252 wac.setServletContext(servletContext);
253 wac.setConfigLocation(servletContext.getInitParameter(CONFIG_LOCATION_PARAM));
254 customizeContext(servletContext, wac);
255 wac.refresh();
256
257 return wac;
258 }
259
260 /**
261 * Return the WebApplicationContext implementation class to use, either the
262 * default XmlWebApplicationContext or a custom context class if specified.
263 * @param servletContext current servlet context
264 * @return the WebApplicationContext implementation class to use
265 * @throws ApplicationContextException if the context class couldn't be loaded
266 * @see #CONTEXT_CLASS_PARAM
267 * @see org.springframework.web.context.support.XmlWebApplicationContext
268 */
269 protected Class determineContextClass(ServletContext servletContext) throws ApplicationContextException {
270 String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
271 if (contextClassName != null) {
272 try {
273 return ClassUtils.forName(contextClassName);
274 }
275 catch (ClassNotFoundException ex) {
276 throw new ApplicationContextException(
277 "Failed to load custom context class [" + contextClassName + "]", ex);
278 }
279 }
280 else {
281 contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
282 try {
283 return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
284 }
285 catch (ClassNotFoundException ex) {
286 throw new ApplicationContextException(
287 "Failed to load default context class [" + contextClassName + "]", ex);
288 }
289 }
290 }
291
292 /**
293 * Customize the {@link ConfigurableWebApplicationContext} created by this
294 * ContextLoader after config locations have been supplied to the context
295 * but before the context is <em>refreshed</em>.
296 * <p>The default implementation is empty but can be overridden in subclasses
297 * to customize the application context.
298 * @param servletContext the current servlet context
299 * @param applicationContext the newly created application context
300 * @see #createWebApplicationContext(ServletContext, ApplicationContext)
301 */
302 protected void customizeContext(
303 ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
304 }
305
306 /**
307 * Template method with default implementation (which may be overridden by a
308 * subclass), to load or obtain an ApplicationContext instance which will be
309 * used as the parent context of the root WebApplicationContext. If the
310 * return value from the method is null, no parent context is set.
311 * <p>The main reason to load a parent context here is to allow multiple root
312 * web application contexts to all be children of a shared EAR context, or
313 * alternately to also share the same parent context that is visible to
314 * EJBs. For pure web applications, there is usually no need to worry about
315 * having a parent context to the root web application context.
316 * <p>The default implementation uses
317 * {@link org.springframework.context.access.ContextSingletonBeanFactoryLocator},
318 * configured via {@link #LOCATOR_FACTORY_SELECTOR_PARAM} and
319 * {@link #LOCATOR_FACTORY_KEY_PARAM}, to load a parent context
320 * which will be shared by all other users of ContextsingletonBeanFactoryLocator
321 * which also use the same configuration parameters.
322 * @param servletContext current servlet context
323 * @return the parent application context, or <code>null</code> if none
324 * @throws BeansException if the context couldn't be initialized
325 * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator
326 */
327 protected ApplicationContext loadParentContext(ServletContext servletContext)
328 throws BeansException {
329
330 ApplicationContext parentContext = null;
331 String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
332 String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
333
334 if (parentContextKey != null) {
335 // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"
336 BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
337 if (logger.isDebugEnabled()) {
338 logger.debug("Getting parent context definition: using parent context key of '" +
339 parentContextKey + "' with BeanFactoryLocator");
340 }
341 this.parentContextRef = locator.useBeanFactory(parentContextKey);
342 parentContext = (ApplicationContext) this.parentContextRef.getFactory();
343 }
344
345 return parentContext;
346 }
347
348 /**
349 * Close Spring's web application context for the given servlet context. If
350 * the default {@link #loadParentContext(ServletContext)} implementation,
351 * which uses ContextSingletonBeanFactoryLocator, has loaded any shared
352 * parent context, release one reference to that shared parent context.
353 * <p>If overriding {@link #loadParentContext(ServletContext)}, you may have
354 * to override this method as well.
355 * @param servletContext the ServletContext that the WebApplicationContext runs in
356 */
357 public void closeWebApplicationContext(ServletContext servletContext) {
358 servletContext.log("Closing Spring root WebApplicationContext");
359 try {
360 if (this.context instanceof ConfigurableWebApplicationContext) {
361 ((ConfigurableWebApplicationContext) this.context).close();
362 }
363 }
364 finally {
365 currentContextPerThread.remove(Thread.currentThread().getContextClassLoader());
366 servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
367 if (this.parentContextRef != null) {
368 this.parentContextRef.release();
369 }
370 }
371 }
372
373
374 /**
375 * Obtain the Spring root web application context for the current thread
376 * (i.e. for the current thread's context ClassLoader, which needs to be
377 * the web application's ClassLoader).
378 * @return the current root web application context, or <code>null</code>
379 * if none found
380 * @see org.springframework.web.context.support.SpringBeanAutowiringSupport
381 */
382 public static WebApplicationContext getCurrentWebApplicationContext() {
383 return (WebApplicationContext) currentContextPerThread.get(Thread.currentThread().getContextClassLoader());
384 }
385
386 }