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><action></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 }