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.struts;
018:
019: import javax.servlet.ServletContext;
020: import javax.servlet.ServletException;
021:
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024: import org.apache.struts.action.ActionServlet;
025: import org.apache.struts.action.PlugIn;
026: import org.apache.struts.config.ModuleConfig;
027:
028: import org.springframework.beans.BeanUtils;
029: import org.springframework.beans.BeansException;
030: import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
031: import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
032: import org.springframework.context.ApplicationContextException;
033: import org.springframework.context.ConfigurableApplicationContext;
034: import org.springframework.util.ClassUtils;
035: import org.springframework.util.StringUtils;
036: import org.springframework.web.context.ConfigurableWebApplicationContext;
037: import org.springframework.web.context.WebApplicationContext;
038: import org.springframework.web.context.support.WebApplicationContextUtils;
039: import org.springframework.web.context.support.XmlWebApplicationContext;
040:
041: /**
042: * Struts 1.1+ PlugIn that loads a Spring application context for the Struts
043: * ActionServlet. This context will automatically refer to the root
044: * WebApplicationContext (loaded by ContextLoaderListener/Servlet) as parent.
045: *
046: * <p>The default namespace of the WebApplicationContext is the name of the
047: * Struts ActionServlet, suffixed with "-servlet" (e.g. "action-servlet").
048: * The default location of the XmlWebApplicationContext configuration file
049: * is therefore "/WEB-INF/action-servlet.xml".
050: *
051: * <pre>
052: * <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"/></pre>
053: *
054: * The location of the context configuration files can be customized
055: * through the "contextConfigLocation" setting, analogous to the root
056: * WebApplicationContext and FrameworkServlet contexts.
057: *
058: * <pre>
059: * <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
060: * <set-property property="contextConfigLocation" value="/WEB-INF/action-servlet.xml /WEB-INF/myContext.xml"/>
061: * </plug-in></pre>
062: *
063: * Beans defined in the ContextLoaderPlugIn context can be accessed
064: * from conventional Struts Actions, via fetching the WebApplicationContext
065: * reference from the ServletContext. ActionSupport and DispatchActionSupport
066: * are pre-built convenience classes that provide easy access to the context.
067: *
068: * <p>It is normally preferable to access Spring's root WebApplicationContext
069: * in such scenarios, though: A shared middle tier should be defined there
070: * rather than in a ContextLoaderPlugin context, for access by any web component.
071: * ActionSupport and DispatchActionSupport autodetect the root context too.
072: *
073: * <p>A special usage of this PlugIn is to define Struts Actions themselves
074: * as beans, typically wiring them with middle tier components defined in the
075: * root context. Such Actions will then be delegated to by proxy definitions
076: * in the Struts configuration, using the DelegatingActionProxy class or
077: * the DelegatingRequestProcessor.
078: *
079: * <p>Note that you can use a single ContextLoaderPlugIn for all Struts modules.
080: * That context can in turn be loaded from multiple XML files, for example split
081: * according to Struts modules. Alternatively, define one ContextLoaderPlugIn per
082: * Struts module, specifying appropriate "contextConfigLocation" parameters.
083: *
084: * <p>Note: The idea of delegating to Spring-managed Struts Actions originated in
085: * Don Brown's <a href="http://struts.sourceforge.net/struts-spring">Spring Struts Plugin</a>.
086: * ContextLoaderPlugIn and DelegatingActionProxy constitute a clean-room
087: * implementation of the same idea, essentially superseding the original plugin.
088: * Many thanks to Don Brown and Matt Raible for the original work and for the
089: * agreement to reimplement the idea in Spring proper!
090: *
091: * @author Juergen Hoeller
092: * @since 1.0.1
093: * @see #SERVLET_CONTEXT_PREFIX
094: * @see ActionSupport
095: * @see DispatchActionSupport
096: * @see DelegatingActionProxy
097: * @see DelegatingRequestProcessor
098: * @see DelegatingTilesRequestProcessor
099: * @see org.springframework.web.context.ContextLoaderListener
100: * @see org.springframework.web.context.ContextLoaderServlet
101: * @see org.springframework.web.servlet.FrameworkServlet
102: */
103: public class ContextLoaderPlugIn implements PlugIn {
104:
105: /**
106: * Suffix for WebApplicationContext namespaces. If a Struts ActionServlet is
107: * given the name "action" in a context, the namespace used by this PlugIn will
108: * resolve to "action-servlet".
109: */
110: public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";
111:
112: /**
113: * Default context class for ContextLoaderPlugIn.
114: * @see org.springframework.web.context.support.XmlWebApplicationContext
115: */
116: public static final Class DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
117:
118: /**
119: * Prefix for the ServletContext attribute for the WebApplicationContext.
120: * The completion is the Struts module name.
121: */
122: public static final String SERVLET_CONTEXT_PREFIX = ContextLoaderPlugIn.class
123: .getName()
124: + ".CONTEXT.";
125:
126: protected final Log logger = LogFactory.getLog(getClass());
127:
128: /** Custom WebApplicationContext class */
129: private Class contextClass = DEFAULT_CONTEXT_CLASS;
130:
131: /** Namespace for this servlet */
132: private String namespace;
133:
134: /** Explicit context config location */
135: private String contextConfigLocation;
136:
137: /** The Struts ActionServlet that this PlugIn is registered with */
138: private ActionServlet actionServlet;
139:
140: /** The Struts ModuleConfig that this PlugIn is registered with */
141: private ModuleConfig moduleConfig;
142:
143: /** WebApplicationContext for the ActionServlet */
144: private WebApplicationContext webApplicationContext;
145:
146: /**
147: * Set a custom context class by name. This class must be of type WebApplicationContext,
148: * when using the default ContextLoaderPlugIn implementation, the context class
149: * must also implement ConfigurableWebApplicationContext.
150: * @see #createWebApplicationContext
151: */
152: public void setContextClassName(String contextClassName)
153: throws ClassNotFoundException {
154: this .contextClass = ClassUtils.forName(contextClassName);
155: }
156:
157: /**
158: * Set a custom context class. This class must be of type WebApplicationContext,
159: * when using the default ContextLoaderPlugIn implementation, the context class
160: * must also implement ConfigurableWebApplicationContext.
161: * @see #createWebApplicationContext
162: */
163: public void setContextClass(Class contextClass) {
164: this .contextClass = contextClass;
165: }
166:
167: /**
168: * Return the custom context class.
169: */
170: public Class getContextClass() {
171: return this .contextClass;
172: }
173:
174: /**
175: * Set a custom namespace for the ActionServlet,
176: * to be used for building a default context config location.
177: */
178: public void setNamespace(String namespace) {
179: this .namespace = namespace;
180: }
181:
182: /**
183: * Return the namespace for the ActionServlet, falling back to default scheme if
184: * no custom namespace was set: e.g. "test-servlet" for a servlet named "test".
185: */
186: public String getNamespace() {
187: if (this .namespace != null) {
188: return this .namespace;
189: }
190: if (this .actionServlet != null) {
191: return this .actionServlet.getServletName()
192: + DEFAULT_NAMESPACE_SUFFIX;
193: }
194: return null;
195: }
196:
197: /**
198: * Set the context config location explicitly, instead of relying on the default
199: * location built from the namespace. This location string can consist of
200: * multiple locations separated by any number of commas and spaces.
201: */
202: public void setContextConfigLocation(String contextConfigLocation) {
203: this .contextConfigLocation = contextConfigLocation;
204: }
205:
206: /**
207: * Return the explicit context config location, if any.
208: */
209: public String getContextConfigLocation() {
210: return this .contextConfigLocation;
211: }
212:
213: /**
214: * Create the ActionServlet's WebApplicationContext.
215: */
216: public final void init(ActionServlet actionServlet,
217: ModuleConfig moduleConfig) throws ServletException {
218: long startTime = System.currentTimeMillis();
219: if (logger.isInfoEnabled()) {
220: logger
221: .info("ContextLoaderPlugIn for Struts ActionServlet '"
222: + actionServlet.getServletName()
223: + ", module '"
224: + moduleConfig.getPrefix()
225: + "': initialization started");
226: }
227:
228: this .actionServlet = actionServlet;
229: this .moduleConfig = moduleConfig;
230: try {
231: this .webApplicationContext = initWebApplicationContext();
232: onInit();
233: } catch (RuntimeException ex) {
234: logger.error("Context initialization failed", ex);
235: throw ex;
236: }
237:
238: if (logger.isInfoEnabled()) {
239: long elapsedTime = System.currentTimeMillis() - startTime;
240: logger
241: .info("ContextLoaderPlugIn for Struts ActionServlet '"
242: + actionServlet.getServletName()
243: + "', module '"
244: + moduleConfig.getPrefix()
245: + "': initialization completed in "
246: + elapsedTime + " ms");
247: }
248: }
249:
250: /**
251: * Return the Struts ActionServlet that this PlugIn is associated with.
252: */
253: public final ActionServlet getActionServlet() {
254: return actionServlet;
255: }
256:
257: /**
258: * Return the name of the ActionServlet that this PlugIn is associated with.
259: */
260: public final String getServletName() {
261: return this .actionServlet.getServletName();
262: }
263:
264: /**
265: * Return the ServletContext that this PlugIn is associated with.
266: */
267: public final ServletContext getServletContext() {
268: return this .actionServlet.getServletContext();
269: }
270:
271: /**
272: * Return the Struts ModuleConfig that this PlugIn is associated with.
273: */
274: public final ModuleConfig getModuleConfig() {
275: return this .moduleConfig;
276: }
277:
278: /**
279: * Return the prefix of the ModuleConfig that this PlugIn is associated with.
280: * @see org.apache.struts.config.ModuleConfig#getPrefix
281: */
282: public final String getModulePrefix() {
283: return this .moduleConfig.getPrefix();
284: }
285:
286: /**
287: * Initialize and publish the WebApplicationContext for the ActionServlet.
288: * <p>Delegates to {@link #createWebApplicationContext} for actual creation.
289: * <p>Can be overridden in subclasses. Call <code>getActionServlet()</code>
290: * and/or <code>getModuleConfig()</code> to access the Struts configuration
291: * that this PlugIn is associated with.
292: * @throws org.springframework.beans.BeansException if the context couldn't be initialized
293: * @throws IllegalStateException if there is already a context for the Struts ActionServlet
294: * @see #getActionServlet()
295: * @see #getServletName()
296: * @see #getServletContext()
297: * @see #getModuleConfig()
298: * @see #getModulePrefix()
299: */
300: protected WebApplicationContext initWebApplicationContext()
301: throws BeansException, IllegalStateException {
302: getServletContext().log(
303: "Initializing WebApplicationContext for Struts ActionServlet '"
304: + getServletName() + "', module '"
305: + getModulePrefix() + "'");
306: WebApplicationContext parent = WebApplicationContextUtils
307: .getWebApplicationContext(getServletContext());
308:
309: WebApplicationContext wac = createWebApplicationContext(parent);
310: if (logger.isInfoEnabled()) {
311: logger.info("Using context class '"
312: + wac.getClass().getName() + "' for servlet '"
313: + getServletName() + "'");
314: }
315:
316: // Publish the context as a servlet context attribute.
317: String attrName = getServletContextAttributeName();
318: getServletContext().setAttribute(attrName, wac);
319: if (logger.isDebugEnabled()) {
320: logger
321: .debug("Published WebApplicationContext of Struts ActionServlet '"
322: + getServletName()
323: + "', module '"
324: + getModulePrefix()
325: + "' as ServletContext attribute with name ["
326: + attrName + "]");
327: }
328:
329: return wac;
330: }
331:
332: /**
333: * Instantiate the WebApplicationContext for the ActionServlet, either a default
334: * XmlWebApplicationContext or a custom context class if set.
335: * <p>This implementation expects custom contexts to implement ConfigurableWebApplicationContext.
336: * Can be overridden in subclasses.
337: * @throws org.springframework.beans.BeansException if the context couldn't be initialized
338: * @see #setContextClass
339: * @see org.springframework.web.context.support.XmlWebApplicationContext
340: */
341: protected WebApplicationContext createWebApplicationContext(
342: WebApplicationContext parent) throws BeansException {
343:
344: if (logger.isDebugEnabled()) {
345: logger
346: .debug("ContextLoaderPlugIn for Struts ActionServlet '"
347: + getServletName()
348: + "', module '"
349: + getModulePrefix()
350: + "' will try to create custom WebApplicationContext "
351: + "context of class '"
352: + getContextClass().getName()
353: + "', using parent context ["
354: + parent
355: + "]");
356: }
357: if (!ConfigurableWebApplicationContext.class
358: .isAssignableFrom(getContextClass())) {
359: throw new ApplicationContextException(
360: "Fatal initialization error in ContextLoaderPlugIn for Struts ActionServlet '"
361: + getServletName()
362: + "', module '"
363: + getModulePrefix()
364: + "': custom WebApplicationContext class ["
365: + getContextClass().getName()
366: + "] is not of type ConfigurableWebApplicationContext");
367: }
368:
369: ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils
370: .instantiateClass(getContextClass());
371: wac.setParent(parent);
372: wac.setServletContext(getServletContext());
373: wac.setNamespace(getNamespace());
374: if (getContextConfigLocation() != null) {
375: wac
376: .setConfigLocations(StringUtils
377: .tokenizeToStringArray(
378: getContextConfigLocation(),
379: ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
380: }
381: wac.addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() {
382: public void postProcessBeanFactory(
383: ConfigurableListableBeanFactory beanFactory) {
384: beanFactory
385: .addBeanPostProcessor(new ActionServletAwareProcessor(
386: getActionServlet()));
387: beanFactory.ignoreDependencyType(ActionServlet.class);
388: }
389: });
390:
391: wac.refresh();
392: return wac;
393: }
394:
395: /**
396: * Return the ServletContext attribute name for this PlugIn's WebApplicationContext.
397: * <p>The default implementation returns SERVLET_CONTEXT_PREFIX + module prefix.
398: * @see #SERVLET_CONTEXT_PREFIX
399: * @see #getModulePrefix()
400: */
401: public String getServletContextAttributeName() {
402: return SERVLET_CONTEXT_PREFIX + getModulePrefix();
403: }
404:
405: /**
406: * Return this PlugIn's WebApplicationContext.
407: */
408: public final WebApplicationContext getWebApplicationContext() {
409: return webApplicationContext;
410: }
411:
412: /**
413: * Callback for custom initialization after the context has been set up.
414: * @throws ServletException if initialization failed
415: */
416: protected void onInit() throws ServletException {
417: }
418:
419: /**
420: * Close the WebApplicationContext of the ActionServlet.
421: * @see org.springframework.context.ConfigurableApplicationContext#close()
422: */
423: public void destroy() {
424: getServletContext().log(
425: "Closing WebApplicationContext of Struts ActionServlet '"
426: + getServletName() + "', module '"
427: + getModulePrefix() + "'");
428: if (getWebApplicationContext() instanceof ConfigurableApplicationContext) {
429: ((ConfigurableApplicationContext) getWebApplicationContext())
430: .close();
431: }
432: }
433:
434: }
|