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><html:form></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 }