View Javadoc

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