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