View Javadoc

1   /*
2    * $Id: ActionConfig.java 421119 2006-07-12 04:49:11Z wsmoak $
3    *
4    * Copyright 1999-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  package org.apache.struts.config;
19  
20  import org.apache.commons.beanutils.BeanUtils;
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.apache.struts.util.RequestUtils;
24  
25  import java.lang.reflect.InvocationTargetException;
26  
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  
30  /***
31   * <p>A JavaBean representing the configuration information of an
32   * <code>&lt;action&gt;</code> element from a Struts module configuration
33   * file.</p>
34   *
35   * @version $Rev: 421119 $ $Date: 2005-08-06 04:12:10 -0400 (Sat, 06 Aug 2005)
36   *          $
37   * @since Struts 1.1
38   */
39  public class ActionConfig extends BaseConfig {
40      private static final Log log = LogFactory.getLog(ActionConfig.class);
41  
42      // ----------------------------------------------------- Instance Variables
43  
44      /***
45       * <p> The set of exception handling configurations for this action, if
46       * any, keyed by the <code>type</code> property. </p>
47       */
48      protected HashMap exceptions = new HashMap();
49  
50      /***
51       * <p> The set of local forward configurations for this action, if any,
52       * keyed by the <code>name</code> property. </p>
53       */
54      protected HashMap forwards = new HashMap();
55  
56      // ------------------------------------------------------------- Properties
57  
58      /***
59       * <p> The module configuration with which we are associated. </p>
60       */
61      protected ModuleConfig moduleConfig = null;
62  
63      /***
64       * <p> The request-scope or session-scope attribute name under which our
65       * form bean is accessed, if it is different from the form bean's
66       * specified <code>name</code>. </p>
67       */
68      protected String attribute = null;
69  
70      /***
71       * <p>The path of the ActionConfig that this object should inherit
72       * properties from.</p> </p>
73       */
74      protected String inherit = null;
75  
76      /***
77       * <p>Can this Action be cancelled? [false]</p> <p> By default, when an
78       * Action is cancelled, validation is bypassed and the Action should not
79       * execute the business operation. If a request tries to cancel an Action
80       * when cancellable is not set, a "InvalidCancelException" is thrown.
81       * </p>
82       */
83      protected boolean cancellable = false;
84  
85      /***
86       * <p> Have the inheritance values for this class been applied?</p>
87       */
88      protected boolean extensionProcessed = false;
89  
90      /***
91       * <p> Context-relative path of the web application resource that will
92       * process this request via RequestDispatcher.forward(), instead of
93       * instantiating and calling the <code>Action</code> class specified by
94       * "type". Exactly one of <code>forward</code>, <code>include</code>, or
95       * <code>type</code> must be specified. </p>
96       */
97      protected String forward = null;
98  
99      /***
100      * <p> Context-relative path of the web application resource that will
101      * process this request via RequestDispatcher.include(), instead of
102      * instantiating and calling the <code>Action</code> class specified by
103      * "type". Exactly one of <code>forward</code>, <code>include</code>, or
104      * <code>type</code> must be specified. </p>
105      */
106     protected String include = null;
107 
108     /***
109      * <p> Context-relative path of the input form to which control should be
110      * returned if a validation error is encountered.  Required if "name" is
111      * specified and the input bean returns validation errors. </p>
112      */
113     protected String input = null;
114 
115     /***
116      * <p> Fully qualified Java class name of the <code>MultipartRequestHandler</code>
117      * implementation class used to process multi-part request data for this
118      * Action. </p>
119      */
120     protected String multipartClass = null;
121 
122     /***
123      * <p> Name of the form bean, if any, associated with this Action. </p>
124      */
125     protected String name = null;
126 
127     /***
128      * <p> General purpose configuration parameter that can be used to pass
129      * extra information to the Action instance selected by this Action.
130      * Struts does not itself use this value in any way. </p>
131      */
132     protected String parameter = null;
133 
134     /***
135      * <p> Context-relative path of the submitted request, starting with a
136      * slash ("/") character, and omitting any filename extension if extension
137      * mapping is being used. </p>
138      */
139     protected String path = null;
140 
141     /***
142      * <p> Prefix used to match request parameter names to form bean property
143      * names, if any. </p>
144      */
145     protected String prefix = null;
146 
147     /***
148      * <p> Comma-delimited list of security role names allowed to request this
149      * Action. </p>
150      */
151     protected String roles = null;
152 
153     /***
154      * <p> The set of security role names used to authorize access to this
155      * Action, as an array for faster access. </p>
156      */
157     protected String[] roleNames = new String[0];
158 
159     /***
160      * <p> Identifier of the scope ("request" or "session") within which our
161      * form bean is accessed, if any. </p>
162      */
163     protected String scope = "session";
164 
165     /***
166      * <p> Suffix used to match request parameter names to form bean property
167      * names, if any. </p>
168      */
169     protected String suffix = null;
170 
171     /***
172      * <p> Fully qualified Java class name of the <code>Action</code> class to
173      * be used to process requests for this mapping if the
174      * <code>forward</code> and <code>include</code> properties are not set.
175      * Exactly one of <code>forward</code>, <code>include</code>, or
176      * <code>type</code> must be specified.
177      */
178     protected String type = null;
179 
180     /***
181      * <p> Indicates Action be configured as the default one for this module,
182      * when true.
183      */
184     protected boolean unknown = false;
185 
186     /***
187      * <p> Should the <code>validate()</code> method of the form bean
188      * associated with this action be called?
189      */
190     protected boolean validate = true;
191 
192     /***
193      * <p> The name of a <code>commons-chain</code> command which should be
194      * executed as part of the processing of this action.
195      *
196      * @since Struts 1.3.0
197      */
198     protected String command = null;
199 
200     /***
201      * <p> The name of a <code>commons-chain</code> catalog in which
202      * <code>command</code> should be sought.  If a <code>command</code> is
203      * defined and this property is undefined, the "default" catalog will be
204      * used. This is likely to be infrequently used after a future release of
205      * <code>commons-chain</code> supports a one-string expression of a
206      * catalog/chain combination.
207      *
208      * @since Struts 1.3.0
209      */
210     protected String catalog = null;
211 
212     /***
213      * <p> The module configuration with which we are associated.
214      */
215     public ModuleConfig getModuleConfig() {
216         return (this.moduleConfig);
217     }
218 
219     /***
220      * <p> The module configuration with which we are associated.
221      */
222     public void setModuleConfig(ModuleConfig moduleConfig) {
223         if (configured) {
224             throw new IllegalStateException("Configuration is frozen");
225         }
226 
227         this.moduleConfig = moduleConfig;
228     }
229 
230     /***
231      * <p> Returns the request-scope or session-scope attribute name under
232      * which our form bean is accessed, if it is different from the form
233      * bean's specified <code>name</code>.
234      *
235      * @return attribute name under which our form bean is accessed.
236      */
237     public String getAttribute() {
238         if (this.attribute == null) {
239             return (this.name);
240         } else {
241             return (this.attribute);
242         }
243     }
244 
245     /***
246      * <p> Set the request-scope or session-scope attribute name under which
247      * our form bean is accessed, if it is different from the form bean's
248      * specified <code>name</code>.
249      *
250      * @param attribute the request-scope or session-scope attribute name
251      *                  under which our form bean is access.
252      */
253     public void setAttribute(String attribute) {
254         if (configured) {
255             throw new IllegalStateException("Configuration is frozen");
256         }
257 
258         this.attribute = attribute;
259     }
260 
261     /***
262      * <p>Accessor for cancellable property</p>
263      *
264      * @return True if Action can be cancelled
265      */
266     public boolean getCancellable() {
267         return (this.cancellable);
268     }
269 
270     /***
271      * <p>Mutator for for cancellable property</p>
272      *
273      * @param cancellable
274      */
275     public void setCancellable(boolean cancellable) {
276         if (configured) {
277             throw new IllegalStateException("Configuration is frozen");
278         }
279 
280         this.cancellable = cancellable;
281     }
282 
283     /***
284      * <p>Returns the path of the ActionConfig that this object should inherit
285      * properties from.</p>
286      *
287      * @return the path of the ActionConfig that this object should inherit
288      *         properties from.
289      */
290     public String getExtends() {
291         return (this.inherit);
292     }
293 
294     /***
295      * <p>Set the path of the ActionConfig that this object should inherit
296      * properties from.</p>
297      *
298      * @param inherit the path of the ActionConfig that this object should
299      *                inherit properties from.
300      */
301     public void setExtends(String inherit) {
302         if (configured) {
303             throw new IllegalStateException("Configuration is frozen");
304         }
305 
306         this.inherit = inherit;
307     }
308 
309     public boolean isExtensionProcessed() {
310         return extensionProcessed;
311     }
312 
313     /***
314      * <p> Returns context-relative path of the web application resource that
315      * will process this request.
316      *
317      * @return context-relative path of the web application resource that will
318      *         process this request.
319      */
320     public String getForward() {
321         return (this.forward);
322     }
323 
324     /***
325      * <p> Set the context-relative path of the web application resource that
326      * will process this request. Exactly one of <code>forward</code>,
327      * <code>include</code>, or <code>type</code> must be specified.
328      *
329      * @param forward context-relative path of the web application resource
330      *                that will process this request.
331      */
332     public void setForward(String forward) {
333         if (configured) {
334             throw new IllegalStateException("Configuration is frozen");
335         }
336 
337         this.forward = forward;
338     }
339 
340     /***
341      * <p> Context-relative path of the web application resource that will
342      * process this request.
343      *
344      * @return Context-relative path of the web application resource that will
345      *         process this request.
346      */
347     public String getInclude() {
348         return (this.include);
349     }
350 
351     /***
352      * <p> Set context-relative path of the web application resource that will
353      * process this request. Exactly one of <code>forward</code>,
354      * <code>include</code>, or <code>type</code> must be specified.
355      *
356      * @param include context-relative path of the web application resource
357      *                that will process this request.
358      */
359     public void setInclude(String include) {
360         if (configured) {
361             throw new IllegalStateException("Configuration is frozen");
362         }
363 
364         this.include = include;
365     }
366 
367     /***
368      * <p> Get the context-relative path of the input form to which control
369      * should be returned if a validation error is encountered.
370      *
371      * @return context-relative path of the input form to which control should
372      *         be returned if a validation error is encountered.
373      */
374     public String getInput() {
375         return (this.input);
376     }
377 
378     /***
379      * <p> Set the context-relative path of the input form to which control
380      * should be returned if a validation error is encountered.  Required if
381      * "name" is specified and the input bean returns validation errors.
382      *
383      * @param input context-relative path of the input form to which control
384      *              should be returned if a validation error is encountered.
385      */
386     public void setInput(String input) {
387         if (configured) {
388             throw new IllegalStateException("Configuration is frozen");
389         }
390 
391         this.input = input;
392     }
393 
394     /***
395      * <p> Return the fully qualified Java class name of the
396      * <code>MultipartRequestHandler</code> implementation class used to
397      * process multi-part request data for this Action.
398      */
399     public String getMultipartClass() {
400         return (this.multipartClass);
401     }
402 
403     /***
404      * <p> Set the fully qualified Java class name of the
405      * <code>MultipartRequestHandler</code> implementation class used to
406      * process multi-part request data for this Action.
407      *
408      * @param multipartClass fully qualified class name of the
409      *                       <code>MultipartRequestHandler</code>
410      *                       implementation class.
411      */
412     public void setMultipartClass(String multipartClass) {
413         if (configured) {
414             throw new IllegalStateException("Configuration is frozen");
415         }
416 
417         this.multipartClass = multipartClass;
418     }
419 
420     /***
421      * <p> Return name of the form bean, if any, associated with this Action.
422      */
423     public String getName() {
424         return (this.name);
425     }
426 
427     /***
428      * @param name name of the form bean associated with this Action.
429      */
430     public void setName(String name) {
431         if (configured) {
432             throw new IllegalStateException("Configuration is frozen");
433         }
434 
435         this.name = name;
436     }
437 
438     /***
439      * <p> Return general purpose configuration parameter that can be used to
440      * pass extra information to the Action instance selected by this Action.
441      * Struts does not itself use this value in any way.
442      */
443     public String getParameter() {
444         return (this.parameter);
445     }
446 
447     /***
448      * <p> General purpose configuration parameter that can be used to pass
449      * extra information to the Action instance selected by this Action.
450      * Struts does not itself use this value in any way.
451      *
452      * @param parameter General purpose configuration parameter.
453      */
454     public void setParameter(String parameter) {
455         if (configured) {
456             throw new IllegalStateException("Configuration is frozen");
457         }
458 
459         this.parameter = parameter;
460     }
461 
462     /***
463      * <p> Return context-relative path of the submitted request, starting
464      * with a slash ("/") character, and omitting any filename extension if
465      * extension mapping is being used.
466      */
467     public String getPath() {
468         return (this.path);
469     }
470 
471     /***
472      * <p> Set context-relative path of the submitted request, starting with a
473      * slash ("/") character, and omitting any filename extension if extension
474      * mapping is being used.
475      *
476      * @param path context-relative path of the submitted request.
477      */
478     public void setPath(String path) {
479         if (configured) {
480             throw new IllegalStateException("Configuration is frozen");
481         }
482 
483         this.path = path;
484     }
485 
486     /***
487      * <p> Retruns prefix used to match request parameter names to form bean
488      * property names, if any.
489      */
490     public String getPrefix() {
491         return (this.prefix);
492     }
493 
494     /***
495      * @param prefix Prefix used to match request parameter names to form bean
496      *               property names, if any.
497      */
498     public void setPrefix(String prefix) {
499         if (configured) {
500             throw new IllegalStateException("Configuration is frozen");
501         }
502 
503         this.prefix = prefix;
504     }
505 
506     public String getRoles() {
507         return (this.roles);
508     }
509 
510     public void setRoles(String roles) {
511         if (configured) {
512             throw new IllegalStateException("Configuration is frozen");
513         }
514 
515         this.roles = roles;
516 
517         if (roles == null) {
518             roleNames = new String[0];
519 
520             return;
521         }
522 
523         ArrayList list = new ArrayList();
524 
525         while (true) {
526             int comma = roles.indexOf(',');
527 
528             if (comma < 0) {
529                 break;
530             }
531 
532             list.add(roles.substring(0, comma).trim());
533             roles = roles.substring(comma + 1);
534         }
535 
536         roles = roles.trim();
537 
538         if (roles.length() > 0) {
539             list.add(roles);
540         }
541 
542         roleNames = (String[]) list.toArray(new String[list.size()]);
543     }
544 
545     /***
546      * <p> Get array of security role names used to authorize access to this
547      * Action.
548      */
549     public String[] getRoleNames() {
550         return (this.roleNames);
551     }
552 
553     /***
554      * <p> Get the scope ("request" or "session") within which our form bean
555      * is accessed, if any.
556      */
557     public String getScope() {
558         return (this.scope);
559     }
560 
561     /***
562      * @param scope scope ("request" or "session") within which our form bean
563      *              is accessed, if any.
564      */
565     public void setScope(String scope) {
566         if (configured) {
567             throw new IllegalStateException("Configuration is frozen");
568         }
569 
570         this.scope = scope;
571     }
572 
573     /***
574      * <p> Return suffix used to match request parameter names to form bean
575      * property names, if any. </p>
576      */
577     public String getSuffix() {
578         return (this.suffix);
579     }
580 
581     /***
582      * @param suffix Suffix used to match request parameter names to form bean
583      *               property names, if any.
584      */
585     public void setSuffix(String suffix) {
586         if (configured) {
587             throw new IllegalStateException("Configuration is frozen");
588         }
589 
590         this.suffix = suffix;
591     }
592 
593     public String getType() {
594         return (this.type);
595     }
596 
597     public void setType(String type) {
598         if (configured) {
599             throw new IllegalStateException("Configuration is frozen");
600         }
601 
602         this.type = type;
603     }
604 
605     /***
606      * <p> Determine whether Action is configured as the default one for this
607      * module. </p>
608      */
609     public boolean getUnknown() {
610         return (this.unknown);
611     }
612 
613     /***
614      * @param unknown Indicates Action is configured as the default one for
615      *                this module, when true.
616      */
617     public void setUnknown(boolean unknown) {
618         if (configured) {
619             throw new IllegalStateException("Configuration is frozen");
620         }
621 
622         this.unknown = unknown;
623     }
624 
625     public boolean getValidate() {
626         return (this.validate);
627     }
628 
629     public void setValidate(boolean validate) {
630         if (configured) {
631             throw new IllegalStateException("Configuration is frozen");
632         }
633 
634         this.validate = validate;
635     }
636 
637     /***
638      * <p> Get the name of a <code>commons-chain</code> command which should
639      * be executed as part of the processing of this action. </p>
640      *
641      * @return name of a <code>commons-chain</code> command which should be
642      *         executed as part of the processing of this action.
643      * @since Struts 1.3.0
644      */
645     public String getCommand() {
646         return (this.command);
647     }
648 
649     /***
650      * <p> Get the name of a <code>commons-chain</code> catalog in which a
651      * specified command should be sought.  This is likely to be infrequently
652      * used after a future release of <code>commons-chain</code> supports a
653      * one-string expression of a catalog/chain combination. </p>
654      *
655      * @return name of a <code>commons-chain</code> catalog in which a
656      *         specified command should be sought.
657      * @since Struts 1.3.0
658      */
659     public String getCatalog() {
660         return (this.catalog);
661     }
662 
663     /***
664      * <p> Set the name of a <code>commons-chain</code> command which should
665      * be executed as part of the processing of this action. </p>
666      *
667      * @param command name of a <code>commons-chain</code> command which
668      *                should be executed as part of the processing of this
669      *                action.
670      * @since Struts 1.3.0
671      */
672     public void setCommand(String command) {
673         if (configured) {
674             throw new IllegalStateException("Configuration is frozen");
675         }
676 
677         this.command = command;
678     }
679 
680     /***
681      * <p> Set the name of a <code>commons-chain</code> catalog in which a
682      * specified command should be sought. This is likely to be infrequently
683      * used after a future release of <code>commons-chain</code> supports a
684      * one-string expression of a catalog/chain combination. </p>
685      *
686      * @param catalog name of a <code>commons-chain</code> catalog in which a
687      *                specified command should be sought.
688      * @since Struts 1.3.0
689      */
690     public void setCatalog(String catalog) {
691         if (configured) {
692             throw new IllegalStateException("Configuration is frozen");
693         }
694 
695         this.catalog = catalog;
696     }
697 
698     // ------------------------------------------------------ Protected Methods
699 
700     /***
701      * <p>Traces the hierarchy of this object to check if any of the ancestors
702      * is extending this instance.</p>
703      *
704      * @param moduleConfig The configuration for the module being configured.
705      * @return true if circular inheritance was detected.
706      */
707     protected boolean checkCircularInheritance(ModuleConfig moduleConfig) {
708         String ancestorPath = getExtends();
709 
710         while (ancestorPath != null) {
711             // check if we have the same path as an ancestor
712             if (getPath().equals(ancestorPath)) {
713                 return true;
714             }
715 
716             // get our ancestor's ancestor
717             ActionConfig ancestor = moduleConfig.findActionConfig(ancestorPath);
718 
719             if (ancestor != null) {
720                 ancestorPath = ancestor.getExtends();
721             } else {
722                 ancestorPath = null;
723             }
724         }
725 
726         return false;
727     }
728 
729     /***
730      * <p>Compare the exception handlers of this action with that of the given
731      * and copy those that are not present.</p>
732      *
733      * @param baseConfig The action config to copy handlers from.
734      * @see #inheritFrom(ActionConfig)
735      */
736     protected void inheritExceptionHandlers(ActionConfig baseConfig)
737         throws ClassNotFoundException, IllegalAccessException,
738             InstantiationException, InvocationTargetException {
739         if (configured) {
740             throw new IllegalStateException("Configuration is frozen");
741         }
742 
743         // Inherit exception handler configs
744         ExceptionConfig[] baseHandlers = baseConfig.findExceptionConfigs();
745 
746         for (int i = 0; i < baseHandlers.length; i++) {
747             ExceptionConfig baseHandler = baseHandlers[i];
748 
749             // Do we have this handler?
750             ExceptionConfig copy =
751                 this.findExceptionConfig(baseHandler.getType());
752 
753             if (copy == null) {
754                 // We don't have this, so let's copy it
755                 copy =
756                     (ExceptionConfig) RequestUtils.applicationInstance(baseHandler.getClass()
757                                                                                   .getName());
758 
759                 BeanUtils.copyProperties(copy, baseHandler);
760                 this.addExceptionConfig(copy);
761                 copy.setProperties(baseHandler.copyProperties());
762             } else {
763                 // process any extension that this config might have
764                 copy.processExtends(getModuleConfig(), this);
765             }
766         }
767     }
768 
769     /***
770      * <p>Compare the forwards of this action with that of the given and copy
771      * those that are not present.</p>
772      *
773      * @param baseConfig The action config to copy forwards from.
774      * @see #inheritFrom(ActionConfig)
775      */
776     protected void inheritForwards(ActionConfig baseConfig)
777         throws ClassNotFoundException, IllegalAccessException,
778             InstantiationException, InvocationTargetException {
779         if (configured) {
780             throw new IllegalStateException("Configuration is frozen");
781         }
782 
783         // Inherit forward configs
784         ForwardConfig[] baseForwards = baseConfig.findForwardConfigs();
785 
786         for (int i = 0; i < baseForwards.length; i++) {
787             ForwardConfig baseForward = baseForwards[i];
788 
789             // Do we have this forward?
790             ForwardConfig copy = this.findForwardConfig(baseForward.getName());
791 
792             if (copy == null) {
793                 // We don't have this, so let's copy it
794                 copy =
795                     (ForwardConfig) RequestUtils.applicationInstance(baseForward.getClass()
796                                                                                 .getName());
797                 BeanUtils.copyProperties(copy, baseForward);
798 
799                 this.addForwardConfig(copy);
800                 copy.setProperties(baseForward.copyProperties());
801             } else {
802                 // process any extension for this forward
803                 copy.processExtends(getModuleConfig(), this);
804             }
805         }
806     }
807 
808     // --------------------------------------------------------- Public Methods
809 
810     /***
811      * <p> Add a new <code>ExceptionConfig</code> instance to the set
812      * associated with this action. </p>
813      *
814      * @param config The new configuration instance to be added
815      * @throws IllegalStateException if this module configuration has been
816      *                               frozen
817      */
818     public void addExceptionConfig(ExceptionConfig config) {
819         if (configured) {
820             throw new IllegalStateException("Configuration is frozen");
821         }
822 
823         exceptions.put(config.getType(), config);
824     }
825 
826     /***
827      * <p> Add a new <code>ForwardConfig</code> instance to the set of global
828      * forwards associated with this action. </p>
829      *
830      * @param config The new configuration instance to be added
831      * @throws IllegalStateException if this module configuration has been
832      *                               frozen
833      */
834     public void addForwardConfig(ForwardConfig config) {
835         if (configured) {
836             throw new IllegalStateException("Configuration is frozen");
837         }
838 
839         forwards.put(config.getName(), config);
840     }
841 
842     /***
843      * <p> Return the exception configuration for the specified type, if any;
844      * otherwise return <code>null</code>. </p>
845      *
846      * @param type Exception class name to find a configuration for
847      */
848     public ExceptionConfig findExceptionConfig(String type) {
849         return ((ExceptionConfig) exceptions.get(type));
850     }
851 
852     /***
853      * <p> Return the exception configurations for this action.  If there are
854      * none, a zero-length array is returned. </p>
855      */
856     public ExceptionConfig[] findExceptionConfigs() {
857         ExceptionConfig[] results = new ExceptionConfig[exceptions.size()];
858 
859         return ((ExceptionConfig[]) exceptions.values().toArray(results));
860     }
861 
862     /***
863      * <p>Find and return the <code>ExceptionConfig</code> instance defining
864      * how <code>Exceptions</code> of the specified type should be handled.
865      * This is performed by checking local and then global configurations for
866      * the specified exception's class, and then looking up the superclass
867      * chain (again checking local and then global configurations). If no
868      * handler configuration can be found, return <code>null</code>.</p>
869      *
870      * <p>Introduced in <code>ActionMapping</code> in Struts 1.1, but pushed
871      * up to <code>ActionConfig</code> in Struts 1.2.0.</p>
872      *
873      * @param type Exception class for which to find a handler
874      * @since Struts 1.2.0
875      */
876     public ExceptionConfig findException(Class type) {
877         // Check through the entire superclass hierarchy as needed
878         ExceptionConfig config;
879 
880         while (true) {
881             // Check for a locally defined handler
882             String name = type.getName();
883 
884             log.debug("findException: look locally for " + name);
885             config = findExceptionConfig(name);
886 
887             if (config != null) {
888                 return (config);
889             }
890 
891             // Check for a globally defined handler
892             log.debug("findException: look globally for " + name);
893             config = getModuleConfig().findExceptionConfig(name);
894 
895             if (config != null) {
896                 return (config);
897             }
898 
899             // Loop again for our superclass (if any)
900             type = type.getSuperclass();
901 
902             if (type == null) {
903                 break;
904             }
905         }
906 
907         return (null); // No handler has been configured
908     }
909 
910     /***
911      * <p> Return the forward configuration for the specified key, if any;
912      * otherwise return <code>null</code>. </p>
913      *
914      * @param name Name of the forward configuration to return
915      */
916     public ForwardConfig findForwardConfig(String name) {
917         return ((ForwardConfig) forwards.get(name));
918     }
919 
920     /***
921      * <p> Return all forward configurations for this module.  If there are
922      * none, a zero-length array is returned. </p>
923      */
924     public ForwardConfig[] findForwardConfigs() {
925         ForwardConfig[] results = new ForwardConfig[forwards.size()];
926 
927         return ((ForwardConfig[]) forwards.values().toArray(results));
928     }
929 
930     /***
931      * <p> Freeze the configuration of this action. </p>
932      */
933     public void freeze() {
934         super.freeze();
935 
936         ExceptionConfig[] econfigs = findExceptionConfigs();
937 
938         for (int i = 0; i < econfigs.length; i++) {
939             econfigs[i].freeze();
940         }
941 
942         ForwardConfig[] fconfigs = findForwardConfigs();
943 
944         for (int i = 0; i < fconfigs.length; i++) {
945             fconfigs[i].freeze();
946         }
947     }
948 
949     /***
950      * <p>Inherit values that have not been overridden from the provided
951      * config object.  Subclasses overriding this method should verify that
952      * the given parameter is of a class that contains a property it is trying
953      * to inherit:</p>
954      *
955      * <pre>
956      * if (config instanceof MyCustomConfig) {
957      *     MyCustomConfig myConfig =
958      *         (MyCustomConfig) config;
959      *
960      *     if (getMyCustomProp() == null) {
961      *         setMyCustomProp(myConfig.getMyCustomProp());
962      *     }
963      * }
964      * </pre>
965      *
966      * <p>If the given <code>config</code> is extending another object, those
967      * extensions should be resolved before it's used as a parameter to this
968      * method.</p>
969      *
970      * @param config The object that this instance will be inheriting its
971      *               values from.
972      * @see #processExtends(ModuleConfig)
973      */
974     public void inheritFrom(ActionConfig config)
975         throws ClassNotFoundException, IllegalAccessException,
976             InstantiationException, InvocationTargetException {
977         if (configured) {
978             throw new IllegalStateException("Configuration is frozen");
979         }
980 
981         // Inherit values that have not been overridden
982         if (getAttribute() == null) {
983             setAttribute(config.getAttribute());
984         }
985 
986         if (!getCancellable()) {
987             setCancellable(config.getCancellable());
988         }
989 
990         if (getCatalog() == null) {
991             setCatalog(config.getCatalog());
992         }
993 
994         if (getCommand() == null) {
995             setCommand(config.getCommand());
996         }
997 
998         if (getForward() == null) {
999             setForward(config.getForward());
1000         }
1001 
1002         if (getInclude() == null) {
1003             setInclude(config.getInclude());
1004         }
1005 
1006         if (getInput() == null) {
1007             setInput(config.getInput());
1008         }
1009 
1010         if (getMultipartClass() == null) {
1011             setMultipartClass(config.getMultipartClass());
1012         }
1013 
1014         if (getName() == null) {
1015             setName(config.getName());
1016         }
1017 
1018         if (getParameter() == null) {
1019             setParameter(config.getParameter());
1020         }
1021 
1022         if (getPath() == null) {
1023             setPath(config.getPath());
1024         }
1025 
1026         if (getPrefix() == null) {
1027             setPrefix(config.getPrefix());
1028         }
1029 
1030         if (getRoles() == null) {
1031             setRoles(config.getRoles());
1032         }
1033 
1034         if (getScope().equals("session")) {
1035             setScope(config.getScope());
1036         }
1037 
1038         if (getSuffix() == null) {
1039             setSuffix(config.getSuffix());
1040         }
1041 
1042         if (getType() == null) {
1043             setType(config.getType());
1044         }
1045 
1046         if (!getUnknown()) {
1047             setUnknown(config.getUnknown());
1048         }
1049 
1050         if (getValidate()) {
1051             setValidate(config.getValidate());
1052         }
1053 
1054         inheritExceptionHandlers(config);
1055         inheritForwards(config);
1056         inheritProperties(config);
1057     }
1058 
1059     /***
1060      * <p>Inherit configuration information from the ActionConfig that this
1061      * instance is extending.  This method verifies that any action config
1062      * object that it inherits from has also had its processExtends() method
1063      * called.</p>
1064      *
1065      * @param moduleConfig The {@link ModuleConfig} that this bean is from.
1066      * @see #inheritFrom(ActionConfig)
1067      */
1068     public void processExtends(ModuleConfig moduleConfig)
1069         throws ClassNotFoundException, IllegalAccessException,
1070             InstantiationException, InvocationTargetException {
1071         if (configured) {
1072             throw new IllegalStateException("Configuration is frozen");
1073         }
1074 
1075         String ancestorPath = getExtends();
1076 
1077         if ((!extensionProcessed) && (ancestorPath != null)) {
1078             ActionConfig baseConfig =
1079                 moduleConfig.findActionConfig(ancestorPath);
1080 
1081             if (baseConfig == null) {
1082                 throw new NullPointerException("Unable to find "
1083                     + "action for '" + ancestorPath + "' to extend.");
1084             }
1085 
1086             // Check against circular inheritance and make sure the base
1087             //  config's own extends has been processed already
1088             if (checkCircularInheritance(moduleConfig)) {
1089                 throw new IllegalArgumentException(
1090                     "Circular inheritance detected for action " + getPath());
1091             }
1092 
1093             // Make sure the ancestor's own extension has been processed.
1094             if (!baseConfig.isExtensionProcessed()) {
1095                 baseConfig.processExtends(moduleConfig);
1096             }
1097 
1098             // Copy values from the base config
1099             inheritFrom(baseConfig);
1100         }
1101 
1102         extensionProcessed = true;
1103     }
1104 
1105     /***
1106      * <p> Remove the specified exception configuration instance. </p>
1107      *
1108      * @param config ExceptionConfig instance to be removed
1109      * @throws IllegalStateException if this module configuration has been
1110      *                               frozen
1111      */
1112     public void removeExceptionConfig(ExceptionConfig config) {
1113         if (configured) {
1114             throw new IllegalStateException("Configuration is frozen");
1115         }
1116 
1117         exceptions.remove(config.getType());
1118     }
1119 
1120     /***
1121      * <p> Remove the specified forward configuration instance. </p>
1122      *
1123      * @param config ForwardConfig instance to be removed
1124      * @throws IllegalStateException if this module configuration has been
1125      *                               frozen
1126      */
1127     public void removeForwardConfig(ForwardConfig config) {
1128         if (configured) {
1129             throw new IllegalStateException("Configuration is frozen");
1130         }
1131 
1132         forwards.remove(config.getName());
1133     }
1134 
1135     /***
1136      * <p> Return a String representation of this object. </p>
1137      */
1138     public String toString() {
1139         StringBuffer sb = new StringBuffer("ActionConfig[");
1140 
1141         sb.append("cancellable=");
1142         sb.append(cancellable);
1143 
1144         sb.append("path=");
1145         sb.append(path);
1146 
1147         sb.append("validate=");
1148         sb.append(validate);
1149 
1150         if (attribute != null) {
1151             sb.append(",attribute=");
1152             sb.append(attribute);
1153         }
1154 
1155         if (catalog != null) {
1156             sb.append(",catalog=");
1157             sb.append(catalog);
1158         }
1159 
1160         if (command != null) {
1161             sb.append(",command=");
1162             sb.append(command);
1163         }
1164 
1165         if (inherit != null) {
1166             sb.append(",extends=");
1167             sb.append(inherit);
1168         }
1169 
1170         if (forward != null) {
1171             sb.append(",forward=");
1172             sb.append(forward);
1173         }
1174 
1175         if (include != null) {
1176             sb.append(",include=");
1177             sb.append(include);
1178         }
1179 
1180         if (input != null) {
1181             sb.append(",input=");
1182             sb.append(input);
1183         }
1184 
1185         if (multipartClass != null) {
1186             sb.append(",multipartClass=");
1187             sb.append(multipartClass);
1188         }
1189 
1190         if (name != null) {
1191             sb.append(",name=");
1192             sb.append(name);
1193         }
1194 
1195         if (parameter != null) {
1196             sb.append(",parameter=");
1197             sb.append(parameter);
1198         }
1199 
1200         if (prefix != null) {
1201             sb.append(",prefix=");
1202             sb.append(prefix);
1203         }
1204 
1205         if (roles != null) {
1206             sb.append(",roles=");
1207             sb.append(roles);
1208         }
1209 
1210         if (scope != null) {
1211             sb.append(",scope=");
1212             sb.append(scope);
1213         }
1214 
1215         if (suffix != null) {
1216             sb.append(",suffix=");
1217             sb.append(suffix);
1218         }
1219 
1220         if (type != null) {
1221             sb.append(",type=");
1222             sb.append(type);
1223         }
1224 
1225         return (sb.toString());
1226     }
1227 }