View Javadoc

1   /*
2    * $Id: ActionServlet.java 483039 2006-12-06 11:34:28Z niallp $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  package org.apache.struts.action;
22  
23  import org.apache.commons.beanutils.BeanUtils;
24  import org.apache.commons.beanutils.ConvertUtils;
25  import org.apache.commons.beanutils.PropertyUtils;
26  import org.apache.commons.beanutils.converters.BigDecimalConverter;
27  import org.apache.commons.beanutils.converters.BigIntegerConverter;
28  import org.apache.commons.beanutils.converters.BooleanConverter;
29  import org.apache.commons.beanutils.converters.ByteConverter;
30  import org.apache.commons.beanutils.converters.CharacterConverter;
31  import org.apache.commons.beanutils.converters.DoubleConverter;
32  import org.apache.commons.beanutils.converters.FloatConverter;
33  import org.apache.commons.beanutils.converters.IntegerConverter;
34  import org.apache.commons.beanutils.converters.LongConverter;
35  import org.apache.commons.beanutils.converters.ShortConverter;
36  import org.apache.commons.chain.CatalogFactory;
37  import org.apache.commons.chain.config.ConfigParser;
38  import org.apache.commons.digester.Digester;
39  import org.apache.commons.digester.RuleSet;
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  import org.apache.struts.Globals;
43  import org.apache.struts.config.ActionConfig;
44  import org.apache.struts.config.ConfigRuleSet;
45  import org.apache.struts.config.ExceptionConfig;
46  import org.apache.struts.config.FormBeanConfig;
47  import org.apache.struts.config.FormPropertyConfig;
48  import org.apache.struts.config.ForwardConfig;
49  import org.apache.struts.config.MessageResourcesConfig;
50  import org.apache.struts.config.ModuleConfig;
51  import org.apache.struts.config.ModuleConfigFactory;
52  import org.apache.struts.config.PlugInConfig;
53  import org.apache.struts.util.MessageResources;
54  import org.apache.struts.util.MessageResourcesFactory;
55  import org.apache.struts.util.ModuleUtils;
56  import org.apache.struts.util.RequestUtils;
57  import org.xml.sax.InputSource;
58  import org.xml.sax.SAXException;
59  
60  import javax.servlet.ServletContext;
61  import javax.servlet.ServletException;
62  import javax.servlet.UnavailableException;
63  import javax.servlet.http.HttpServlet;
64  import javax.servlet.http.HttpServletRequest;
65  import javax.servlet.http.HttpServletResponse;
66  
67  import java.io.IOException;
68  import java.io.InputStream;
69  
70  import java.math.BigDecimal;
71  import java.math.BigInteger;
72  
73  import java.net.MalformedURLException;
74  import java.net.URL;
75  import java.net.URLConnection;
76  
77  import java.util.ArrayList;
78  import java.util.Enumeration;
79  import java.util.Iterator;
80  import java.util.List;
81  import java.util.MissingResourceException;
82  
83  /**
84   * <p><strong>ActionServlet</strong> provides the "controller" in the
85   * Model-View-Controller (MVC) design pattern for web applications that is
86   * commonly known as "Model 2".  This nomenclature originated with a
87   * description in the JavaServerPages Specification, version 0.92, and has
88   * persisted ever since (in the absence of a better name).</p>
89   *
90   * <p>Generally, a "Model 2" application is architected as follows:</p>
91   *
92   * <ul>
93   *
94   * <li>The user interface will generally be created with server pages, which
95   * will not themselves contain any business logic. These pages represent the
96   * "view" component of an MVC architecture.</li>
97   *
98   * <li>Forms and hyperlinks in the user interface that require business logic
99   * to be executed will be submitted to a request URI that is mapped to this
100  * servlet.</li>
101  *
102  * <li>There can be <b>one</b> instance of this servlet class, which receives
103  * and processes all requests that change the state of a user's interaction
104  * with the application. The servlet delegates the handling of a request to a
105  * {@link RequestProcessor} object. This component represents the "controller"
106  * component of an MVC architecture. </li>
107  *
108  * <li>The <code>RequestProcessor</code> selects and invokes an {@link Action}
109  * class to perform the requested business logic, or delegates the response to
110  * another resource.</li>
111  *
112  * <li>The <code>Action</code> classes can manipulate the state of the
113  * application's interaction with the user, typically by creating or modifying
114  * JavaBeans that are stored as request or session attributes (depending on
115  * how long they need to be available). Such JavaBeans represent the "model"
116  * component of an MVC architecture.</li>
117  *
118  * <li>Instead of producing the next page of the user interface directly,
119  * <code>Action</code> classes generally return an {@link ActionForward} to
120  * indicate which resource should handle the response. If the
121  * <code>Action</code> does not return null, the <code>RequestProcessor</code>
122  * forwards or redirects to the specified resource (by utilizing
123  * <code>RequestDispatcher.forward</code> or <code>Response.sendRedirect</code>)
124  * so as to produce the next page of the user interface.</li>
125  *
126  * </ul>
127  *
128  * <p>The standard version of <code>RequestsProcessor</code> implements the
129  * following logic for each incoming HTTP request. You can override some or
130  * all of this functionality by subclassing this object and implementing your
131  * own version of the processing.</p>
132  *
133  * <ul>
134  *
135  * <li>Identify, from the incoming request URI, the substring that will be
136  * used to select an action procedure.</li>
137  *
138  * <li>Use this substring to map to the Java class name of the corresponding
139  * action class (an implementation of the <code>Action</code> interface).
140  * </li>
141  *
142  * <li>If this is the first request for a particular <code>Action</code>
143  * class, instantiate an instance of that class and cache it for future
144  * use.</li>
145  *
146  * <li>Optionally populate the properties of an <code>ActionForm</code> bean
147  * associated with this mapping.</li>
148  *
149  * <li>Call the <code>execute</code> method of this <code>Action</code> class,
150  * passing on a reference to the mapping that was used, the relevant form-bean
151  * (if any), and the request and the response that were passed to the
152  * controller by the servlet container (thereby providing access to any
153  * specialized properties of the mapping itself as well as to the
154  * ServletContext). </li>
155  *
156  * </ul>
157  *
158  * <p>The standard version of <code>ActionServlet</code> is configured based
159  * on the following servlet initialization parameters, which you will specify
160  * in the web application deployment descriptor (<code>/WEB-INF/web.xml</code>)
161  * for your application.  Subclasses that specialize this servlet are free to
162  * define additional initialization parameters. </p>
163  *
164  * <ul>
165  *
166  * <li><strong>config</strong> - Comma-separated list of context-relative
167  * path(s) to the XML resource(s) containing the configuration information for
168  * the default module.  (Multiple files support since Struts 1.1)
169  * [/WEB-INF/struts-config.xml].</li>
170  *
171  * <li><strong>config/${module}</strong> - Comma-separated list of
172  * Context-relative path(s) to the XML resource(s) containing the
173  * configuration information for the module that will use the specified prefix
174  * (/${module}). This can be repeated as many times as required for multiple
175  * modules. (Since Struts 1.1)</li>
176  *
177  * <li><strong>configFactory</strong> - The Java class name of the
178  * <code>ModuleConfigFactory</code> used to create the implementation of the
179  * ModuleConfig interface. </li>
180  *
181  * <li><strong>convertNull</strong> - Force simulation of the Struts 1.0
182  * behavior when populating forms. If set to true, the numeric Java wrapper
183  * class types (like <code>java.lang.Integer</code>) will default to null
184  * (rather than 0). (Since Struts 1.1) [false] </li>
185  *
186  * <li><strong>rulesets </strong> - Comma-delimited list of fully qualified
187  * classnames of additional <code>org.apache.commons.digester.RuleSet</code>
188  * instances that should be added to the <code>Digester</code> that will be
189  * processing <code>struts-config.xml</code> files.  By default, only the
190  * <code>RuleSet</code> for the standard configuration elements is loaded.
191  * (Since Struts 1.1)</li>
192  *
193  * <li><strong>validating</strong> - Should we use a validating XML parser to
194  * process the configuration file (strongly recommended)? [true]</li>
195  *
196  * <li><strong>chainConfig</strong> - Comma-separated list of either
197  * context-relative or classloader path(s) to load commons-chain catalog
198  * definitions from.  If none specified, the default Struts catalog that is
199  * provided with Struts will be used.</li>
200  *
201  * </ul>
202  *
203  * @version $Rev: 483039 $ $Date: 2005-10-14 19:54:16 -0400 (Fri, 14 Oct 2005)
204  *          $
205  */
206 public class ActionServlet extends HttpServlet {
207     /**
208      * <p>Commons Logging instance.</p>
209      *
210      * @since Struts 1.1
211      */
212     protected static Log log = LogFactory.getLog(ActionServlet.class);
213 
214     // ----------------------------------------------------- Instance Variables
215 
216     /**
217      * <p>Comma-separated list of context-relative path(s) to our
218      * configuration resource(s) for the default module.</p>
219      */
220     protected String config = "/WEB-INF/struts-config.xml";
221 
222     /**
223      * <p>Comma-separated list of context or classloader-relative path(s) that
224      * contain the configuration for the default commons-chain
225      * catalog(s).</p>
226      */
227     protected String chainConfig = "org/apache/struts/chain/chain-config.xml";
228 
229     /**
230      * <p>The Digester used to produce ModuleConfig objects from a Struts
231      * configuration file.</p>
232      *
233      * @since Struts 1.1
234      */
235     protected Digester configDigester = null;
236 
237     /**
238      * <p>The flag to request backwards-compatible conversions for form bean
239      * properties of the Java wrapper class types.</p>
240      *
241      * @since Struts 1.1
242      */
243     protected boolean convertNull = false;
244 
245     /**
246      * <p>The resources object for our internal resources.</p>
247      */
248     protected MessageResources internal = null;
249 
250     /**
251      * <p>The Java base name of our internal resources.</p>
252      *
253      * @since Struts 1.1
254      */
255     protected String internalName = "org.apache.struts.action.ActionResources";
256 
257     /**
258      * <p>The set of public identifiers, and corresponding resource names, for
259      * the versions of the configuration file DTDs that we know about.  There
260      * <strong>MUST</strong> be an even number of Strings in this list!</p>
261      */
262     protected String[] registrations =
263         {
264             "-//Apache Software Foundation//DTD Struts Configuration 1.0//EN",
265             "/org/apache/struts/resources/struts-config_1_0.dtd",
266             "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN",
267             "/org/apache/struts/resources/struts-config_1_1.dtd",
268             "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN",
269             "/org/apache/struts/resources/struts-config_1_2.dtd",
270             "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN",
271             "/org/apache/struts/resources/struts-config_1_3.dtd",
272             "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN",
273             "/org/apache/struts/resources/web-app_2_3.dtd"
274         };
275 
276     /**
277      * <p>The URL pattern to which we are mapped in our web application
278      * deployment descriptor.</p>
279      */
280     protected String servletMapping = null; // :FIXME: - multiples?
281 
282     /**
283      * <p>The servlet name under which we are registered in our web
284      * application deployment descriptor.</p>
285      */
286     protected String servletName = null;
287 
288     // ---------------------------------------------------- HttpServlet Methods
289 
290     /**
291      * <p>Gracefully shut down this controller servlet, releasing any
292      * resources that were allocated at initialization.</p>
293      */
294     public void destroy() {
295         if (log.isDebugEnabled()) {
296             log.debug(internal.getMessage("finalizing"));
297         }
298 
299         destroyModules();
300         destroyInternal();
301         getServletContext().removeAttribute(Globals.ACTION_SERVLET_KEY);
302 
303         // Release our LogFactory and Log instances (if any)
304         ClassLoader classLoader =
305             Thread.currentThread().getContextClassLoader();
306 
307         if (classLoader == null) {
308             classLoader = ActionServlet.class.getClassLoader();
309         }
310 
311         try {
312             LogFactory.release(classLoader);
313         } catch (Throwable t) {
314             ; // Servlet container doesn't have the latest version
315 
316             // of commons-logging-api.jar installed
317             // :FIXME: Why is this dependent on the container's version of
318             // commons-logging? Shouldn't this depend on the version packaged
319             // with Struts?
320 
321             /*
322               Reason: LogFactory.release(classLoader); was added as
323               an attempt to investigate the OutOfMemory error reported on
324               Bugzilla #14042. It was committed for version 1.136 by craigmcc
325             */
326         }
327 
328         CatalogFactory.clear();
329         PropertyUtils.clearDescriptors();
330     }
331 
332     /**
333      * <p>Initialize this servlet.  Most of the processing has been factored
334      * into support methods so that you can override particular functionality
335      * at a fairly granular level.</p>
336      *
337      * @throws ServletException if we cannot configure ourselves correctly
338      */
339     public void init() throws ServletException {
340         final String configPrefix = "config/";
341         final int configPrefixLength = configPrefix.length() - 1;
342 
343         // Wraps the entire initialization in a try/catch to better handle
344         // unexpected exceptions and errors to provide better feedback
345         // to the developer
346         try {
347             initInternal();
348             initOther();
349             initServlet();
350             initChain();
351 
352             getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this);
353             initModuleConfigFactory();
354 
355             // Initialize modules as needed
356             ModuleConfig moduleConfig = initModuleConfig("", config);
357 
358             initModuleMessageResources(moduleConfig);
359             initModulePlugIns(moduleConfig);
360             initModuleFormBeans(moduleConfig);
361             initModuleForwards(moduleConfig);
362             initModuleExceptionConfigs(moduleConfig);
363             initModuleActions(moduleConfig);
364             moduleConfig.freeze();
365 
366             Enumeration names = getServletConfig().getInitParameterNames();
367 
368             while (names.hasMoreElements()) {
369                 String name = (String) names.nextElement();
370 
371                 if (!name.startsWith(configPrefix)) {
372                     continue;
373                 }
374 
375                 String prefix = name.substring(configPrefixLength);
376 
377                 moduleConfig =
378                     initModuleConfig(prefix,
379                         getServletConfig().getInitParameter(name));
380                 initModuleMessageResources(moduleConfig);
381                 initModulePlugIns(moduleConfig);
382                 initModuleFormBeans(moduleConfig);
383                 initModuleForwards(moduleConfig);
384                 initModuleExceptionConfigs(moduleConfig);
385                 initModuleActions(moduleConfig);
386                 moduleConfig.freeze();
387             }
388 
389             this.initModulePrefixes(this.getServletContext());
390 
391             this.destroyConfigDigester();
392         } catch (UnavailableException ex) {
393             throw ex;
394         } catch (Throwable t) {
395             // The follow error message is not retrieved from internal message
396             // resources as they may not have been able to have been
397             // initialized
398             log.error("Unable to initialize Struts ActionServlet due to an "
399                 + "unexpected exception or error thrown, so marking the "
400                 + "servlet as unavailable.  Most likely, this is due to an "
401                 + "incorrect or missing library dependency.", t);
402             throw new UnavailableException(t.getMessage());
403         }
404     }
405 
406     /**
407      * <p>Saves a String[] of module prefixes in the ServletContext under
408      * Globals.MODULE_PREFIXES_KEY.  <strong>NOTE</strong> - the "" prefix for
409      * the default module is not included in this list.</p>
410      *
411      * @param context The servlet context.
412      * @since Struts 1.2
413      */
414     protected void initModulePrefixes(ServletContext context) {
415         ArrayList prefixList = new ArrayList();
416 
417         Enumeration names = context.getAttributeNames();
418 
419         while (names.hasMoreElements()) {
420             String name = (String) names.nextElement();
421 
422             if (!name.startsWith(Globals.MODULE_KEY)) {
423                 continue;
424             }
425 
426             String prefix = name.substring(Globals.MODULE_KEY.length());
427 
428             if (prefix.length() > 0) {
429                 prefixList.add(prefix);
430             }
431         }
432 
433         String[] prefixes =
434             (String[]) prefixList.toArray(new String[prefixList.size()]);
435 
436         context.setAttribute(Globals.MODULE_PREFIXES_KEY, prefixes);
437     }
438 
439     /**
440      * <p>Process an HTTP "GET" request.</p>
441      *
442      * @param request  The servlet request we are processing
443      * @param response The servlet response we are creating
444      * @throws IOException      if an input/output error occurs
445      * @throws ServletException if a servlet exception occurs
446      */
447     public void doGet(HttpServletRequest request, HttpServletResponse response)
448         throws IOException, ServletException {
449         process(request, response);
450     }
451 
452     /**
453      * <p>Process an HTTP "POST" request.</p>
454      *
455      * @param request  The servlet request we are processing
456      * @param response The servlet response we are creating
457      * @throws IOException      if an input/output error occurs
458      * @throws ServletException if a servlet exception occurs
459      */
460     public void doPost(HttpServletRequest request, HttpServletResponse response)
461         throws IOException, ServletException {
462         process(request, response);
463     }
464 
465     // --------------------------------------------------------- Public Methods
466 
467     /**
468      * <p>Remember a servlet mapping from our web application deployment
469      * descriptor, if it is for this servlet.</p>
470      *
471      * @param servletName The name of the servlet being mapped
472      * @param urlPattern  The URL pattern to which this servlet is mapped
473      */
474     public void addServletMapping(String servletName, String urlPattern) {
475         if (servletName == null) {
476             return;
477         }
478 
479         if (servletName.equals(this.servletName)) {
480             if (log.isDebugEnabled()) {
481                 log.debug("Process servletName=" + servletName
482                     + ", urlPattern=" + urlPattern);
483             }
484 
485             this.servletMapping = urlPattern;
486         }
487     }
488 
489     /**
490      * <p>Return the <code>MessageResources</code> instance containing our
491      * internal message strings.</p>
492      *
493      * @return the <code>MessageResources</code> instance containing our
494      *         internal message strings.
495      * @since Struts 1.1
496      */
497     public MessageResources getInternal() {
498         return (this.internal);
499     }
500 
501     // ------------------------------------------------------ Protected Methods
502 
503     /**
504      * <p>Gracefully terminate use of any modules associated with this
505      * application (if any).</p>
506      *
507      * @since Struts 1.1
508      */
509     protected void destroyModules() {
510         ArrayList values = new ArrayList();
511         Enumeration names = getServletContext().getAttributeNames();
512 
513         while (names.hasMoreElements()) {
514             values.add(names.nextElement());
515         }
516 
517         Iterator keys = values.iterator();
518 
519         while (keys.hasNext()) {
520             String name = (String) keys.next();
521             Object value = getServletContext().getAttribute(name);
522 
523             if (!(value instanceof ModuleConfig)) {
524                 continue;
525             }
526 
527             ModuleConfig config = (ModuleConfig) value;
528 
529             if (this.getProcessorForModule(config) != null) {
530                 this.getProcessorForModule(config).destroy();
531             }
532 
533             getServletContext().removeAttribute(name);
534 
535             PlugIn[] plugIns =
536                 (PlugIn[]) getServletContext().getAttribute(Globals.PLUG_INS_KEY
537                     + config.getPrefix());
538 
539             if (plugIns != null) {
540                 for (int i = 0; i < plugIns.length; i++) {
541                     int j = plugIns.length - (i + 1);
542 
543                     plugIns[j].destroy();
544                 }
545 
546                 getServletContext().removeAttribute(Globals.PLUG_INS_KEY
547                     + config.getPrefix());
548             }
549         }
550     }
551 
552     /**
553      * <p>Gracefully release any configDigester instance that we have created.
554      * </p>
555      *
556      * @since Struts 1.1
557      */
558     protected void destroyConfigDigester() {
559         configDigester = null;
560     }
561 
562     /**
563      * <p>Gracefully terminate use of the internal MessageResources.</p>
564      */
565     protected void destroyInternal() {
566         internal = null;
567     }
568 
569     /**
570      * <p>Return the module configuration object for the currently selected
571      * module.</p>
572      *
573      * @param request The servlet request we are processing
574      * @return The module configuration object for the currently selected
575      *         module.
576      * @since Struts 1.1
577      */
578     protected ModuleConfig getModuleConfig(HttpServletRequest request) {
579         ModuleConfig config =
580             (ModuleConfig) request.getAttribute(Globals.MODULE_KEY);
581 
582         if (config == null) {
583             config =
584                 (ModuleConfig) getServletContext().getAttribute(Globals.MODULE_KEY);
585         }
586 
587         return (config);
588     }
589 
590     /**
591      * <p>Look up and return the {@link RequestProcessor} responsible for the
592      * specified module, creating a new one if necessary.</p>
593      *
594      * @param config The module configuration for which to acquire and return
595      *               a RequestProcessor.
596      * @return The {@link RequestProcessor} responsible for the specified
597      *         module,
598      * @throws ServletException If we cannot instantiate a RequestProcessor
599      *                          instance a {@link UnavailableException} is
600      *                          thrown, meaning your application is not loaded
601      *                          and will not be available.
602      * @since Struts 1.1
603      */
604     protected synchronized RequestProcessor getRequestProcessor(
605         ModuleConfig config) throws ServletException {
606         RequestProcessor processor = this.getProcessorForModule(config);
607 
608         if (processor == null) {
609             try {
610                 processor =
611                     (RequestProcessor) RequestUtils.applicationInstance(config.getControllerConfig()
612                                                                               .getProcessorClass());
613             } catch (Exception e) {
614                 throw new UnavailableException(
615                     "Cannot initialize RequestProcessor of class "
616                     + config.getControllerConfig().getProcessorClass() + ": "
617                     + e);
618             }
619 
620             processor.init(this, config);
621 
622             String key = Globals.REQUEST_PROCESSOR_KEY + config.getPrefix();
623 
624             getServletContext().setAttribute(key, processor);
625         }
626 
627         return (processor);
628     }
629 
630     /**
631      * <p>Returns the RequestProcessor for the given module or null if one
632      * does not exist.  This method will not create a RequestProcessor.</p>
633      *
634      * @param config The ModuleConfig.
635      * @return The <code>RequestProcessor</code> for the given module, or
636      *         <code>null</code> if one does not exist.
637      */
638     private RequestProcessor getProcessorForModule(ModuleConfig config) {
639         String key = Globals.REQUEST_PROCESSOR_KEY + config.getPrefix();
640 
641         return (RequestProcessor) getServletContext().getAttribute(key);
642     }
643 
644     /**
645      * <p>Initialize the factory used to create the module configuration.</p>
646      *
647      * @since Struts 1.2
648      */
649     protected void initModuleConfigFactory() {
650         String configFactory =
651             getServletConfig().getInitParameter("configFactory");
652 
653         if (configFactory != null) {
654             ModuleConfigFactory.setFactoryClass(configFactory);
655         }
656     }
657 
658     /**
659      * <p>Initialize the module configuration information for the specified
660      * module.</p>
661      *
662      * @param prefix Module prefix for this module
663      * @param paths  Comma-separated list of context-relative resource path(s)
664      *               for this modules's configuration resource(s)
665      * @return The new module configuration instance.
666      * @throws ServletException if initialization cannot be performed
667      * @since Struts 1.1
668      */
669     protected ModuleConfig initModuleConfig(String prefix, String paths)
670         throws ServletException {
671         if (log.isDebugEnabled()) {
672             log.debug("Initializing module path '" + prefix
673                 + "' configuration from '" + paths + "'");
674         }
675 
676         // Parse the configuration for this module
677         ModuleConfigFactory factoryObject = ModuleConfigFactory.createFactory();
678         ModuleConfig config = factoryObject.createModuleConfig(prefix);
679 
680         // Configure the Digester instance we will use
681         Digester digester = initConfigDigester();
682 
683         List urls = splitAndResolvePaths(paths);
684         URL url;
685 
686         for (Iterator i = urls.iterator(); i.hasNext();) {
687             url = (URL) i.next();
688             digester.push(config);
689             this.parseModuleConfigFile(digester, url);
690         }
691 
692         getServletContext().setAttribute(Globals.MODULE_KEY
693             + config.getPrefix(), config);
694 
695         return config;
696     }
697 
698     /**
699      * <p>Parses one module config file.</p>
700      *
701      * @param digester Digester instance that does the parsing
702      * @param path     The path to the config file to parse.
703      * @throws UnavailableException if file cannot be read or parsed
704      * @since Struts 1.2
705      * @deprecated use parseModuleConfigFile(Digester digester, URL url)
706      *             instead
707      */
708     protected void parseModuleConfigFile(Digester digester, String path)
709         throws UnavailableException {
710         try {
711             List paths = splitAndResolvePaths(path);
712 
713             if (paths.size() > 0) {
714                 // Get first path as was the old behavior
715                 URL url = (URL) paths.get(0);
716 
717                 parseModuleConfigFile(digester, url);
718             } else {
719                 throw new UnavailableException("Cannot locate path " + path);
720             }
721         } catch (UnavailableException ex) {
722             throw ex;
723         } catch (ServletException ex) {
724             handleConfigException(path, ex);
725         }
726     }
727 
728     /**
729      * <p>Parses one module config file.</p>
730      *
731      * @param digester Digester instance that does the parsing
732      * @param url      The url to the config file to parse.
733      * @throws UnavailableException if file cannot be read or parsed
734      * @since Struts 1.3
735      */
736     protected void parseModuleConfigFile(Digester digester, URL url)
737         throws UnavailableException {
738 
739         try {
740             digester.parse(url);
741         } catch (IOException e) {
742             handleConfigException(url.toString(), e);
743         } catch (SAXException e) {
744             handleConfigException(url.toString(), e);
745         }
746     }
747 
748     /**
749      * <p>Simplifies exception handling in the parseModuleConfigFile
750      * method.<p>
751      *
752      * @param path The path to which the exception relates.
753      * @param e    The exception to be wrapped and thrown.
754      * @throws UnavailableException as a wrapper around Exception
755      */
756     private void handleConfigException(String path, Exception e)
757         throws UnavailableException {
758         String msg = internal.getMessage("configParse", path);
759 
760         log.error(msg, e);
761         throw new UnavailableException(msg);
762     }
763 
764     /**
765      * <p>Handle errors related to creating an instance of the specified
766      * class.</p>
767      *
768      * @param className The className that could not be instantiated.
769      * @param e         The exception that was caught.
770      * @throws ServletException to communicate the error.
771      */
772     private void handleCreationException(String className, Exception e)
773         throws ServletException {
774         String errorMessage =
775             internal.getMessage("configExtends.creation", className);
776 
777         log.error(errorMessage, e);
778         throw new UnavailableException(errorMessage);
779     }
780 
781     /**
782      * <p>General handling for exceptions caught while inheriting config
783      * information.</p>
784      *
785      * @param configType The type of configuration object of configName.
786      * @param configName The name of the config that could not be extended.
787      * @param e          The exception that was caught.
788      * @throws ServletException to communicate the error.
789      */
790     private void handleGeneralExtensionException(String configType,
791         String configName, Exception e)
792         throws ServletException {
793         String errorMessage =
794             internal.getMessage("configExtends", configType, configName);
795 
796         log.error(errorMessage, e);
797         throw new UnavailableException(errorMessage);
798     }
799 
800     /**
801      * <p>Handle errors caused by required fields that were not
802      * specified.</p>
803      *
804      * @param field      The name of the required field that was not found.
805      * @param configType The type of configuration object of configName.
806      * @param configName The name of the config that's missing the required
807      *                   value.
808      * @throws ServletException to communicate the error.
809      */
810     private void handleValueRequiredException(String field, String configType,
811         String configName) throws ServletException {
812         String errorMessage =
813             internal.getMessage("configFieldRequired", field, configType,
814                 configName);
815 
816         log.error(errorMessage);
817         throw new UnavailableException(errorMessage);
818     }
819 
820     /**
821      * <p>Initialize the plug ins for the specified module.</p>
822      *
823      * @param config ModuleConfig information for this module
824      * @throws ServletException if initialization cannot be performed
825      * @since Struts 1.1
826      */
827     protected void initModulePlugIns(ModuleConfig config)
828         throws ServletException {
829         if (log.isDebugEnabled()) {
830             log.debug("Initializing module path '" + config.getPrefix()
831                 + "' plug ins");
832         }
833 
834         PlugInConfig[] plugInConfigs = config.findPlugInConfigs();
835         PlugIn[] plugIns = new PlugIn[plugInConfigs.length];
836 
837         getServletContext().setAttribute(Globals.PLUG_INS_KEY
838             + config.getPrefix(), plugIns);
839 
840         for (int i = 0; i < plugIns.length; i++) {
841             try {
842                 plugIns[i] =
843                     (PlugIn) RequestUtils.applicationInstance(plugInConfigs[i]
844                         .getClassName());
845                 BeanUtils.populate(plugIns[i], plugInConfigs[i].getProperties());
846 
847                 // Pass the current plugIn config object to the PlugIn.
848                 // The property is set only if the plugin declares it.
849                 // This plugin config object is needed by Tiles
850                 try {
851                     PropertyUtils.setProperty(plugIns[i],
852                         "currentPlugInConfigObject", plugInConfigs[i]);
853                 } catch (Exception e) {
854                     ;
855 
856                     // FIXME Whenever we fail silently, we must document a valid
857                     // reason for doing so.  Why should we fail silently if a
858                     // property can't be set on the plugin?
859 
860                     /**
861                      * Between version 1.138-1.140 cedric made these changes.
862                      * The exceptions are caught to deal with containers
863                      * applying strict security. This was in response to bug
864                      * #15736
865                      *
866                      * Recommend that we make the currentPlugInConfigObject part
867                      * of the PlugIn Interface if we can, Rob
868                      */
869                 }
870 
871                 plugIns[i].init(this, config);
872             } catch (ServletException e) {
873                 throw e;
874             } catch (Exception e) {
875                 String errMsg =
876                     internal.getMessage("plugIn.init",
877                         plugInConfigs[i].getClassName());
878 
879                 log(errMsg, e);
880                 throw new UnavailableException(errMsg);
881             }
882         }
883     }
884 
885     /**
886      * <p>Initialize the form beans for the specified module.</p>
887      *
888      * @param config ModuleConfig information for this module
889      * @throws ServletException if initialization cannot be performed
890      * @since Struts 1.3
891      */
892     protected void initModuleFormBeans(ModuleConfig config)
893         throws ServletException {
894         if (log.isDebugEnabled()) {
895             log.debug("Initializing module path '" + config.getPrefix()
896                 + "' form beans");
897         }
898 
899         // Process form bean extensions.
900         FormBeanConfig[] formBeans = config.findFormBeanConfigs();
901 
902         for (int i = 0; i < formBeans.length; i++) {
903             FormBeanConfig beanConfig = formBeans[i];
904 
905             processFormBeanExtension(beanConfig, config);
906         }
907 
908         for (int i = 0; i < formBeans.length; i++) {
909             FormBeanConfig formBean = formBeans[i];
910 
911             // Verify that required fields are all present for the form config
912             if (formBean.getType() == null) {
913                 handleValueRequiredException("type", formBean.getName(),
914                     "form bean");
915             }
916 
917             // ... and the property configs
918             FormPropertyConfig[] fpcs = formBean.findFormPropertyConfigs();
919 
920             for (int j = 0; j < fpcs.length; j++) {
921                 FormPropertyConfig property = fpcs[j];
922 
923                 if (property.getType() == null) {
924                     handleValueRequiredException("type", property.getName(),
925                         "form property");
926                 }
927             }
928 
929             // Force creation and registration of DynaActionFormClass instances
930             // for all dynamic form beans
931             if (formBean.getDynamic()) {
932                 formBean.getDynaActionFormClass();
933             }
934         }
935     }
936 
937     /**
938      * <p>Extend the form bean's configuration as necessary.</p>
939      *
940      * @param beanConfig   the configuration to process.
941      * @param moduleConfig the module configuration for this module.
942      * @throws ServletException if initialization cannot be performed.
943      */
944     protected void processFormBeanExtension(FormBeanConfig beanConfig,
945         ModuleConfig moduleConfig)
946         throws ServletException {
947         try {
948             if (!beanConfig.isExtensionProcessed()) {
949                 if (log.isDebugEnabled()) {
950                     log.debug("Processing extensions for '"
951                         + beanConfig.getName() + "'");
952                 }
953 
954                 beanConfig =
955                     processFormBeanConfigClass(beanConfig, moduleConfig);
956 
957                 beanConfig.processExtends(moduleConfig);
958             }
959         } catch (ServletException e) {
960             throw e;
961         } catch (Exception e) {
962             handleGeneralExtensionException("FormBeanConfig",
963                 beanConfig.getName(), e);
964         }
965     }
966 
967     /**
968      * <p>Checks if the current beanConfig is using the correct class based on
969      * the class of its ancestor form bean config.</p>
970      *
971      * @param beanConfig   The form bean to check.
972      * @param moduleConfig The config for the current module.
973      * @return The form bean config using the correct class as determined by
974      *         the config's ancestor and its own overridden value.
975      * @throws UnavailableException if an instance of the form bean config
976      *                              class cannot be created.
977      * @throws ServletException     on class creation error
978      */
979     protected FormBeanConfig processFormBeanConfigClass(
980         FormBeanConfig beanConfig, ModuleConfig moduleConfig)
981         throws ServletException {
982         String ancestor = beanConfig.getExtends();
983 
984         if (ancestor == null) {
985             // Nothing to do, then
986             return beanConfig;
987         }
988 
989         // Make sure that this bean is of the right class
990         FormBeanConfig baseConfig = moduleConfig.findFormBeanConfig(ancestor);
991 
992         if (baseConfig == null) {
993             throw new UnavailableException("Unable to find " + "form bean '"
994                 + ancestor + "' to extend.");
995         }
996 
997         // Was our bean's class overridden already?
998         if (beanConfig.getClass().equals(FormBeanConfig.class)) {
999             // Ensure that our bean is using the correct class
1000             if (!baseConfig.getClass().equals(beanConfig.getClass())) {
1001                 // Replace the bean with an instance of the correct class
1002                 FormBeanConfig newBeanConfig = null;
1003                 String baseConfigClassName = baseConfig.getClass().getName();
1004 
1005                 try {
1006                     newBeanConfig =
1007                         (FormBeanConfig) RequestUtils.applicationInstance(baseConfigClassName);
1008 
1009                     // copy the values
1010                     BeanUtils.copyProperties(newBeanConfig, beanConfig);
1011 
1012                     FormPropertyConfig[] fpc =
1013                         beanConfig.findFormPropertyConfigs();
1014 
1015                     for (int i = 0; i < fpc.length; i++) {
1016                         newBeanConfig.addFormPropertyConfig(fpc[i]);
1017                     }
1018                 } catch (Exception e) {
1019                     handleCreationException(baseConfigClassName, e);
1020                 }
1021 
1022                 // replace beanConfig with newBeanConfig
1023                 moduleConfig.removeFormBeanConfig(beanConfig);
1024                 moduleConfig.addFormBeanConfig(newBeanConfig);
1025                 beanConfig = newBeanConfig;
1026             }
1027         }
1028 
1029         return beanConfig;
1030     }
1031 
1032     /**
1033      * <p>Initialize the forwards for the specified module.</p>
1034      *
1035      * @param config ModuleConfig information for this module
1036      * @throws ServletException if initialization cannot be performed
1037      */
1038     protected void initModuleForwards(ModuleConfig config)
1039         throws ServletException {
1040         if (log.isDebugEnabled()) {
1041             log.debug("Initializing module path '" + config.getPrefix()
1042                 + "' forwards");
1043         }
1044 
1045         // Process forwards extensions.
1046         ForwardConfig[] forwards = config.findForwardConfigs();
1047 
1048         for (int i = 0; i < forwards.length; i++) {
1049             ForwardConfig forward = forwards[i];
1050 
1051             processForwardExtension(forward, config, null);
1052         }
1053 
1054         for (int i = 0; i < forwards.length; i++) {
1055             ForwardConfig forward = forwards[i];
1056 
1057             // Verify that required fields are all present for the forward
1058             if (forward.getPath() == null) {
1059                 handleValueRequiredException("path", forward.getName(),
1060                     "global forward");
1061             }
1062         }
1063     }
1064 
1065     /**
1066      * <p>Extend the forward's configuration as necessary.  If actionConfig is
1067      * provided, then this method will process the forwardConfig as part
1068      * of that actionConfig.  If actionConfig is null, the forwardConfig
1069      * will be processed as a global forward.</p>
1070      *
1071      * @param forwardConfig the configuration to process.
1072      * @param moduleConfig  the module configuration for this module.
1073      * @param actionConfig  If applicable, the config for the current action.
1074      * @throws ServletException if initialization cannot be performed.
1075      */
1076     protected void processForwardExtension(ForwardConfig forwardConfig,
1077         ModuleConfig moduleConfig, ActionConfig actionConfig)
1078         throws ServletException {
1079         try {
1080             if (!forwardConfig.isExtensionProcessed()) {
1081                 if (log.isDebugEnabled()) {
1082                     log.debug("Processing extensions for '"
1083                         + forwardConfig.getName() + "'");
1084                 }
1085 
1086                 forwardConfig =
1087                     processForwardConfigClass(forwardConfig, moduleConfig,
1088                         actionConfig);
1089 
1090                 forwardConfig.processExtends(moduleConfig, actionConfig);
1091             }
1092         } catch (ServletException e) {
1093             throw e;
1094         } catch (Exception e) {
1095             handleGeneralExtensionException("Forward", forwardConfig.getName(),
1096                 e);
1097         }
1098     }
1099 
1100     /**
1101      * <p>Checks if the current forwardConfig is using the correct class based
1102      * on the class of its configuration ancestor.  If actionConfig is
1103      * provided, then this method will process the forwardConfig as part
1104      * of that actionConfig.  If actionConfig is null, the forwardConfig
1105      * will be processed as a global forward.</p>
1106      *
1107      * @param forwardConfig The forward to check.
1108      * @param moduleConfig  The config for the current module.
1109      * @param actionConfig  If applicable, the config for the current action.
1110      * @return The forward config using the correct class as determined by the
1111      *         config's ancestor and its own overridden value.
1112      * @throws UnavailableException if an instance of the forward config class
1113      *                              cannot be created.
1114      * @throws ServletException     on class creation error
1115      */
1116     protected ForwardConfig processForwardConfigClass(
1117         ForwardConfig forwardConfig, ModuleConfig moduleConfig,
1118         ActionConfig actionConfig)
1119         throws ServletException {
1120         String ancestor = forwardConfig.getExtends();
1121 
1122         if (ancestor == null) {
1123             // Nothing to do, then
1124             return forwardConfig;
1125         }
1126 
1127         // Make sure that this config is of the right class
1128         ForwardConfig baseConfig = null;
1129         if (actionConfig != null) {
1130             // Look for this in the actionConfig
1131             baseConfig = actionConfig.findForwardConfig(ancestor);
1132         }
1133 
1134         if (baseConfig == null) {
1135             // Either this is a forwardConfig that inherits a global config,
1136             //  or actionConfig is null
1137             baseConfig = moduleConfig.findForwardConfig(ancestor);
1138         }
1139 
1140         if (baseConfig == null) {
1141             throw new UnavailableException("Unable to find " + "forward '"
1142                 + ancestor + "' to extend.");
1143         }
1144 
1145         // Was our forwards's class overridden already?
1146         if (forwardConfig.getClass().equals(ActionForward.class)) {
1147             // Ensure that our forward is using the correct class
1148             if (!baseConfig.getClass().equals(forwardConfig.getClass())) {
1149                 // Replace the config with an instance of the correct class
1150                 ForwardConfig newForwardConfig = null;
1151                 String baseConfigClassName = baseConfig.getClass().getName();
1152 
1153                 try {
1154                     newForwardConfig =
1155                         (ForwardConfig) RequestUtils.applicationInstance(
1156                                 baseConfigClassName);
1157 
1158                     // copy the values
1159                     BeanUtils.copyProperties(newForwardConfig, forwardConfig);
1160                 } catch (Exception e) {
1161                     handleCreationException(baseConfigClassName, e);
1162                 }
1163 
1164                 // replace forwardConfig with newForwardConfig
1165                 if (actionConfig != null) {
1166                     actionConfig.removeForwardConfig(forwardConfig);
1167                     actionConfig.addForwardConfig(newForwardConfig);
1168                 } else {
1169                     // this is a global forward
1170                     moduleConfig.removeForwardConfig(forwardConfig);
1171                     moduleConfig.addForwardConfig(newForwardConfig);
1172                 }
1173                 forwardConfig = newForwardConfig;
1174             }
1175         }
1176 
1177         return forwardConfig;
1178     }
1179 
1180     /**
1181      * <p>Initialize the exception handlers for the specified module.</p>
1182      *
1183      * @param config ModuleConfig information for this module
1184      * @throws ServletException if initialization cannot be performed
1185      * @since Struts 1.3
1186      */
1187     protected void initModuleExceptionConfigs(ModuleConfig config)
1188         throws ServletException {
1189         if (log.isDebugEnabled()) {
1190             log.debug("Initializing module path '" + config.getPrefix()
1191                 + "' forwards");
1192         }
1193 
1194         // Process exception config extensions.
1195         ExceptionConfig[] exceptions = config.findExceptionConfigs();
1196 
1197         for (int i = 0; i < exceptions.length; i++) {
1198             ExceptionConfig exception = exceptions[i];
1199 
1200             processExceptionExtension(exception, config, null);
1201         }
1202 
1203         for (int i = 0; i < exceptions.length; i++) {
1204             ExceptionConfig exception = exceptions[i];
1205 
1206             // Verify that required fields are all present for the config
1207             if (exception.getKey() == null) {
1208                 handleValueRequiredException("key", exception.getType(),
1209                     "global exception config");
1210             }
1211         }
1212     }
1213 
1214     /**
1215      * <p>Extend the exception's configuration as necessary. If actionConfig is
1216      * provided, then this method will process the exceptionConfig as part
1217      * of that actionConfig.  If actionConfig is null, the exceptionConfig
1218      * will be processed as a global forward.</p>
1219      *
1220      * @param exceptionConfig the configuration to process.
1221      * @param moduleConfig    the module configuration for this module.
1222      * @param actionConfig  If applicable, the config for the current action.
1223      * @throws ServletException if initialization cannot be performed.
1224      */
1225     protected void processExceptionExtension(ExceptionConfig exceptionConfig,
1226         ModuleConfig moduleConfig, ActionConfig actionConfig)
1227         throws ServletException {
1228         try {
1229             if (!exceptionConfig.isExtensionProcessed()) {
1230                 if (log.isDebugEnabled()) {
1231                     log.debug("Processing extensions for '"
1232                         + exceptionConfig.getType() + "'");
1233                 }
1234 
1235                 exceptionConfig =
1236                     processExceptionConfigClass(exceptionConfig, moduleConfig,
1237                         actionConfig);
1238 
1239                 exceptionConfig.processExtends(moduleConfig, actionConfig);
1240             }
1241         } catch (ServletException e) {
1242             throw e;
1243         } catch (Exception e) {
1244             handleGeneralExtensionException("Exception",
1245                 exceptionConfig.getType(), e);
1246         }
1247     }
1248 
1249     /**
1250      * <p>Checks if the current exceptionConfig is using the correct class
1251      * based on the class of its configuration ancestor. If actionConfig is
1252      * provided, then this method will process the exceptionConfig as part
1253      * of that actionConfig.  If actionConfig is null, the exceptionConfig
1254      * will be processed as a global forward.</p>
1255      *
1256      * @param exceptionConfig The config to check.
1257      * @param moduleConfig    The config for the current module.
1258      * @param actionConfig  If applicable, the config for the current action.
1259      * @return The exception config using the correct class as determined by
1260      *         the config's ancestor and its own overridden value.
1261      * @throws ServletException if an instance of the exception config class
1262      *                          cannot be created.
1263      */
1264     protected ExceptionConfig processExceptionConfigClass(
1265         ExceptionConfig exceptionConfig, ModuleConfig moduleConfig,
1266         ActionConfig actionConfig)
1267         throws ServletException {
1268         String ancestor = exceptionConfig.getExtends();
1269 
1270         if (ancestor == null) {
1271             // Nothing to do, then
1272             return exceptionConfig;
1273         }
1274 
1275         // Make sure that this config is of the right class
1276         ExceptionConfig baseConfig = null;
1277         if (actionConfig != null) {
1278             baseConfig = actionConfig.findExceptionConfig(ancestor);
1279         }
1280 
1281         if (baseConfig == null) {
1282             // This means either there's no actionConfig anyway, or the
1283             // ancestor is not defined within the action.
1284             baseConfig = moduleConfig.findExceptionConfig(ancestor);
1285         }
1286 
1287         if (baseConfig == null) {
1288             throw new UnavailableException("Unable to find "
1289                 + "exception config '" + ancestor + "' to extend.");
1290         }
1291 
1292         // Was our config's class overridden already?
1293         if (exceptionConfig.getClass().equals(ExceptionConfig.class)) {
1294             // Ensure that our config is using the correct class
1295             if (!baseConfig.getClass().equals(exceptionConfig.getClass())) {
1296                 // Replace the config with an instance of the correct class
1297                 ExceptionConfig newExceptionConfig = null;
1298                 String baseConfigClassName = baseConfig.getClass().getName();
1299 
1300                 try {
1301                     newExceptionConfig =
1302                         (ExceptionConfig) RequestUtils.applicationInstance(
1303                             baseConfigClassName);
1304 
1305                     // copy the values
1306                     BeanUtils.copyProperties(newExceptionConfig,
1307                         exceptionConfig);
1308                 } catch (Exception e) {
1309                     handleCreationException(baseConfigClassName, e);
1310                 }
1311 
1312                 // replace exceptionConfig with newExceptionConfig
1313                 if (actionConfig != null) {
1314                     actionConfig.removeExceptionConfig(exceptionConfig);
1315                     actionConfig.addExceptionConfig(newExceptionConfig);
1316                 } else {
1317                     moduleConfig.removeExceptionConfig(exceptionConfig);
1318                     moduleConfig.addExceptionConfig(newExceptionConfig);
1319                 }
1320                 exceptionConfig = newExceptionConfig;
1321             }
1322         }
1323 
1324         return exceptionConfig;
1325     }
1326 
1327     /**
1328      * <p>Initialize the action configs for the specified module.</p>
1329      *
1330      * @param config ModuleConfig information for this module
1331      * @throws ServletException if initialization cannot be performed
1332      * @since Struts 1.3
1333      */
1334     protected void initModuleActions(ModuleConfig config)
1335         throws ServletException {
1336         if (log.isDebugEnabled()) {
1337             log.debug("Initializing module path '" + config.getPrefix()
1338                 + "' action configs");
1339         }
1340 
1341         // Process ActionConfig extensions.
1342         ActionConfig[] actionConfigs = config.findActionConfigs();
1343 
1344         for (int i = 0; i < actionConfigs.length; i++) {
1345             ActionConfig actionConfig = actionConfigs[i];
1346 
1347             processActionConfigExtension(actionConfig, config);
1348         }
1349 
1350         for (int i = 0; i < actionConfigs.length; i++) {
1351             ActionConfig actionConfig = actionConfigs[i];
1352 
1353             // Verify that required fields are all present for the forward
1354             // configs
1355             ForwardConfig[] forwards = actionConfig.findForwardConfigs();
1356 
1357             for (int j = 0; j < forwards.length; j++) {
1358                 ForwardConfig forward = forwards[j];
1359 
1360                 if (forward.getPath() == null) {
1361                     handleValueRequiredException("path", forward.getName(),
1362                         "action forward");
1363                 }
1364             }
1365 
1366             // ... and the exception configs
1367             ExceptionConfig[] exceptions = actionConfig.findExceptionConfigs();
1368 
1369             for (int j = 0; j < exceptions.length; j++) {
1370                 ExceptionConfig exception = exceptions[j];
1371 
1372                 if (exception.getKey() == null) {
1373                     handleValueRequiredException("key", exception.getType(),
1374                         "action exception config");
1375                 }
1376             }
1377         }
1378     }
1379 
1380     /**
1381      * <p>Extend the action's configuration as necessary.</p>
1382      *
1383      * @param actionConfig the configuration to process.
1384      * @param moduleConfig the module configuration for this module.
1385      * @throws ServletException if initialization cannot be performed.
1386      */
1387     protected void processActionConfigExtension(ActionConfig actionConfig,
1388         ModuleConfig moduleConfig)
1389         throws ServletException {
1390         try {
1391             if (!actionConfig.isExtensionProcessed()) {
1392                 if (log.isDebugEnabled()) {
1393                     log.debug("Processing extensions for '"
1394                         + actionConfig.getPath() + "'");
1395                 }
1396 
1397                 actionConfig =
1398                     processActionConfigClass(actionConfig, moduleConfig);
1399 
1400                 actionConfig.processExtends(moduleConfig);
1401             }
1402 
1403             // Process forwards extensions.
1404             ForwardConfig[] forwards = actionConfig.findForwardConfigs();
1405             for (int i = 0; i < forwards.length; i++) {
1406                 ForwardConfig forward = forwards[i];
1407                 processForwardExtension(forward, moduleConfig, actionConfig);
1408             }
1409 
1410             // Process exception extensions.
1411             ExceptionConfig[] exceptions = actionConfig.findExceptionConfigs();
1412             for (int i = 0; i < exceptions.length; i++) {
1413                 ExceptionConfig exception = exceptions[i];
1414                 processExceptionExtension(exception, moduleConfig,
1415                     actionConfig);
1416             }
1417         } catch (ServletException e) {
1418             throw e;
1419         } catch (Exception e) {
1420             handleGeneralExtensionException("Action", actionConfig.getPath(), e);
1421         }
1422     }
1423 
1424     /**
1425      * <p>Checks if the current actionConfig is using the correct class based
1426      * on the class of its ancestor ActionConfig.</p>
1427      *
1428      * @param actionConfig The action config to check.
1429      * @param moduleConfig The config for the current module.
1430      * @return The config object using the correct class as determined by the
1431      *         config's ancestor and its own overridden value.
1432      * @throws ServletException if an instance of the action config class
1433      *                          cannot be created.
1434      */
1435     protected ActionConfig processActionConfigClass(ActionConfig actionConfig,
1436         ModuleConfig moduleConfig)
1437         throws ServletException {
1438         String ancestor = actionConfig.getExtends();
1439 
1440         if (ancestor == null) {
1441             // Nothing to do, then
1442             return actionConfig;
1443         }
1444 
1445         // Make sure that this config is of the right class
1446         ActionConfig baseConfig = moduleConfig.findActionConfig(ancestor);
1447 
1448         if (baseConfig == null) {
1449             throw new UnavailableException("Unable to find "
1450                 + "action config for '" + ancestor + "' to extend.");
1451         }
1452 
1453         // Was our actionConfig's class overridden already?
1454         if (actionConfig.getClass().equals(ActionMapping.class)) {
1455             // Ensure that our config is using the correct class
1456             if (!baseConfig.getClass().equals(actionConfig.getClass())) {
1457                 // Replace the config with an instance of the correct class
1458                 ActionConfig newActionConfig = null;
1459                 String baseConfigClassName = baseConfig.getClass().getName();
1460 
1461                 try {
1462                     newActionConfig =
1463                         (ActionConfig) RequestUtils.applicationInstance(baseConfigClassName);
1464 
1465                     // copy the values
1466                     BeanUtils.copyProperties(newActionConfig, actionConfig);
1467 
1468                     // copy the forward and exception configs, too
1469                     ForwardConfig[] forwards =
1470                         actionConfig.findForwardConfigs();
1471 
1472                     for (int i = 0; i < forwards.length; i++) {
1473                         newActionConfig.addForwardConfig(forwards[i]);
1474                     }
1475 
1476                     ExceptionConfig[] exceptions =
1477                         actionConfig.findExceptionConfigs();
1478 
1479                     for (int i = 0; i < exceptions.length; i++) {
1480                         newActionConfig.addExceptionConfig(exceptions[i]);
1481                     }
1482                 } catch (Exception e) {
1483                     handleCreationException(baseConfigClassName, e);
1484                 }
1485 
1486                 // replace actionConfig with newActionConfig
1487                 moduleConfig.removeActionConfig(actionConfig);
1488                 moduleConfig.addActionConfig(newActionConfig);
1489                 actionConfig = newActionConfig;
1490             }
1491         }
1492 
1493         return actionConfig;
1494     }
1495 
1496     /**
1497      * <p>Initialize the application <code>MessageResources</code> for the
1498      * specified module.</p>
1499      *
1500      * @param config ModuleConfig information for this module
1501      * @throws ServletException if initialization cannot be performed
1502      * @since Struts 1.1
1503      */
1504     protected void initModuleMessageResources(ModuleConfig config)
1505         throws ServletException {
1506         MessageResourcesConfig[] mrcs = config.findMessageResourcesConfigs();
1507 
1508         for (int i = 0; i < mrcs.length; i++) {
1509             if ((mrcs[i].getFactory() == null)
1510                 || (mrcs[i].getParameter() == null)) {
1511                 continue;
1512             }
1513 
1514             if (log.isDebugEnabled()) {
1515                 log.debug("Initializing module path '" + config.getPrefix()
1516                     + "' message resources from '" + mrcs[i].getParameter()
1517                     + "'");
1518             }
1519 
1520             String factory = mrcs[i].getFactory();
1521 
1522             MessageResourcesFactory.setFactoryClass(factory);
1523 
1524             MessageResourcesFactory factoryObject =
1525                 MessageResourcesFactory.createFactory();
1526 
1527             factoryObject.setConfig(mrcs[i]);
1528 
1529             MessageResources resources =
1530                 factoryObject.createResources(mrcs[i].getParameter());
1531 
1532             resources.setReturnNull(mrcs[i].getNull());
1533             resources.setEscape(mrcs[i].isEscape());
1534             getServletContext().setAttribute(mrcs[i].getKey()
1535                 + config.getPrefix(), resources);
1536         }
1537     }
1538 
1539     /**
1540      * <p>Create (if needed) and return a new <code>Digester</code> instance
1541      * that has been initialized to process Struts module configuration files
1542      * and configure a corresponding <code>ModuleConfig</code> object (which
1543      * must be pushed on to the evaluation stack before parsing begins).</p>
1544      *
1545      * @return A new configured <code>Digester</code> instance.
1546      * @throws ServletException if a Digester cannot be configured
1547      * @since Struts 1.1
1548      */
1549     protected Digester initConfigDigester()
1550         throws ServletException {
1551         // :FIXME: Where can ServletException be thrown?
1552         // Do we have an existing instance?
1553         if (configDigester != null) {
1554             return (configDigester);
1555         }
1556 
1557         // Create a new Digester instance with standard capabilities
1558         configDigester = new Digester();
1559         configDigester.setNamespaceAware(true);
1560         configDigester.setValidating(this.isValidating());
1561         configDigester.setUseContextClassLoader(true);
1562         configDigester.addRuleSet(new ConfigRuleSet());
1563 
1564         for (int i = 0; i < registrations.length; i += 2) {
1565             URL url = this.getClass().getResource(registrations[i + 1]);
1566 
1567             if (url != null) {
1568                 configDigester.register(registrations[i], url.toString());
1569             }
1570         }
1571 
1572         this.addRuleSets();
1573 
1574         // Return the completely configured Digester instance
1575         return (configDigester);
1576     }
1577 
1578     /**
1579      * <p>Add any custom RuleSet instances to configDigester that have been
1580      * specified in the <code>rulesets</code> init parameter.</p>
1581      *
1582      * @throws ServletException if an error occurs
1583      */
1584     private void addRuleSets()
1585         throws ServletException {
1586         String rulesets = getServletConfig().getInitParameter("rulesets");
1587 
1588         if (rulesets == null) {
1589             rulesets = "";
1590         }
1591 
1592         rulesets = rulesets.trim();
1593 
1594         String ruleset;
1595 
1596         while (rulesets.length() > 0) {
1597             int comma = rulesets.indexOf(",");
1598 
1599             if (comma < 0) {
1600                 ruleset = rulesets.trim();
1601                 rulesets = "";
1602             } else {
1603                 ruleset = rulesets.substring(0, comma).trim();
1604                 rulesets = rulesets.substring(comma + 1).trim();
1605             }
1606 
1607             if (log.isDebugEnabled()) {
1608                 log.debug("Configuring custom Digester Ruleset of type "
1609                     + ruleset);
1610             }
1611 
1612             try {
1613                 RuleSet instance =
1614                     (RuleSet) RequestUtils.applicationInstance(ruleset);
1615 
1616                 this.configDigester.addRuleSet(instance);
1617             } catch (Exception e) {
1618                 log.error("Exception configuring custom Digester RuleSet", e);
1619                 throw new ServletException(e);
1620             }
1621         }
1622     }
1623 
1624     /**
1625      * <p>Check the status of the <code>validating</code> initialization
1626      * parameter.</p>
1627      *
1628      * @return true if the module Digester should validate.
1629      */
1630     private boolean isValidating() {
1631         boolean validating = true;
1632         String value = getServletConfig().getInitParameter("validating");
1633 
1634         if ("false".equalsIgnoreCase(value) || "no".equalsIgnoreCase(value)
1635             || "n".equalsIgnoreCase(value) || "0".equalsIgnoreCase(value)) {
1636             validating = false;
1637         }
1638 
1639         return validating;
1640     }
1641 
1642     /**
1643      * <p>Initialize our internal MessageResources bundle.</p>
1644      *
1645      * @throws ServletException     if we cannot initialize these resources
1646      * @throws UnavailableException if we cannot load  resources
1647      */
1648     protected void initInternal()
1649         throws ServletException {
1650         try {
1651             internal = MessageResources.getMessageResources(internalName);
1652         } catch (MissingResourceException e) {
1653             log.error("Cannot load internal resources from '" + internalName
1654                 + "'", e);
1655             throw new UnavailableException(
1656                 "Cannot load internal resources from '" + internalName + "'");
1657         }
1658     }
1659 
1660     /**
1661      * <p>Parse the configuration documents specified by the
1662      * <code>chainConfig</code> init-param to configure the default {@link
1663      * org.apache.commons.chain.Catalog} that is registered in the {@link
1664      * CatalogFactory} instance for this application.</p>
1665      *
1666      * @throws ServletException if an error occurs.
1667      */
1668     protected void initChain()
1669         throws ServletException {
1670         // Parse the configuration file specified by path or resource
1671         try {
1672             String value;
1673 
1674             value = getServletConfig().getInitParameter("chainConfig");
1675 
1676             if (value != null) {
1677                 chainConfig = value;
1678             }
1679 
1680             ConfigParser parser = new ConfigParser();
1681             List urls = splitAndResolvePaths(chainConfig);
1682             URL resource;
1683 
1684             for (Iterator i = urls.iterator(); i.hasNext();) {
1685                 resource = (URL) i.next();
1686                 log.info("Loading chain catalog from " + resource);
1687                 parser.parse(resource);
1688             }
1689         } catch (Exception e) {
1690             log.error("Exception loading resources", e);
1691             throw new ServletException(e);
1692         }
1693     }
1694 
1695     /**
1696      * <p>Initialize other global characteristics of the controller
1697      * servlet.</p>
1698      *
1699      * @throws ServletException if we cannot initialize these resources
1700      */
1701     protected void initOther()
1702         throws ServletException {
1703         String value;
1704 
1705         value = getServletConfig().getInitParameter("config");
1706 
1707         if (value != null) {
1708             config = value;
1709         }
1710 
1711         // Backwards compatibility for form beans of Java wrapper classes
1712         // Set to true for strict Struts 1.0 compatibility
1713         value = getServletConfig().getInitParameter("convertNull");
1714 
1715         if ("true".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value)
1716             || "on".equalsIgnoreCase(value) || "y".equalsIgnoreCase(value)
1717             || "1".equalsIgnoreCase(value)) {
1718             convertNull = true;
1719         }
1720 
1721         if (convertNull) {
1722             ConvertUtils.deregister();
1723             ConvertUtils.register(new BigDecimalConverter(null),
1724                 BigDecimal.class);
1725             ConvertUtils.register(new BigIntegerConverter(null),
1726                 BigInteger.class);
1727             ConvertUtils.register(new BooleanConverter(null), Boolean.class);
1728             ConvertUtils.register(new ByteConverter(null), Byte.class);
1729             ConvertUtils.register(new CharacterConverter(null), Character.class);
1730             ConvertUtils.register(new DoubleConverter(null), Double.class);
1731             ConvertUtils.register(new FloatConverter(null), Float.class);
1732             ConvertUtils.register(new IntegerConverter(null), Integer.class);
1733             ConvertUtils.register(new LongConverter(null), Long.class);
1734             ConvertUtils.register(new ShortConverter(null), Short.class);
1735         }
1736     }
1737 
1738     /**
1739      * <p>Initialize the servlet mapping under which our controller servlet is
1740      * being accessed.  This will be used in the <code>&html:form&gt;</code>
1741      * tag to generate correct destination URLs for form submissions.</p>
1742      *
1743      * @throws ServletException if error happens while scanning web.xml
1744      */
1745     protected void initServlet()
1746         throws ServletException {
1747         // Remember our servlet name
1748         this.servletName = getServletConfig().getServletName();
1749 
1750         // Prepare a Digester to scan the web application deployment descriptor
1751         Digester digester = new Digester();
1752 
1753         digester.push(this);
1754         digester.setNamespaceAware(true);
1755         digester.setValidating(false);
1756 
1757         // Register our local copy of the DTDs that we can find
1758         for (int i = 0; i < registrations.length; i += 2) {
1759             URL url = this.getClass().getResource(registrations[i + 1]);
1760 
1761             if (url != null) {
1762                 digester.register(registrations[i], url.toString());
1763             }
1764         }
1765 
1766         // Configure the processing rules that we need
1767         digester.addCallMethod("web-app/servlet-mapping", "addServletMapping", 2);
1768         digester.addCallParam("web-app/servlet-mapping/servlet-name", 0);
1769         digester.addCallParam("web-app/servlet-mapping/url-pattern", 1);
1770 
1771         // Process the web application deployment descriptor
1772         if (log.isDebugEnabled()) {
1773             log.debug("Scanning web.xml for controller servlet mapping");
1774         }
1775 
1776         InputStream input =
1777             getServletContext().getResourceAsStream("/WEB-INF/web.xml");
1778 
1779         if (input == null) {
1780             log.error(internal.getMessage("configWebXml"));
1781             throw new ServletException(internal.getMessage("configWebXml"));
1782         }
1783 
1784         try {
1785             digester.parse(input);
1786         } catch (IOException e) {
1787             log.error(internal.getMessage("configWebXml"), e);
1788             throw new ServletException(e);
1789         } catch (SAXException e) {
1790             log.error(internal.getMessage("configWebXml"), e);
1791             throw new ServletException(e);
1792         } finally {
1793             try {
1794                 input.close();
1795             } catch (IOException e) {
1796                 log.error(internal.getMessage("configWebXml"), e);
1797                 throw new ServletException(e);
1798             }
1799         }
1800 
1801         // Record a servlet context attribute (if appropriate)
1802         if (log.isDebugEnabled()) {
1803             log.debug("Mapping for servlet '" + servletName + "' = '"
1804                 + servletMapping + "'");
1805         }
1806 
1807         if (servletMapping != null) {
1808             getServletContext().setAttribute(Globals.SERVLET_KEY, servletMapping);
1809         }
1810     }
1811 
1812     /**
1813      * <p>Takes a comma-delimited string and splits it into paths, then
1814      * resolves those paths using the ServletContext and appropriate
1815      * ClassLoader.  When loading from the classloader, multiple resources per
1816      * path are supported to support, for example, multiple jars containing
1817      * the same named config file.</p>
1818      *
1819      * @param paths A comma-delimited string of paths
1820      * @return A list of resolved URL's for all found resources
1821      * @throws ServletException if a servlet exception is thrown
1822      */
1823     protected List splitAndResolvePaths(String paths)
1824         throws ServletException {
1825         ClassLoader loader = Thread.currentThread().getContextClassLoader();
1826 
1827         if (loader == null) {
1828             loader = this.getClass().getClassLoader();
1829         }
1830 
1831         ArrayList resolvedUrls = new ArrayList();
1832 
1833         URL resource;
1834         String path = null;
1835 
1836         try {
1837             // Process each specified resource path
1838             while (paths.length() > 0) {
1839                 resource = null;
1840 
1841                 int comma = paths.indexOf(',');
1842 
1843                 if (comma >= 0) {
1844                     path = paths.substring(0, comma).trim();
1845                     paths = paths.substring(comma + 1);
1846                 } else {
1847                     path = paths.trim();
1848                     paths = "";
1849                 }
1850 
1851                 if (path.length() < 1) {
1852                     break;
1853                 }
1854 
1855                 if (path.charAt(0) == '/') {
1856                     resource = getServletContext().getResource(path);
1857                 }
1858 
1859                 if (resource == null) {
1860                     if (log.isDebugEnabled()) {
1861                         log.debug("Unable to locate " + path
1862                             + " in the servlet context, "
1863                             + "trying classloader.");
1864                     }
1865 
1866                     Enumeration e = loader.getResources(path);
1867 
1868                     if (!e.hasMoreElements()) {
1869                         String msg = internal.getMessage("configMissing", path);
1870 
1871                         log.error(msg);
1872                         throw new UnavailableException(msg);
1873                     } else {
1874                         while (e.hasMoreElements()) {
1875                             resolvedUrls.add(e.nextElement());
1876                         }
1877                     }
1878                 } else {
1879                     resolvedUrls.add(resource);
1880                 }
1881             }
1882         } catch (MalformedURLException e) {
1883             handleConfigException(path, e);
1884         } catch (IOException e) {
1885             handleConfigException(path, e);
1886         }
1887 
1888         return resolvedUrls;
1889     }
1890 
1891     /**
1892      * <p>Perform the standard request processing for this request, and create
1893      * the corresponding response.</p>
1894      *
1895      * @param request  The servlet request we are processing
1896      * @param response The servlet response we are creating
1897      * @throws IOException      if an input/output error occurs
1898      * @throws ServletException if a servlet exception is thrown
1899      */
1900     protected void process(HttpServletRequest request,
1901         HttpServletResponse response)
1902         throws IOException, ServletException {
1903         ModuleUtils.getInstance().selectModule(request, getServletContext());
1904 
1905         ModuleConfig config = getModuleConfig(request);
1906 
1907         RequestProcessor processor = getProcessorForModule(config);
1908 
1909         if (processor == null) {
1910             processor = getRequestProcessor(config);
1911         }
1912 
1913         processor.process(request, response);
1914     }
1915 }