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