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