Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/apache/struts/action/ActionServlet.java


1   /*
2    * $Id: ActionServlet.java 105787 2004-11-19 07:29:33Z mrdon $ 
3    *
4    * Copyright 2000-2004 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  
19  package org.apache.struts.action;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.math.BigDecimal;
24  import java.math.BigInteger;
25  import java.net.MalformedURLException;
26  import java.net.URL;
27  import java.util.ArrayList;
28  import java.util.Enumeration;
29  import java.util.Iterator;
30  import java.util.MissingResourceException;
31  
32  import javax.servlet.ServletContext;
33  import javax.servlet.ServletException;
34  import javax.servlet.UnavailableException;
35  import javax.servlet.http.HttpServlet;
36  import javax.servlet.http.HttpServletRequest;
37  import javax.servlet.http.HttpServletResponse;
38  import javax.sql.DataSource;
39  
40  import org.apache.commons.beanutils.BeanUtils;
41  import org.apache.commons.beanutils.ConvertUtils;
42  import org.apache.commons.beanutils.PropertyUtils;
43  import org.apache.commons.beanutils.converters.BigDecimalConverter;
44  import org.apache.commons.beanutils.converters.BigIntegerConverter;
45  import org.apache.commons.beanutils.converters.BooleanConverter;
46  import org.apache.commons.beanutils.converters.ByteConverter;
47  import org.apache.commons.beanutils.converters.CharacterConverter;
48  import org.apache.commons.beanutils.converters.DoubleConverter;
49  import org.apache.commons.beanutils.converters.FloatConverter;
50  import org.apache.commons.beanutils.converters.IntegerConverter;
51  import org.apache.commons.beanutils.converters.LongConverter;
52  import org.apache.commons.beanutils.converters.ShortConverter;
53  import org.apache.commons.collections.FastHashMap;
54  import org.apache.commons.digester.Digester;
55  import org.apache.commons.digester.RuleSet;
56  import org.apache.commons.logging.Log;
57  import org.apache.commons.logging.LogFactory;
58  import org.apache.struts.Globals;
59  import org.apache.struts.config.ConfigRuleSet;
60  import org.apache.struts.config.DataSourceConfig;
61  import org.apache.struts.config.FormBeanConfig;
62  import org.apache.struts.config.MessageResourcesConfig;
63  import org.apache.struts.config.ModuleConfig;
64  import org.apache.struts.config.ModuleConfigFactory;
65  import org.apache.struts.config.PlugInConfig;
66  import org.apache.struts.util.MessageResources;
67  import org.apache.struts.util.MessageResourcesFactory;
68  import org.apache.struts.util.ModuleUtils;
69  import org.apache.struts.util.RequestUtils;
70  import org.apache.struts.util.ServletContextWriter;
71  import org.xml.sax.InputSource;
72  import org.xml.sax.SAXException;
73  
74  /**
75   * <p><strong>ActionServlet</strong> provides the "controller" in the
76   * Model-View-Controller (MVC) design pattern for web applications that is
77   * commonly known as "Model 2".  This nomenclature originated with a
78   * description in the JavaServerPages Specification, version 0.92, and has
79   * persisted ever since (in the absence of a better name).</p>
80   *
81   * <p>Generally, a "Model 2" application is architected as follows:</p>
82   * <ul>
83   * <li>The user interface will generally be created with server pages, which
84   *     will not themselves contain any business logic. These pages represent
85   *     the "view" component of an MVC architecture.</li>
86   * <li>Forms and hyperlinks in the user interface that require business logic
87   *     to be executed will be submitted to a request URI that is mapped to this
88   *     servlet.</li>
89   * <li>There can be <b>one</b> instance of this servlet class,
90   *     which receives and processes all requests that change the state of
91   *     a user's interaction with the application.  The servlet delegates the
92   *     handling of a request to a @link(RequestProcessor) object. This component
93   *     represents the "controller" component of an MVC architecture.</li>
94   * <li>The <code>RequestProcessor</code> selects and invokes an @link(Action) class to perform
95   *     the requested business logic, or delegates the response to another resource.</li>
96   * <li>The <code>Action</code> classes can manipulate the state of the application's
97   *     interaction with the user, typically by creating or modifying JavaBeans
98   *     that are stored as request or session attributes (depending on how long
99   *     they need to be available). Such JavaBeans represent the "model"
100  *     component of an MVC architecture.</li>
101  * <li>Instead of producing the next page of the user interface directly,
102  *     <code>Action</code> classes generally return an @link(ActionForward) to indicate
103  *     which resource should handle the response. If the <code>Action</code>
104  *     does not return null, the <code>RequestProcessor</code> forwards or
105  *     redirects to the specified resource (by utilizing
106  *     <code>RequestDispatcher.forward</code> or <code>Response.sendRedirect</code>)
107  *     so as to produce the next page of the user interface.</li>
108  * </ul>
109  *
110  * <p>The standard version of <code>RequestsProcessor</code> implements the
111  *    following logic for each incoming HTTP request. You can override
112  *    some or all of this functionality by subclassing this object and
113  *    implementing your own version of the processing.</p>
114  * <ul>
115  * <li>Identify, from the incoming request URI, the substring that will be
116  *     used to select an action procedure.</li>
117  * <li>Use this substring to map to the Java class name of the corresponding
118  *     action class (an implementation of the <code>Action</code> interface).
119  *     </li>
120  * <li>If this is the first request for a particular <code>Action</code> class,
121  *     instantiate an instance of that class and cache it for future use.</li>
122  * <li>Optionally populate the properties of an <code>ActionForm</code> bean
123  *     associated with this mapping.</li>
124  * <li>Call the <code>execute</code> method of this <code>Action</code> class, passing
125  *     on a reference to the mapping that was used, the relevant form-bean
126  *     (if any), and the request and the response that were passed to the
127  *     controller by the servlet container (thereby providing access to any
128  *     specialized properties of the mapping itself as well as to the
129  *     ServletContext).
130  *     </li>
131  * </ul>
132  *
133  * <p>The standard version of <code>ActionServlet</code> is configured based
134  * on the following servlet initialization parameters, which you will specify
135  * in the web application deployment descriptor (<code>/WEB-INF/web.xml</code>)
136  * for your application.  Subclasses that specialize this servlet are free to
137  * define additional initialization parameters. </p>
138  * <ul>
139  * <li><strong>config</strong> - Comma-separated list of context-relative
140  *     path(s) to the XML resource(s) containing the configuration information
141  *     for the default module.  (Multiple files support since Struts 1.1)
142  *     [/WEB-INF/struts-config.xml].</li>
143  * <li><strong>config/${module}</strong> - Comma-separated list of
144  *     Context-relative path(s) to the XML resource(s)
145  *     containing the configuration information for the module that
146  *     will use the specified prefix (/${module}). This can be repeated as many
147  *     times as required for multiple modules. (Since Struts 1.1)</li>
148  * <li><strong>configFactory</strong> - The Java class name of the
149  *     <code>ModuleConfigFactory</code> used to create the implementation of the
150  *     <code>ModuleConfig</code> interface.
151  *     [org.apache.struts.config.impl.DefaultModuleConfigFactory]
152  * </li>
153  * <li><strong>convertNull</strong> - Force simulation of the Struts 1.0 behavior
154  *     when populating forms. If set to true, the numeric Java wrapper class types
155  *     (like <code>java.lang.Integer</code>) will default to null (rather than 0).
156  *     (Since Struts 1.1) [false] </li>
157  * <li><strong>rulesets</strong> - Comma-delimited list of fully qualified
158  *     classnames of additional <code>org.apache.commons.digester.RuleSet</code>
159  *     instances that should be added to the <code>Digester</code> that will
160  *     be processing <code>struts-config.xml</code> files.  By default, only
161  *     the <code>RuleSet</code> for the standard configuration elements is
162  *     loaded.  (Since Struts 1.1)</li>
163  * <li><strong>validating</strong> - Should we use a validating XML parser to
164  *     process the configuration file (strongly recommended)? [true]</li>
165  * </ul>
166  *
167  * @version $Rev: 105787 $ $Date: 2004-11-18 23:29:33 -0800 (Thu, 18 Nov 2004) $
168  */
169 public class ActionServlet extends HttpServlet {
170 
171 
172     // ----------------------------------------------------- Instance Variables
173 
174 
175     /**
176      * <p>Comma-separated list of context-relative path(s) to our configuration
177      * resource(s) for the default module.</p>
178      */
179     protected String config = "/WEB-INF/struts-config.xml";
180 
181 
182     /**
183      * <p>The Digester used to produce ModuleConfig objects from a
184      * Struts configuration file.</p>
185      *
186      * @since Struts 1.1
187      */
188     protected Digester configDigester = null;
189 
190 
191     /**
192      * <p>The flag to request backwards-compatible conversions for form bean
193      * properties of the Java wrapper class types.</p>
194      *
195      * @since Struts 1.1
196      */
197     protected boolean convertNull = false;
198 
199 
200     /**
201      * <p>The JDBC data sources that has been configured for this module,
202      * if any, keyed by the servlet context attribute under which they are
203      * stored.</p>
204      */
205     protected FastHashMap dataSources = new FastHashMap();
206 
207 
208     /**
209      * <p>The resources object for our internal resources.</p>
210      */
211     protected MessageResources internal = null;
212 
213 
214     /**
215      * <p>The Java base name of our internal resources.</p>
216      * @since Struts 1.1
217      */
218     protected String internalName = "org.apache.struts.action.ActionResources";
219 
220 
221     /**
222      * <p>Commons Logging instance.</p>
223      *
224      * @since Struts 1.1
225      */
226     protected static Log log = LogFactory.getLog(ActionServlet.class);
227 
228 
229     /**
230      * <p>The <code>RequestProcessor</code> instance we will use to process
231      * all incoming requests.</p>
232      *
233      * @since Struts 1.1
234      */
235     protected RequestProcessor processor = null;
236 
237 
238     /**
239      * <p>The set of public identifiers, and corresponding resource names, for
240      * the versions of the configuration file DTDs that we know about.  There
241      * <strong>MUST</strong> be an even number of Strings in this list!</p>
242      */
243     protected String registrations[] = {
244         "-//Apache Software Foundation//DTD Struts Configuration 1.0//EN",
245         "/org/apache/struts/resources/struts-config_1_0.dtd",
246         "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN",
247         "/org/apache/struts/resources/struts-config_1_1.dtd",
248         "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN",
249         "/org/apache/struts/resources/struts-config_1_2.dtd",
250         "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN",
251         "/org/apache/struts/resources/web-app_2_2.dtd",
252         "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN",
253         "/org/apache/struts/resources/web-app_2_3.dtd"
254     };
255 
256 
257     /**
258      * <p>The URL pattern to which we are mapped in our web application
259      * deployment descriptor.</p>
260      */
261     protected String servletMapping = null; // :FIXME: - multiples?
262 
263 
264     /**
265      * <p>The servlet name under which we are registered in our web application
266      * deployment descriptor.</p>
267      */
268     protected String servletName = null;
269 
270 
271     // ---------------------------------------------------- HttpServlet Methods
272 
273 
274     /**
275      * <p>Gracefully shut down this controller servlet, releasing any resources
276      * that were allocated at initialization.</p>
277      */
278     public void destroy() {
279 
280         if (log.isDebugEnabled()) {
281             log.debug(internal.getMessage("finalizing"));
282         }
283 
284         destroyModules();
285         destroyInternal();
286         getServletContext().removeAttribute(Globals.ACTION_SERVLET_KEY);
287 
288         // Release our LogFactory and Log instances (if any)
289         ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
290         if (classLoader == null) {
291             classLoader = ActionServlet.class.getClassLoader();
292         }
293         try {
294             LogFactory.release(classLoader);
295         } catch (Throwable t) {
296             ; // Servlet container doesn't have the latest version
297             ; // of commons-logging-api.jar installed
298 
299             // :FIXME: Why is this dependent on the container's version of commons-logging?
300             // Shouldn't this depend on the version packaged with Struts?
301             /*
302               Reason: LogFactory.release(classLoader); was added as
303               an attempt to investigate the OutOfMemory error reported on Bugzilla #14042.
304               It was committed for version 1.136 by craigmcc
305             */
306         }
307 
308     }
309 
310 
311     /**
312      * <p>Initialize this servlet.  Most of the processing has been factored into
313      * support methods so that you can override particular functionality at a
314      * fairly granular level.</p>
315      *
316      * @exception ServletException if we cannot configure ourselves correctly
317      */
318     public void init() throws ServletException {
319 
320         // Wraps the entire initialization in a try/catch to better handle
321         // unexpected exceptions and errors to provide better feedback
322         // to the developer
323         try {
324             initInternal();
325             initOther();
326             initServlet();
327     
328             getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this);
329             initModuleConfigFactory();
330             // Initialize modules as needed
331             ModuleConfig moduleConfig = initModuleConfig("", config);
332             initModuleMessageResources(moduleConfig);
333             initModuleDataSources(moduleConfig);
334             initModulePlugIns(moduleConfig);
335             moduleConfig.freeze();
336     
337             Enumeration names = getServletConfig().getInitParameterNames();
338             while (names.hasMoreElements()) {
339                 String name = (String) names.nextElement();
340                 if (!name.startsWith("config/")) {
341                     continue;
342                 }
343                 String prefix = name.substring(6);
344                 moduleConfig = initModuleConfig
345                     (prefix, getServletConfig().getInitParameter(name));
346                 initModuleMessageResources(moduleConfig);
347                 initModuleDataSources(moduleConfig);
348                 initModulePlugIns(moduleConfig);
349                 moduleConfig.freeze();
350             }
351     
352             this.initModulePrefixes(this.getServletContext());
353     
354             this.destroyConfigDigester();
355         } catch (UnavailableException ex) {
356             throw ex;
357         } catch (Throwable t) {
358 
359             // The follow error message is not retrieved from internal message
360             // resources as they may not have been able to have been 
361             // initialized
362             log.error("Unable to initialize Struts ActionServlet due to an "
363                 + "unexpected exception or error thrown, so marking the "
364                 + "servlet as unavailable.  Most likely, this is due to an "
365                 + "incorrect or missing library dependency.", t);
366             throw new UnavailableException(t.getMessage());
367         }    
368     }
369 
370     /**
371      * <p>Saves a String[] of module prefixes in the ServletContext under
372      * Globals.MODULE_PREFIXES_KEY.  <strong>NOTE</strong> -
373      * the "" prefix for the default module is not included in this list.</p>
374      *
375      * @param context The servlet context.
376      * @since Struts 1.2
377      */
378     protected void initModulePrefixes(ServletContext context) {
379         ArrayList prefixList = new ArrayList();
380 
381         Enumeration names = context.getAttributeNames();
382         while (names.hasMoreElements()) {
383             String name = (String) names.nextElement();
384             if (!name.startsWith(Globals.MODULE_KEY)) {
385                 continue;
386             }
387 
388             String prefix = name.substring(Globals.MODULE_KEY.length());
389             if (prefix.length() > 0) {
390                 prefixList.add(prefix);
391             }
392         }
393 
394         String[] prefixes = (String[]) prefixList.toArray(new String[prefixList.size()]);
395         context.setAttribute(Globals.MODULE_PREFIXES_KEY, prefixes);
396     }
397 
398 
399     /**
400      * <p>Process an HTTP "GET" request.</p>
401      *
402      * @param request The servlet request we are processing
403      * @param response The servlet response we are creating
404      *
405      * @exception IOException if an input/output error occurs
406      * @exception ServletException if a servlet exception occurs
407      */
408     public void doGet(HttpServletRequest request,
409               HttpServletResponse response)
410         throws IOException, ServletException {
411 
412         process(request, response);
413 
414     }
415 
416 
417     /**
418      * <p>Process an HTTP "POST" request.</p>
419      *
420      * @param request The servlet request we are processing
421      * @param response The servlet response we are creating
422      *
423      * @exception IOException if an input/output error occurs
424      * @exception ServletException if a servlet exception occurs
425      */
426     public void doPost(HttpServletRequest request,
427                HttpServletResponse response)
428         throws IOException, ServletException {
429 
430         process(request, response);
431 
432     }
433 
434 
435     // --------------------------------------------------------- Public Methods
436 
437 
438     /**
439      * <p>Remember a servlet mapping from our web application deployment
440      * descriptor, if it is for this servlet.</p>
441      *
442      * @param servletName The name of the servlet being mapped
443      * @param urlPattern The URL pattern to which this servlet is mapped
444      */
445     public void addServletMapping(String servletName, String urlPattern) {
446 
447         if (log.isDebugEnabled()) {
448             log.debug("Process servletName=" + servletName +
449                       ", urlPattern=" + urlPattern);
450         }
451         if (servletName == null) {
452             return;
453         }
454         if (servletName.equals(this.servletName)) {
455             this.servletMapping = urlPattern;
456         }
457 
458     }
459 
460 
461     /**
462      * <p>Return the <code>MessageResources</code> instance containing our
463      * internal message strings.</p>
464      *
465      * @since Struts 1.1
466      */
467     public MessageResources getInternal() {
468 
469         return (this.internal);
470 
471     }
472 
473 
474     // ------------------------------------------------------ Protected Methods
475 
476     /**
477      * <p>Gracefully terminate use of any modules associated with this
478      * application (if any).</p>
479      *
480      * @since Struts 1.1
481      */
482     protected void destroyModules() {
483 
484         ArrayList values = new ArrayList();
485         Enumeration names = getServletContext().getAttributeNames();
486         while (names.hasMoreElements()) {
487             values.add(names.nextElement());
488         }
489 
490         Iterator keys = values.iterator();
491         while (keys.hasNext()) {
492             String name = (String) keys.next();
493             Object value = getServletContext().getAttribute(name);
494 
495             if (!(value instanceof ModuleConfig)) {
496                 continue;
497             }
498 
499             ModuleConfig config = (ModuleConfig) value;
500 
501             if (this.getProcessorForModule(config) != null) {
502                 this.getProcessorForModule(config).destroy();
503             }
504 
505             getServletContext().removeAttribute(name);
506 
507             PlugIn plugIns[] =
508                 (PlugIn[]) getServletContext().getAttribute(
509                     Globals.PLUG_INS_KEY + config.getPrefix());
510 
511             if (plugIns != null) {
512                 for (int i = 0; i < plugIns.length; i++) {
513                     int j = plugIns.length - (i + 1);
514                     plugIns[j].destroy();
515                 }
516 
517                 getServletContext().removeAttribute(
518                     Globals.PLUG_INS_KEY + config.getPrefix());
519             }
520 
521         }
522 
523     }
524 
525 
526     /**
527      * <p>Gracefully release any configDigester instance that we have created.</p>
528      *
529      * @since Struts 1.1
530      */
531     protected void destroyConfigDigester() {
532 
533         configDigester = null;
534 
535     }
536 
537 
538     /**
539      * <p>Gracefully terminate use of the internal MessageResources.</p>
540      */
541     protected void destroyInternal() {
542 
543         internal = null;
544 
545     }
546 
547     /**
548      * <p>Return the module configuration object for the currently selected
549      * module.</p>
550      *
551      * @param request The servlet request we are processing
552      * @since Struts 1.1
553      */
554     protected ModuleConfig getModuleConfig
555         (HttpServletRequest request) {
556 
557         ModuleConfig config = (ModuleConfig)
558             request.getAttribute(Globals.MODULE_KEY);
559         if (config == null) {
560             config = (ModuleConfig)
561                 getServletContext().getAttribute(Globals.MODULE_KEY);
562         }
563         return (config);
564 
565     }
566 
567 
568     /**
569      * <p>Look up and return the {@link RequestProcessor} responsible for the
570      * specified module, creating a new one if necessary.</p>
571      *
572      * @param config The module configuration for which to
573      *  acquire and return a RequestProcessor.
574      *
575      * @exception ServletException if we cannot instantiate a RequestProcessor
576      *  instance
577      * @since Struts 1.1
578      */
579     protected synchronized RequestProcessor getRequestProcessor(ModuleConfig config)
580         throws ServletException {
581 
582         // :FIXME: Document UnavailableException?
583 
584         RequestProcessor processor = this.getProcessorForModule(config);
585 
586         if (processor == null) {
587             try {
588                 processor =
589                     (RequestProcessor) RequestUtils.applicationInstance(
590                         config.getControllerConfig().getProcessorClass());
591 
592             } catch (Exception e) {
593                 throw new UnavailableException(
594                     "Cannot initialize RequestProcessor of class "
595                         + config.getControllerConfig().getProcessorClass()
596                         + ": "
597                         + e);
598             }
599 
600             processor.init(this, config);
601 
602             String key = Globals.REQUEST_PROCESSOR_KEY + config.getPrefix();
603             getServletContext().setAttribute(key, processor);
604 
605         }
606 
607         return (processor);
608 
609     }
610 
611 
612     /**
613      * <p>Returns the RequestProcessor for the given module or null if one does not
614      * exist.  This method will not create a RequestProcessor.</p>
615      *
616      * @param config The ModuleConfig.
617      */
618     private RequestProcessor getProcessorForModule(ModuleConfig config) {
619         String key = Globals.REQUEST_PROCESSOR_KEY + config.getPrefix();
620         return (RequestProcessor) getServletContext().getAttribute(key);
621     }
622 
623 
624     /**
625      * <p>Initialize the factory used to create the module configuration.</p>
626      * @since Struts 1.2
627      */
628     protected void initModuleConfigFactory(){
629         String configFactory = getServletConfig().getInitParameter("configFactory");
630         if (configFactory != null) {
631             ModuleConfigFactory.setFactoryClass(configFactory);
632         }
633     }
634 
635 
636     /**
637      * <p>Initialize the module configuration information for the
638      * specified module.</p>
639      *
640      * @param prefix Module prefix for this module
641      * @param paths Comma-separated list of context-relative resource path(s)
642      *  for this modules's configuration resource(s)
643      *
644      * @exception ServletException if initialization cannot be performed
645      * @since Struts 1.1
646      */
647     protected ModuleConfig initModuleConfig(String prefix, String paths)
648         throws ServletException {
649 
650         // :FIXME: Document UnavailableException? (Doesn't actually throw anything)
651 
652         if (log.isDebugEnabled()) {
653             log.debug(
654                 "Initializing module path '"
655                     + prefix
656                     + "' configuration from '"
657                     + paths
658                     + "'");
659         }
660 
661         // Parse the configuration for this module
662         ModuleConfigFactory factoryObject = ModuleConfigFactory.createFactory();
663         ModuleConfig config = factoryObject.createModuleConfig(prefix);
664 
665         // Configure the Digester instance we will use
666         Digester digester = initConfigDigester();
667 
668         // Process each specified resource path
669         while (paths.length() > 0) {
670             digester.push(config);
671             String path = null;
672             int comma = paths.indexOf(',');
673             if (comma >= 0) {
674                 path = paths.substring(0, comma).trim();
675                 paths = paths.substring(comma + 1);
676             } else {
677                 path = paths.trim();
678                 paths = "";
679             }
680 
681             if (path.length() < 1) {
682                 break;
683             }
684 
685             this.parseModuleConfigFile(digester, path);
686         }
687 
688         getServletContext().setAttribute(
689             Globals.MODULE_KEY + config.getPrefix(),
690             config);
691 
692         // Force creation and registration of DynaActionFormClass instances
693         // for all dynamic form beans we wil be using
694         FormBeanConfig fbs[] = config.findFormBeanConfigs();
695         for (int i = 0; i < fbs.length; i++) {
696             if (fbs[i].getDynamic()) {
697                 fbs[i].getDynaActionFormClass();
698             }
699         }
700 
701         return config;
702     }
703 
704 
705     /**
706      * <p>Parses one module config file.</p>
707      *
708      * @param digester Digester instance that does the parsing
709      * @param path The path to the config file to parse.
710      *
711      * @throws UnavailableException if file cannot be read or parsed
712      * @since Struts 1.2
713      */
714     protected void parseModuleConfigFile(Digester digester, String path)
715         throws UnavailableException {
716 
717         InputStream input = null;
718         try {
719             URL url = getServletContext().getResource(path);
720 
721             // If the config isn't in the servlet context, try the class loader
722             // which allows the config files to be stored in a jar
723             if (url == null) {
724                 url = getClass().getResource(path);
725             }
726             
727             if (url == null) {
728                 String msg = internal.getMessage("configMissing", path);
729                 log.error(msg);
730                 throw new UnavailableException(msg);
731             }
732       
733             InputSource is = new InputSource(url.toExternalForm());
734             input = url.openStream();
735             is.setByteStream(input);
736             digester.parse(is);
737 
738         } catch (MalformedURLException e) {
739             handleConfigException(path, e);
740         } catch (IOException e) {
741             handleConfigException(path, e);
742         } catch (SAXException e) {
743             handleConfigException(path, e);
744         } finally {
745             if (input != null) {
746                 try {
747                     input.close();
748                 } catch (IOException e) {
749                     throw new UnavailableException(e.getMessage());
750                 }
751             }
752         }
753     }
754 
755 
756     /**
757      * <p>Simplifies exception handling in the <code>parseModuleConfigFile</code> method.<p>
758      * @param path
759      * @param e
760      * @throws UnavailableException as a wrapper around Exception
761      */
762     private void handleConfigException(String path, Exception e)
763         throws UnavailableException {
764 
765         String msg = internal.getMessage("configParse", path);
766         log.error(msg, e);
767         throw new UnavailableException(msg);
768     }
769 
770 
771     /**
772      * <p>Initialize the data sources for the specified module.</p>
773      *
774      * @param config ModuleConfig information for this module
775      *
776      * @exception ServletException if initialization cannot be performed
777      * @since Struts 1.1
778      */
779     protected void initModuleDataSources(ModuleConfig config) throws ServletException {
780 
781         // :FIXME: Document UnavailableException?
782 
783         if (log.isDebugEnabled()) {
784             log.debug("Initializing module path '" + config.getPrefix() +
785                 "' data sources");
786         }
787 
788         ServletContextWriter scw =
789             new ServletContextWriter(getServletContext());
790         DataSourceConfig dscs[] = config.findDataSourceConfigs();
791         if (dscs == null) {
792             dscs = new DataSourceConfig[0];
793         }
794 
795         dataSources.setFast(false);
796         for (int i = 0; i < dscs.length; i++) {
797             if (log.isDebugEnabled()) {
798                 log.debug("Initializing module path '" + config.getPrefix() +
799                     "' data source '" + dscs[i].getKey() + "'");
800             }
801             DataSource ds = null;
802             try {
803                 ds = (DataSource)
804                     RequestUtils.applicationInstance(dscs[i].getType());
805                 BeanUtils.populate(ds, dscs[i].getProperties());
806                 ds.setLogWriter(scw);
807 
808             } catch (Exception e) {
809                 log.error(internal.getMessage("dataSource.init", dscs[i].getKey()), e);
810                 throw new UnavailableException
811                     (internal.getMessage("dataSource.init", dscs[i].getKey()));
812             }
813             getServletContext().setAttribute
814                 (dscs[i].getKey() + config.getPrefix(), ds);
815             dataSources.put(dscs[i].getKey(), ds);
816         }
817 
818         dataSources.setFast(true);
819 
820     }
821 
822 
823     /**
824      * <p>Initialize the plug ins for the specified module.</p>
825      *
826      * @param config ModuleConfig information for this module
827      *
828      * @exception ServletException if initialization cannot be performed
829      * @since Struts 1.1
830      */
831     protected void initModulePlugIns
832         (ModuleConfig config) throws ServletException {
833 
834         if (log.isDebugEnabled()) {
835             log.debug("Initializing module path '" + config.getPrefix() + "' plug ins");
836         }
837 
838         PlugInConfig plugInConfigs[] = config.findPlugInConfigs();
839         PlugIn plugIns[] = new PlugIn[plugInConfigs.length];
840 
841         getServletContext().setAttribute(Globals.PLUG_INS_KEY + config.getPrefix(), plugIns);
842         for (int i = 0; i < plugIns.length; i++) {
843             try {
844                 plugIns[i] =
845                     (PlugIn)RequestUtils.applicationInstance(plugInConfigs[i].getClassName());
846                  BeanUtils.populate(plugIns[i], plugInConfigs[i].getProperties());
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(
852                         plugIns[i],
853                         "currentPlugInConfigObject",
854                         plugInConfigs[i]);
855                 } catch (Exception e) {
856                   // FIXME Whenever we fail silently, we must document a valid reason
857                   // for doing so.  Why should we fail silently if a property can't be set on
858                   // the plugin?
859                     /**
860                      * Between version 1.138-1.140 cedric made these changes.
861                      * The exceptions are caught to deal with containers applying strict security.
862                      * This was in response to bug #15736
863                      *
864                      * Recommend that we make the currentPlugInConfigObject part of the PlugIn Interface if we can, Rob
865                      */
866                 }
867                 plugIns[i].init(this, config);
868 
869             } catch (ServletException e) {
870                 throw e;
871             } catch (Exception e) {
872                 String errMsg =
873                     internal.getMessage(
874                         "plugIn.init",
875                         plugInConfigs[i].getClassName());
876 
877                 log(errMsg, e);
878                 throw new UnavailableException(errMsg);
879             }
880         }
881 
882     }
883 
884 
885     /**
886      * <p>Initialize the application <code>MessageResources</code> for the specified
887      * module.</p>
888      *
889      * @param config ModuleConfig information for this module
890      *
891      * @exception ServletException if initialization cannot be performed
892      * @since Struts 1.1
893      */
894     protected void initModuleMessageResources(ModuleConfig config)
895         throws ServletException {
896 
897         MessageResourcesConfig mrcs[] = config.findMessageResourcesConfigs();
898         for (int i = 0; i < mrcs.length; i++) {
899             if ((mrcs[i].getFactory() == null)
900                 || (mrcs[i].getParameter() == null)) {
901                 continue;
902             }
903             if (log.isDebugEnabled()) {
904                 log.debug(
905                     "Initializing module path '"
906                         + config.getPrefix()
907                         + "' message resources from '"
908                         + mrcs[i].getParameter()
909                         + "'");
910             }
911 
912             String factory = mrcs[i].getFactory();
913             MessageResourcesFactory.setFactoryClass(factory);
914             MessageResourcesFactory factoryObject =
915                 MessageResourcesFactory.createFactory();
916 
917             MessageResources resources =
918                 factoryObject.createResources(mrcs[i].getParameter());
919             resources.setReturnNull(mrcs[i].getNull());
920             getServletContext().setAttribute(
921                 mrcs[i].getKey() + config.getPrefix(),
922                 resources);
923         }
924 
925     }
926 
927 
928     /**
929      * <p>Create (if needed) and return a new <code>Digester</code>
930      * instance that has been initialized to process Struts module
931      * configuration files and configure a corresponding <code>ModuleConfig</code>
932      * object (which must be pushed on to the evaluation stack before parsing
933      * begins).</p>
934      *
935      * @exception ServletException if a Digester cannot be configured
936      * @since Struts 1.1
937      */
938     protected Digester initConfigDigester() throws ServletException {
939 
940         // :FIXME: Where can ServletException be thrown?
941 
942         // Do we have an existing instance?
943         if (configDigester != null) {
944             return (configDigester);
945         }
946 
947         // Create a new Digester instance with standard capabilities
948         configDigester = new Digester();
949         configDigester.setNamespaceAware(true);
950         configDigester.setValidating(this.isValidating());
951         configDigester.setUseContextClassLoader(true);
952         configDigester.addRuleSet(new ConfigRuleSet());
953 
954         for (int i = 0; i < registrations.length; i += 2) {
955             URL url = this.getClass().getResource(registrations[i+1]);
956             if (url != null) {
957                 configDigester.register(registrations[i], url.toString());
958             }
959         }
960 
961         this.addRuleSets();
962 
963         // Return the completely configured Digester instance
964         return (configDigester);
965     }
966 
967 
968     /**
969      * <p>Add any custom RuleSet instances to configDigester that have
970      * been specified in the <code>rulesets</code> init parameter.</p>
971      *
972      * @throws ServletException
973      */
974     private void addRuleSets() throws ServletException {
975 
976         String rulesets = getServletConfig().getInitParameter("rulesets");
977         if (rulesets == null) {
978             rulesets = "";
979         }
980 
981         rulesets = rulesets.trim();
982         String ruleset = null;
983         while (rulesets.length() > 0) {
984             int comma = rulesets.indexOf(",");
985             if (comma < 0) {
986                 ruleset = rulesets.trim();
987                 rulesets = "";
988             } else {
989                 ruleset = rulesets.substring(0, comma).trim();
990                 rulesets = rulesets.substring(comma + 1).trim();
991             }
992 
993             if (log.isDebugEnabled()) {
994                 log.debug("Configuring custom Digester Ruleset of type " + ruleset);
995             }
996 
997             try {
998                 RuleSet instance = (RuleSet) RequestUtils.applicationInstance(ruleset);
999                 this.configDigester.addRuleSet(instance);
1000            } catch (Exception e) {
1001                log.error("Exception configuring custom Digester RuleSet", e);
1002                throw new ServletException(e);
1003            }
1004        }
1005    }
1006
1007
1008    /**
1009     * <p>Check the status of the <code>validating</code> initialization parameter.</p>
1010     *
1011     * @return true if the module Digester should validate.
1012     */
1013    private boolean isValidating() {
1014
1015        boolean validating = true;
1016        String value = getServletConfig().getInitParameter("validating");
1017
1018        if ("false".equalsIgnoreCase(value)
1019            || "no".equalsIgnoreCase(value)
1020            || "n".equalsIgnoreCase(value)
1021            || "0".equalsIgnoreCase(value)) {
1022
1023            validating = false;
1024        }
1025
1026        return validating;
1027    }
1028
1029
1030
1031    /**
1032     * <p>Initialize our internal MessageResources bundle.</p>
1033     *
1034     * @exception ServletException if we cannot initialize these resources
1035     */
1036    protected void initInternal() throws ServletException {
1037
1038        // :FIXME: Document UnavailableException
1039
1040        try {
1041            internal = MessageResources.getMessageResources(internalName);
1042        } catch (MissingResourceException e) {
1043            log.error("Cannot load internal resources from '" + internalName + "'",
1044                e);
1045            throw new UnavailableException
1046                ("Cannot load internal resources from '" + internalName + "'");
1047        }
1048
1049    }
1050
1051
1052    /**
1053     * <p>Initialize other global characteristics of the controller servlet.</p>
1054     *
1055     * @exception ServletException if we cannot initialize these resources
1056     */
1057    protected void initOther() throws ServletException {
1058
1059        String value = null;
1060        value = getServletConfig().getInitParameter("config");
1061        if (value != null) {
1062            config = value;
1063        }
1064
1065        // Backwards compatibility for form beans of Java wrapper classes
1066        // Set to true for strict Struts 1.0 compatibility
1067        value = getServletConfig().getInitParameter("convertNull");
1068        if ("true".equalsIgnoreCase(value)
1069            || "yes".equalsIgnoreCase(value)
1070            || "on".equalsIgnoreCase(value)
1071            || "y".equalsIgnoreCase(value)
1072            || "1".equalsIgnoreCase(value)) {
1073
1074            convertNull = true;
1075        }
1076
1077        if (convertNull) {
1078            ConvertUtils.deregister();
1079            ConvertUtils.register(new BigDecimalConverter(null), BigDecimal.class);
1080            ConvertUtils.register(new BigIntegerConverter(null), BigInteger.class);
1081            ConvertUtils.register(new BooleanConverter(null), Boolean.class);
1082            ConvertUtils.register(new ByteConverter(null), Byte.class);
1083            ConvertUtils.register(new CharacterConverter(null), Character.class);
1084            ConvertUtils.register(new DoubleConverter(null), Double.class);
1085            ConvertUtils.register(new FloatConverter(null), Float.class);
1086            ConvertUtils.register(new IntegerConverter(null), Integer.class);
1087            ConvertUtils.register(new LongConverter(null), Long.class);
1088            ConvertUtils.register(new ShortConverter(null), Short.class);
1089        }
1090
1091    }
1092
1093
1094    /**
1095     * <p>Initialize the servlet mapping under which our controller servlet
1096     * is being accessed.  This will be used in the <code>&html:form&gt;</code>
1097     * tag to generate correct destination URLs for form submissions.</p>
1098     *
1099     * @throws ServletException if error happens while scanning web.xml
1100     */
1101    protected void initServlet() throws ServletException {
1102
1103        // Remember our servlet name
1104        this.servletName = getServletConfig().getServletName();
1105
1106        // Prepare a Digester to scan the web application deployment descriptor
1107        Digester digester = new Digester();
1108        digester.push(this);
1109        digester.setNamespaceAware(true);
1110        digester.setValidating(false);
1111
1112        // Register our local copy of the DTDs that we can find
1113        for (int i = 0; i < registrations.length; i += 2) {
1114            URL url = this.getClass().getResource(registrations[i+1]);
1115            if (url != null) {
1116                digester.register(registrations[i], url.toString());
1117            }
1118        }
1119
1120        // Configure the processing rules that we need
1121        digester.addCallMethod("web-app/servlet-mapping",
1122                               "addServletMapping", 2);
1123        digester.addCallParam("web-app/servlet-mapping/servlet-name", 0);
1124        digester.addCallParam("web-app/servlet-mapping/url-pattern", 1);
1125
1126        // Process the web application deployment descriptor
1127        if (log.isDebugEnabled()) {
1128            log.debug("Scanning web.xml for controller servlet mapping");
1129        }
1130
1131        InputStream input =
1132            getServletContext().getResourceAsStream("/WEB-INF/web.xml");
1133
1134        if (input == null) {
1135            log.error(internal.getMessage("configWebXml"));
1136            throw new ServletException(internal.getMessage("configWebXml"));
1137        }
1138
1139        try {
1140            digester.parse(input);
1141
1142        } catch (IOException e) {
1143            log.error(internal.getMessage("configWebXml"), e);
1144            throw new ServletException(e);
1145
1146        } catch (SAXException e) {
1147            log.error(internal.getMessage("configWebXml"), e);
1148            throw new ServletException(e);
1149
1150        } finally {
1151            try {
1152                input.close();
1153            } catch (IOException e) {
1154                log.error(internal.getMessage("configWebXml"), e);
1155                throw new ServletException(e);
1156            }
1157        }
1158
1159        // Record a servlet context attribute (if appropriate)
1160        if (log.isDebugEnabled()) {
1161            log.debug("Mapping for servlet '" + servletName + "' = '" +
1162                servletMapping + "'");
1163        }
1164
1165        if (servletMapping != null) {
1166            getServletContext().setAttribute(Globals.SERVLET_KEY, servletMapping);
1167        }
1168
1169    }
1170
1171
1172    /**
1173     * <p>Perform the standard request processing for this request, and create
1174     * the corresponding response.</p>
1175     *
1176     * @param request The servlet request we are processing
1177     * @param response The servlet response we are creating
1178     *
1179     * @exception IOException if an input/output error occurs
1180     * @exception ServletException if a servlet exception is thrown
1181     */
1182    protected void process(HttpServletRequest request, HttpServletResponse response)
1183        throws IOException, ServletException {
1184
1185        ModuleUtils.getInstance().selectModule(request, getServletContext());
1186        ModuleConfig config = getModuleConfig(request);
1187
1188        RequestProcessor processor = getProcessorForModule(config);
1189        if (processor == null) {
1190           processor = getRequestProcessor(config);
1191        }
1192        processor.process(request, response);
1193
1194    }
1195
1196}