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.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 }