View Javadoc

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