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.impl;
22
23 import java.io.Serializable;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.Map;
27 import java.util.TreeMap;
28
29 import org.apache.struts.Constants;
30 import org.apache.struts.config.ActionConfig;
31 import org.apache.struts.config.ActionConfigMatcher;
32 import org.apache.struts.config.BaseConfig;
33 import org.apache.struts.config.ControllerConfig;
34 import org.apache.struts.config.ExceptionConfig;
35 import org.apache.struts.config.FormBeanConfig;
36 import org.apache.struts.config.ForwardConfig;
37 import org.apache.struts.config.MessageResourcesConfig;
38 import org.apache.struts.config.ModuleConfig;
39 import org.apache.struts.config.PlugInConfig;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 /**
44 * <p>The collection of static configuration information that describes a
45 * Struts-based module. Multiple modules are identified by a <em>prefix</em>
46 * at the beginning of the context relative portion of the request URI. If no
47 * module prefix can be matched, the default configuration (with a prefix
48 * equal to a zero-length string) is selected, which is elegantly backwards
49 * compatible with the previous Struts behavior that only supported one
50 * module.</p>
51 *
52 * @version $Rev$ $Date: 2005-12-31 03:57:16 -0500 (Sat, 31 Dec 2005)
53 * $
54 * @since Struts 1.1
55 */
56 public class ModuleConfigImpl extends BaseConfig implements Serializable,
57 ModuleConfig {
58
59 private static final long serialVersionUID = -5742785805411686899L;
60
61 /**
62 * The {@code Log} instance for this class.
63 */
64 private transient final Logger log =
65 LoggerFactory.getLogger(ModuleConfigImpl.class);
66
67 // ----------------------------------------------------- Instance Variables
68 // Instance Variables at end to make comparing Interface and implementation easier.
69
70 /**
71 * <p>The set of action configurations for this module, if any, keyed by
72 * the <code>path</code> property.</p>
73 */
74 protected Map<String, ActionConfig> actionConfigs = null;
75
76 /**
77 * <p>The set of action configuration for this module, if any, keyed by
78 * the <code>actionId</code> property.</p>
79 */
80 protected HashMap<String, ActionConfig> actionConfigIds = null;
81
82 /**
83 * <p>The set of action configurations for this module, if any, listed in
84 * the order in which they are added.</p>
85 */
86 protected ArrayList<ActionConfig> actionConfigList = null;
87
88 /**
89 * <p>The set of exception handling configurations for this module, if
90 * any, keyed by the <code>type</code> property.</p>
91 */
92 protected HashMap<String, ExceptionConfig> exceptions = null;
93
94 /**
95 * <p>The set of form bean configurations for this module, if any, keyed
96 * by the <code>name</code> property.</p>
97 */
98 protected HashMap<String, FormBeanConfig> formBeans = null;
99
100 /**
101 * <p>The set of global forward configurations for this module, if any,
102 * keyed by the <code>name</code> property.</p>
103 */
104 protected HashMap<String, ForwardConfig> forwards = null;
105
106 /**
107 * <p>The set of message resources configurations for this module, if any,
108 * keyed by the <code>key</code> property.</p>
109 */
110 protected HashMap<String, MessageResourcesConfig> messageResources = null;
111
112 /**
113 * <p>The set of configured plug-in Actions for this module, if any, in
114 * the order they were declared and configured.</p>
115 */
116 protected ArrayList<PlugInConfig> plugIns = null;
117
118 /**
119 * <p>The controller configuration object for this module.</p>
120 */
121 protected ControllerConfig controllerConfig = null;
122
123 /**
124 * <p>The prefix of the context-relative portion of the request URI, used
125 * to select this configuration versus others supported by the controller
126 * servlet. A configuration with a prefix of a zero-length String is the
127 * default configuration for this web module.</p>
128 */
129 protected String prefix = null;
130
131 /**
132 * <p>The default class name to be used when creating action form bean
133 * instances.</p>
134 */
135 protected String actionFormBeanClass =
136 "org.apache.struts.action.ActionFormBean";
137
138 /**
139 * The default class name to be used when creating action mapping
140 * instances.
141 */
142 protected String actionMappingClass =
143 "org.apache.struts.action.ActionMapping";
144
145 /**
146 * The default class name to be used when creating action forward
147 * instances.
148 */
149 protected String actionForwardClass =
150 "org.apache.struts.action.ActionForward";
151
152 /**
153 * <p>Matches action config paths against compiled wildcard patterns</p>
154 */
155 protected ActionConfigMatcher matcher = null;
156
157 /**
158 * <p>Constructor for ModuleConfigImpl. Assumes default
159 * configuration.</p>
160 *
161 * @since Struts 1.2.8
162 */
163 public ModuleConfigImpl() {
164 this("");
165 }
166
167 /**
168 * <p>Construct an ModuleConfigImpl object according to the specified
169 * parameter values.</p>
170 *
171 * @param prefix Context-relative URI prefix for this module
172 */
173 public ModuleConfigImpl(String prefix) {
174 super();
175 this.prefix = prefix;
176 this.actionConfigs = new HashMap<>();
177 this.actionConfigIds = new HashMap<>();
178 this.actionConfigList = new ArrayList<>();
179 this.actionFormBeanClass = "org.apache.struts.action.ActionFormBean";
180 this.actionMappingClass = "org.apache.struts.action.ActionMapping";
181 this.actionForwardClass = "org.apache.struts.action.ActionForward";
182 this.configured = false;
183 this.controllerConfig = null;
184 this.exceptions = new HashMap<>();
185 this.formBeans = new HashMap<>();
186 this.forwards = new HashMap<>();
187 this.messageResources = new HashMap<>();
188 this.plugIns = new ArrayList<>();
189 }
190
191 // --------------------------------------------------------- Public Methods
192
193 /**
194 * </p> Has this module been completely configured yet. Once this flag
195 * has been set, any attempt to modify the configuration will return an
196 * IllegalStateException.</p>
197 */
198 public boolean getConfigured() {
199 return (this.configured);
200 }
201
202 /**
203 * <p>The controller configuration object for this module.</p>
204 */
205 public ControllerConfig getControllerConfig() {
206 if (this.controllerConfig == null) {
207 this.controllerConfig = new ControllerConfig();
208 }
209
210 return (this.controllerConfig);
211 }
212
213 /**
214 * <p>The controller configuration object for this module.</p>
215 *
216 * @param cc The controller configuration object for this module.
217 */
218 public void setControllerConfig(ControllerConfig cc) {
219 throwIfConfigured();
220 this.controllerConfig = cc;
221 }
222
223 /**
224 * <p>The prefix of the context-relative portion of the request URI, used
225 * to select this configuration versus others supported by the controller
226 * servlet. A configuration with a prefix of a zero-length String is the
227 * default configuration for this web module.</p>
228 */
229 public String getPrefix() {
230 return (this.prefix);
231 }
232
233 /**
234 * <p>The prefix of the context-relative portion of the request URI, used
235 * to select this configuration versus others supported by the controller
236 * servlet. A configuration with a prefix of a zero-length String is the
237 * default configuration for this web module.</p>
238 */
239 public void setPrefix(String prefix) {
240 throwIfConfigured();
241 this.prefix = prefix;
242 }
243
244 /**
245 * <p>The default class name to be used when creating action form bean
246 * instances.</p>
247 */
248 public String getActionFormBeanClass() {
249 return this.actionFormBeanClass;
250 }
251
252 /**
253 * <p>The default class name to be used when creating action form bean
254 * instances.</p>
255 *
256 * @param actionFormBeanClass default class name to be used when creating
257 * action form bean instances.
258 */
259 public void setActionFormBeanClass(String actionFormBeanClass) {
260 this.actionFormBeanClass = actionFormBeanClass;
261 }
262
263 /**
264 * <p>The default class name to be used when creating action mapping
265 * instances.</p>
266 */
267 public String getActionMappingClass() {
268 return this.actionMappingClass;
269 }
270
271 /**
272 * <p> The default class name to be used when creating action mapping
273 * instances. </p>
274 *
275 * @param actionMappingClass default class name to be used when creating
276 * action mapping instances.
277 */
278 public void setActionMappingClass(String actionMappingClass) {
279 this.actionMappingClass = actionMappingClass;
280 }
281
282 /**
283 * </p> Ad d a new <code>ActionConfig</code> instance to the set
284 * associated with this module. </p>
285 *
286 * @param config The new configuration instance to be added
287 * @throws IllegalStateException if this module configuration has been
288 * frozen
289 */
290 public void addActionConfig(ActionConfig config) {
291 throwIfConfigured();
292 config.setModuleConfig(this);
293
294 String path = config.getPath();
295 if (actionConfigs.containsKey(path)) {
296 log.warn("Overriding ActionConfig of path {}", path);
297 }
298
299 String actionId = config.getActionId();
300 if ((actionId != null) && !actionId.equals("")) {
301 if (actionConfigIds.containsKey(actionId)) {
302 log.atWarn()
303 .setMessage("Overriding actionId[{}] for path[{}] with path[{}]")
304 .addArgument(actionId)
305 .addArgument(() -> actionConfigIds.get(actionId).getPath())
306 .addArgument(path)
307 .log();
308 }
309 actionConfigIds.put(actionId, config);
310 }
311
312 actionConfigs.put(path, config);
313 actionConfigList.add(config);
314 }
315
316 /**
317 * <p> Add a new <code>ExceptionConfig</code> instance to the set
318 * associated with this module. </p>
319 *
320 * @param config The new configuration instance to be added
321 * @throws IllegalStateException if this module configuration has been
322 * frozen
323 */
324 public void addExceptionConfig(ExceptionConfig config) {
325 throwIfConfigured();
326
327 String key = config.getType();
328
329 if (exceptions.containsKey(key)) {
330 log.warn("Overriding ExceptionConfig of type {}", key);
331 }
332
333 exceptions.put(key, config);
334 }
335
336 /**
337 * <p> Add a new <code>FormBeanConfig</code> instance to the set
338 * associated with this module. </p>
339 *
340 * @param config The new configuration instance to be added
341 * @throws IllegalStateException if this module configuration has been
342 * frozen
343 */
344 public void addFormBeanConfig(FormBeanConfig config) {
345 throwIfConfigured();
346
347 String key = config.getName();
348
349 if (formBeans.containsKey(key)) {
350 log.warn("Overriding ActionForm of name {}", key);
351 }
352
353 formBeans.put(key, config);
354 }
355
356 /**
357 * <p> The default class name to be used when creating action forward
358 * instances. </p>
359 */
360 public String getActionForwardClass() {
361 return this.actionForwardClass;
362 }
363
364 /**
365 * <p> The default class name to be used when creating action forward
366 * instances. </p>
367 *
368 * @param actionForwardClass default class name to be used when creating
369 * action forward instances.
370 */
371 public void setActionForwardClass(String actionForwardClass) {
372 this.actionForwardClass = actionForwardClass;
373 }
374
375 /**
376 * <p> Add a new <code>ForwardConfig</code> instance to the set of global
377 * forwards associated with this module. </p>
378 *
379 * @param config The new configuration instance to be added
380 * @throws IllegalStateException if this module configuration has been
381 * frozen
382 */
383 public void addForwardConfig(ForwardConfig config) {
384 throwIfConfigured();
385
386 String key = config.getName();
387
388 if (forwards.containsKey(key)) {
389 log.warn("Overriding global ActionForward of name {}", key);
390 }
391
392 forwards.put(key, config);
393 }
394
395 /**
396 * <p> Add a new <code>MessageResourcesConfig</code> instance to the set
397 * associated with this module. </p>
398 *
399 * @param config The new configuration instance to be added
400 * @throws IllegalStateException if this module configuration has been
401 * frozen
402 */
403 public void addMessageResourcesConfig(MessageResourcesConfig config) {
404 throwIfConfigured();
405
406 String key = config.getKey();
407
408 if (messageResources.containsKey(key)) {
409 log.warn("Overriding MessageResources bundle of key {}", key);
410 }
411
412 messageResources.put(key, config);
413 }
414
415 /**
416 * <p> Add a newly configured {@link org.apache.struts.config.PlugInConfig}
417 * instance to the set of plug-in Actions for this module. </p>
418 *
419 * @param plugInConfig The new configuration instance to be added
420 */
421 public void addPlugInConfig(PlugInConfig plugInConfig) {
422 throwIfConfigured();
423 plugIns.add(plugInConfig);
424 }
425
426 /**
427 * <p> Return the action configuration for the specified path, first
428 * looking a direct match, then if none found, a wildcard pattern match;
429 * otherwise return <code>null</code>. </p>
430 *
431 * @param path Path of the action configuration to return
432 */
433 public ActionConfig findActionConfig(String path) {
434 ActionConfig config = actionConfigs.get(path);
435
436 // If a direct match cannot be found, try to match action configs
437 // containing wildcard patterns only if a matcher exists.
438 if ((config == null) && (matcher != null)) {
439 config = matcher.match(path);
440 }
441
442 return config;
443 }
444
445 /**
446 * <p>Returns the action configuration for the specifed action
447 * action identifier.</p>
448 *
449 * @param actionId the action identifier
450 * @return the action config if found; otherwise <code>null</code>
451 * @see ActionConfig#getActionId()
452 * @since Struts 1.3.6
453 */
454 public ActionConfig findActionConfigId(String actionId) {
455 if (actionId != null) {
456 return this.actionConfigIds.get(actionId);
457 }
458 return null;
459 }
460
461 /**
462 * <p> Return the action configurations for this module. If there are
463 * none, a zero-length array is returned. </p>
464 */
465 public ActionConfig[] findActionConfigs() {
466 ActionConfig[] results = new ActionConfig[actionConfigList.size()];
467
468 return (actionConfigList.toArray(results));
469 }
470
471 /**
472 * <p> Return the exception configuration for the specified type, if any;
473 * otherwise return <code>null</code>. </p>
474 *
475 * @param type Exception class name to find a configuration for
476 */
477 public ExceptionConfig findExceptionConfig(String type) {
478 return (exceptions.get(type));
479 }
480
481 /**
482 * <p>Find and return the <code>ExceptionConfig</code> instance defining
483 * how <code>Exceptions</code> of the specified type should be handled.
484 *
485 * <p>In original Struts usage, this was only available in
486 * <code>ActionConfig</code>, but there are cases when an exception could
487 * be thrown before an <code>ActionConfig</code> has been identified,
488 * where global exception handlers may still be pertinent.</p>
489 *
490 * <p>TODO: Look for a way to share this logic with
491 * <code>ActionConfig</code>, although there are subtle differences, and
492 * it certainly doesn't seem like it should be done with inheritance.</p>
493 *
494 * @param type Exception class for which to find a handler
495 * @since Struts 1.3.0
496 */
497 public ExceptionConfig findException(Class<?> type) {
498 // Check through the entire superclass hierarchy as needed
499 ExceptionConfig config = null;
500
501 while (true) {
502 // Check for a locally defined handler
503 String name = type.getName();
504
505 log.debug("findException: look locally for {}", name);
506 config = findExceptionConfig(name);
507
508 if (config != null) {
509 return (config);
510 }
511
512 // Loop again for our superclass (if any)
513 type = type.getSuperclass();
514
515 if (type == null) {
516 break;
517 }
518 }
519
520 return (null); // No handler has been configured
521 }
522
523 /**
524 * <p> Return the exception configurations for this module. If there are
525 * none, a zero-length array is returned. </p>
526 */
527 public ExceptionConfig[] findExceptionConfigs() {
528 ExceptionConfig[] results = new ExceptionConfig[exceptions.size()];
529
530 return (exceptions.values().toArray(results));
531 }
532
533 /**
534 * <p> Return the form bean configuration for the specified key, if any;
535 * otherwise return <code>null</code>. </p>
536 *
537 * @param name Name of the form bean configuration to return
538 */
539 public FormBeanConfig findFormBeanConfig(String name) {
540 return (formBeans.get(name));
541 }
542
543 /**
544 * <p> Return the form bean configurations for this module. If there are
545 * none, a zero-length array is returned. </p>
546 */
547 public FormBeanConfig[] findFormBeanConfigs() {
548 FormBeanConfig[] results = new FormBeanConfig[formBeans.size()];
549
550 return (formBeans.values().toArray(results));
551 }
552
553 /**
554 * <p> Return the forward configuration for the specified key, if any;
555 * otherwise return <code>null</code>. </p>
556 *
557 * @param name Name of the forward configuration to return
558 */
559 public ForwardConfig findForwardConfig(String name) {
560 return (forwards.get(name));
561 }
562
563 /**
564 * <p> Return the form bean configurations for this module. If there are
565 * none, a zero-length array is returned. </p>
566 */
567 public ForwardConfig[] findForwardConfigs() {
568 ForwardConfig[] results = new ForwardConfig[forwards.size()];
569
570 return (forwards.values().toArray(results));
571 }
572
573 /**
574 * <p> Return the message resources configuration for the specified key,
575 * if any; otherwise return <code>null</code>. </p>
576 *
577 * @param key Key of the data source configuration to return
578 */
579 public MessageResourcesConfig findMessageResourcesConfig(String key) {
580 return (messageResources.get(key));
581 }
582
583 /**
584 * <p> Return the message resources configurations for this module. If
585 * there are none, a zero-length array is returned. </p>
586 */
587 public MessageResourcesConfig[] findMessageResourcesConfigs() {
588 MessageResourcesConfig[] results =
589 new MessageResourcesConfig[messageResources.size()];
590
591 return (messageResources.values().toArray(results));
592 }
593
594 /**
595 * <p> Return the configured plug-in actions for this module. If there
596 * are none, a zero-length array is returned. </p>
597 */
598 public PlugInConfig[] findPlugInConfigs() {
599 PlugInConfig[] results = new PlugInConfig[plugIns.size()];
600
601 return (plugIns.toArray(results));
602 }
603
604 /**
605 * <p> Freeze the configuration of this module. After this method
606 * returns, any attempt to modify the configuration will return an
607 * IllegalStateException. </p>
608 */
609 public void freeze() {
610 super.freeze();
611
612 ActionConfig[] aconfigs = findActionConfigs();
613
614 for (int i = 0; i < aconfigs.length; i++) {
615 aconfigs[i].freeze();
616 }
617
618 matcher = new ActionConfigMatcher(aconfigs);
619
620 getControllerConfig().freeze();
621
622 ExceptionConfig[] econfigs = findExceptionConfigs();
623
624 for (int i = 0; i < econfigs.length; i++) {
625 econfigs[i].freeze();
626 }
627
628 FormBeanConfig[] fbconfigs = findFormBeanConfigs();
629
630 for (int i = 0; i < fbconfigs.length; i++) {
631 fbconfigs[i].freeze();
632 }
633
634 ForwardConfig[] fconfigs = findForwardConfigs();
635
636 for (int i = 0; i < fconfigs.length; i++) {
637 fconfigs[i].freeze();
638 }
639
640 MessageResourcesConfig[] mrconfigs = findMessageResourcesConfigs();
641
642 for (int i = 0; i < mrconfigs.length; i++) {
643 mrconfigs[i].freeze();
644 }
645
646 PlugInConfig[] piconfigs = findPlugInConfigs();
647
648 for (int i = 0; i < piconfigs.length; i++) {
649 piconfigs[i].freeze();
650 }
651 }
652
653 /**
654 * <p> Remove the specified action configuration instance. </p>
655 *
656 * @param config ActionConfig instance to be removed
657 * @throws IllegalStateException if this module configuration has been
658 * frozen
659 */
660 public void removeActionConfig(ActionConfig config) {
661 throwIfConfigured();
662 config.setModuleConfig(null);
663 actionConfigs.remove(config.getPath());
664 actionConfigList.remove(config);
665 }
666
667 /**
668 * <p> Remove the specified exception configuration instance. </p>
669 *
670 * @param config ActionConfig instance to be removed
671 * @throws IllegalStateException if this module configuration has been
672 * frozen
673 */
674 public void removeExceptionConfig(ExceptionConfig config) {
675 throwIfConfigured();
676 exceptions.remove(config.getType());
677 }
678
679 /**
680 * <p> Remove the specified form bean configuration instance. </p>
681 *
682 * @param config FormBeanConfig instance to be removed
683 * @throws IllegalStateException if this module configuration has been
684 * frozen
685 */
686 public void removeFormBeanConfig(FormBeanConfig config) {
687 throwIfConfigured();
688 formBeans.remove(config.getName());
689 }
690
691 /**
692 * <p> Remove the specified forward configuration instance. </p>
693 *
694 * @param config ForwardConfig instance to be removed
695 * @throws IllegalStateException if this module configuration has been
696 * frozen
697 */
698 public void removeForwardConfig(ForwardConfig config) {
699 throwIfConfigured();
700 forwards.remove(config.getName());
701 }
702
703 /**
704 * <p> Remove the specified message resources configuration instance.
705 * </p>
706 *
707 * @param config MessageResourcesConfig instance to be removed
708 * @throws IllegalStateException if this module configuration has been
709 * frozen
710 */
711 public void removeMessageResourcesConfig(MessageResourcesConfig config) {
712 throwIfConfigured();
713 messageResources.remove(config.getKey());
714 }
715
716 public void setProperty(String key, String value) {
717 super.setProperty(key, value);
718
719 if (Constants.STRUTS_URL_CASESENSITIVE.equals(key)) {
720 Map<String, ActionConfig> actionConfigs2;
721 if (!Boolean.valueOf(value).booleanValue()) {
722 actionConfigs2 = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
723 } else {
724 actionConfigs2 = new HashMap<>();
725 }
726 actionConfigs2.putAll(actionConfigs);
727 actionConfigs = actionConfigs2;
728 }
729 }
730 }