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