001: /*
002: * Copyright 2002-2007 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:
017: package org.springframework.web.context;
018:
019: import java.io.IOException;
020: import java.util.Properties;
021:
022: import javax.servlet.ServletContext;
023:
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026:
027: import org.springframework.beans.BeanUtils;
028: import org.springframework.beans.BeansException;
029: import org.springframework.beans.factory.access.BeanFactoryLocator;
030: import org.springframework.beans.factory.access.BeanFactoryReference;
031: import org.springframework.context.ApplicationContext;
032: import org.springframework.context.ApplicationContextException;
033: import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
034: import org.springframework.core.io.ClassPathResource;
035: import org.springframework.core.io.support.PropertiesLoaderUtils;
036: import org.springframework.util.ClassUtils;
037: import org.springframework.util.StringUtils;
038:
039: /**
040: * Performs the actual initialization work for the root application context.
041: * Called by {@link ContextLoaderListener} and {@link ContextLoaderServlet}.
042: *
043: * <p>Looks for a "contextClass" parameter at the web.xml context-param level
044: * to specify the context class type, falling back to the default of
045: * {@link org.springframework.web.context.support.XmlWebApplicationContext}
046: * if not found. With the default ContextLoader implementation, any context class
047: * specified needs to implement the ConfigurableWebApplicationContext interface.
048: *
049: * <p>Processes a "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}"
050: * context-param and passes its value to the context instance, parsing it into
051: * potentially multiple file paths which can be separated by any number of
052: * commas and spaces, e.g. "WEB-INF/applicationContext1.xml,
053: * WEB-INF/applicationContext2.xml". Ant-style path patterns are supported as well,
054: * e.g. "WEB-INF/*Context.xml,WEB-INF/spring*.xml" or "WEB-INF/**/*Context.xml".
055: * If not explicitly specified, the context implementation is supposed to use a
056: * default location (with XmlWebApplicationContext: "/WEB-INF/applicationContext.xml").
057: *
058: * <p>Note: In case of multiple config locations, later bean definitions will
059: * override ones defined in previously loaded files, at least when using one of
060: * Spring's default ApplicationContext implementations. This can be leveraged
061: * to deliberately override certain bean definitions via an extra XML file.
062: *
063: * <p>Above and beyond loading the root application context, this class can
064: * optionally load or obtain and hook up a shared parent context to the root
065: * application context. See the
066: * {@link #loadParentContext(ServletContext)} method for more information.
067: *
068: * @author Juergen Hoeller
069: * @author Colin Sampaleanu
070: * @author Sam Brannen
071: * @since 17.02.2003
072: * @see ContextLoaderListener
073: * @see ContextLoaderServlet
074: * @see ConfigurableWebApplicationContext
075: * @see org.springframework.web.context.support.XmlWebApplicationContext
076: */
077: public class ContextLoader {
078:
079: /**
080: * Config param for the root WebApplicationContext implementation class to
081: * use: "<code>contextClass</code>"
082: */
083: public static final String CONTEXT_CLASS_PARAM = "contextClass";
084:
085: /**
086: * Name of servlet context parameter (i.e., "<code>contextConfigLocation</code>")
087: * that can specify the config location for the root context, falling back
088: * to the implementation's default otherwise.
089: * @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION
090: */
091: public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
092:
093: /**
094: * Optional servlet context parameter (i.e., "<code>locatorFactorySelector</code>")
095: * used only when obtaining a parent context using the default implementation
096: * of {@link #loadParentContext(ServletContext servletContext)}.
097: * Specifies the 'selector' used in the
098: * {@link ContextSingletonBeanFactoryLocator#getInstance(String selector)}
099: * method call, which is used to obtain the BeanFactoryLocator instance from
100: * which the parent context is obtained.
101: * <p>The default is <code>classpath*:beanRefContext.xml</code>,
102: * matching the default applied for the
103: * {@link ContextSingletonBeanFactoryLocator#getInstance()} method.
104: * Supplying the "parentContextKey" parameter is sufficient in this case.
105: */
106: public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";
107:
108: /**
109: * Optional servlet context parameter (i.e., "<code>parentContextKey</code>")
110: * used only when obtaining a parent context using the default implementation
111: * of {@link #loadParentContext(ServletContext servletContext)}.
112: * Specifies the 'factoryKey' used in the
113: * {@link BeanFactoryLocator#useBeanFactory(String factoryKey)} method call,
114: * obtaining the parent application context from the BeanFactoryLocator instance.
115: * <p>Supplying this "parentContextKey" parameter is sufficient when relying
116: * on the default <code>classpath*:beanRefContext.xml</code> selector for
117: * candidate factory references.
118: */
119: public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";
120:
121: /**
122: * Name of the class path resource (relative to the ContextLoader class)
123: * that defines ContextLoader's default strategy names.
124: */
125: private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
126:
127: private static final Properties defaultStrategies;
128:
129: static {
130: // Load default strategy implementations from properties file.
131: // This is currently strictly internal and not meant to be customized
132: // by application developers.
133: try {
134: ClassPathResource resource = new ClassPathResource(
135: DEFAULT_STRATEGIES_PATH, ContextLoader.class);
136: defaultStrategies = PropertiesLoaderUtils
137: .loadProperties(resource);
138: } catch (IOException ex) {
139: throw new IllegalStateException(
140: "Could not load 'ContextLoader.properties': "
141: + ex.getMessage());
142: }
143: }
144:
145: private final Log logger = LogFactory.getLog(ContextLoader.class);
146:
147: /**
148: * The root WebApplicationContext instance that this loader manages.
149: */
150: private WebApplicationContext context;
151:
152: /**
153: * Holds BeanFactoryReference when loading parent factory via
154: * ContextSingletonBeanFactoryLocator.
155: */
156: private BeanFactoryReference parentContextRef;
157:
158: /**
159: * Initialize Spring's web application context for the given servlet context,
160: * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
161: * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
162: * @param servletContext current servlet context
163: * @return the new WebApplicationContext
164: * @throws IllegalStateException if there is already a root application context present
165: * @throws BeansException if the context failed to initialize
166: * @see #CONTEXT_CLASS_PARAM
167: * @see #CONFIG_LOCATION_PARAM
168: */
169: public WebApplicationContext initWebApplicationContext(
170: ServletContext servletContext)
171: throws IllegalStateException, BeansException {
172:
173: if (servletContext
174: .getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
175: throw new IllegalStateException(
176: "Cannot initialize context because there is already a root application context present - "
177: + "check whether you have multiple ContextLoader* definitions in your web.xml!");
178: }
179:
180: servletContext
181: .log("Initializing Spring root WebApplicationContext");
182: if (this .logger.isInfoEnabled()) {
183: this .logger
184: .info("Root WebApplicationContext: initialization started");
185: }
186: long startTime = System.currentTimeMillis();
187:
188: try {
189: // Determine parent for root web application context, if any.
190: ApplicationContext parent = loadParentContext(servletContext);
191:
192: // Store context in local instance variable, to guarantee that
193: // it is available on ServletContext shutdown.
194: this .context = createWebApplicationContext(servletContext,
195: parent);
196: servletContext
197: .setAttribute(
198: WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
199: this .context);
200:
201: if (this .logger.isDebugEnabled()) {
202: this .logger
203: .debug("Published root WebApplicationContext as ServletContext attribute with name ["
204: + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
205: + "]");
206: }
207: if (this .logger.isInfoEnabled()) {
208: long elapsedTime = System.currentTimeMillis()
209: - startTime;
210: this .logger
211: .info("Root WebApplicationContext: initialization completed in "
212: + elapsedTime + " ms");
213: }
214:
215: return this .context;
216: } catch (RuntimeException ex) {
217: this .logger.error("Context initialization failed", ex);
218: servletContext
219: .setAttribute(
220: WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
221: ex);
222: throw ex;
223: } catch (Error err) {
224: this .logger.error("Context initialization failed", err);
225: servletContext
226: .setAttribute(
227: WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
228: err);
229: throw err;
230: }
231: }
232:
233: /**
234: * Instantiate the root WebApplicationContext for this loader, either the
235: * default context class or a custom context class if specified.
236: * <p>This implementation expects custom contexts to implement the
237: * ConfigurableWebApplicationContext interface. Can be overridden in subclasses.
238: * <p>In addition, {@link #customizeContext} gets called prior to refreshing the
239: * context, allowing subclasses to perform custom modifications to the context.
240: * @param servletContext current servlet context
241: * @param parent the parent ApplicationContext to use, or <code>null</code> if none
242: * @return the root WebApplicationContext
243: * @throws BeansException if the context couldn't be initialized
244: * @see ConfigurableWebApplicationContext
245: */
246: protected WebApplicationContext createWebApplicationContext(
247: ServletContext servletContext, ApplicationContext parent)
248: throws BeansException {
249:
250: Class contextClass = determineContextClass(servletContext);
251: if (!ConfigurableWebApplicationContext.class
252: .isAssignableFrom(contextClass)) {
253: throw new ApplicationContextException(
254: "Custom context class ["
255: + contextClass.getName()
256: + "] is not of type ["
257: + ConfigurableWebApplicationContext.class
258: .getName() + "]");
259: }
260:
261: ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils
262: .instantiateClass(contextClass);
263: wac.setParent(parent);
264: wac.setServletContext(servletContext);
265: String configLocation = servletContext
266: .getInitParameter(CONFIG_LOCATION_PARAM);
267: if (configLocation != null) {
268: wac
269: .setConfigLocations(StringUtils
270: .tokenizeToStringArray(
271: configLocation,
272: ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
273: }
274:
275: customizeContext(servletContext, wac);
276:
277: wac.refresh();
278: return wac;
279: }
280:
281: /**
282: * Return the WebApplicationContext implementation class to use, either the
283: * default XmlWebApplicationContext or a custom context class if specified.
284: * @param servletContext current servlet context
285: * @return the WebApplicationContext implementation class to use
286: * @throws ApplicationContextException if the context class couldn't be loaded
287: * @see #CONTEXT_CLASS_PARAM
288: * @see org.springframework.web.context.support.XmlWebApplicationContext
289: */
290: protected Class determineContextClass(ServletContext servletContext)
291: throws ApplicationContextException {
292: String contextClassName = servletContext
293: .getInitParameter(CONTEXT_CLASS_PARAM);
294: if (contextClassName != null) {
295: try {
296: return ClassUtils.forName(contextClassName);
297: } catch (ClassNotFoundException ex) {
298: throw new ApplicationContextException(
299: "Failed to load custom context class ["
300: + contextClassName + "]", ex);
301: }
302: } else {
303: contextClassName = defaultStrategies
304: .getProperty(WebApplicationContext.class.getName());
305: try {
306: return ClassUtils.forName(contextClassName);
307: } catch (ClassNotFoundException ex) {
308: throw new ApplicationContextException(
309: "Failed to load default context class ["
310: + contextClassName + "]", ex);
311: }
312: }
313: }
314:
315: /**
316: * Customize the {@link ConfigurableWebApplicationContext} created by this
317: * ContextLoader after config locations have been supplied to the context
318: * but before the context is <em>refreshed</em>.
319: * <p>The default implementation is empty but can be overridden in subclasses
320: * to customize the application context.
321: * @param servletContext the current servlet context
322: * @param applicationContext the newly created application context
323: * @see #createWebApplicationContext(ServletContext, ApplicationContext)
324: */
325: protected void customizeContext(ServletContext servletContext,
326: ConfigurableWebApplicationContext applicationContext) {
327: /* no-op */
328: }
329:
330: /**
331: * Template method with default implementation (which may be overridden by a
332: * subclass), to load or obtain an ApplicationContext instance which will be
333: * used as the parent context of the root WebApplicationContext. If the
334: * return value from the method is null, no parent context is set.
335: * <p>The main reason to load a parent context here is to allow multiple root
336: * web application contexts to all be children of a shared EAR context, or
337: * alternately to also share the same parent context that is visible to
338: * EJBs. For pure web applications, there is usually no need to worry about
339: * having a parent context to the root web application context.
340: * <p>The default implementation uses
341: * {@link org.springframework.context.access.ContextSingletonBeanFactoryLocator ContextSingletonBeanFactoryLocator},
342: * configured via {@link #LOCATOR_FACTORY_SELECTOR_PARAM} and
343: * {@link #LOCATOR_FACTORY_KEY_PARAM}, to load a parent context
344: * which will be shared by all other users of ContextsingletonBeanFactoryLocator
345: * which also use the same configuration parameters.
346: * @param servletContext current servlet context
347: * @return the parent application context, or <code>null</code> if none
348: * @throws BeansException if the context couldn't be initialized
349: * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator
350: */
351: protected ApplicationContext loadParentContext(
352: ServletContext servletContext) throws BeansException {
353:
354: ApplicationContext parentContext = null;
355: String locatorFactorySelector = servletContext
356: .getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
357: String parentContextKey = servletContext
358: .getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
359:
360: if (parentContextKey != null) {
361: // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"
362: BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator
363: .getInstance(locatorFactorySelector);
364: if (this .logger.isDebugEnabled()) {
365: this .logger
366: .debug("Getting parent context definition: using parent context key of '"
367: + parentContextKey
368: + "' with BeanFactoryLocator");
369: }
370: this .parentContextRef = locator
371: .useBeanFactory(parentContextKey);
372: parentContext = (ApplicationContext) this .parentContextRef
373: .getFactory();
374: }
375:
376: return parentContext;
377: }
378:
379: /**
380: * Close Spring's web application context for the given servlet context. If
381: * the default {@link #loadParentContext(ServletContext)} implementation,
382: * which uses ContextSingletonBeanFactoryLocator, has loaded any shared
383: * parent context, release one reference to that shared parent context.
384: * <p>If overriding {@link #loadParentContext(ServletContext)}, you may have
385: * to override this method as well.
386: * @param servletContext the ServletContext that the WebApplicationContext runs in
387: */
388: public void closeWebApplicationContext(ServletContext servletContext) {
389: servletContext.log("Closing Spring root WebApplicationContext");
390: try {
391: if (this .context instanceof ConfigurableWebApplicationContext) {
392: ((ConfigurableWebApplicationContext) this.context)
393: .close();
394: }
395: } finally {
396: if (this.parentContextRef != null) {
397: this.parentContextRef.release();
398: }
399: }
400: }
401:
402: }
|