View Javadoc
1   /*
2    * $Id$
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  package org.apache.struts.config;
22  
23  import java.lang.reflect.InvocationTargetException;
24  
25  /**
26   * <p>A JavaBean representing the configuration information of a
27   * <code>&lt;forward&gt;</code> element from a Struts configuration file.</p>
28   *
29   * @version $Rev$ $Date: 2005-08-14 17:24:39 -0400 (Sun, 14 Aug 2005)
30   *          $
31   * @since Struts 1.1
32   */
33  public class ForwardConfig extends BaseConfig {
34      private static final long serialVersionUID = -3983809829201419914L;
35  
36      // ------------------------------------------------------------- Properties
37  
38      /**
39       * The name of the ForwardConfig that this object should inherit
40       * properties from.
41       */
42      protected String inherit = null;
43  
44      /**
45       * Have the inheritance values for this class been applied?
46       */
47      protected boolean extensionProcessed = false;
48  
49      /**
50       * The unique identifier of this forward, which is used to reference it in
51       * <code>Action</code> classes.
52       */
53      protected String name = null;
54  
55      /**
56       * <p>The URL to which this <code>ForwardConfig</code> entry points, which
57       * must start with a slash ("/") character.  It is interpreted according
58       * to the following rules:</p>
59       *
60       * <ul>
61       *
62       * <li>If <code>contextRelative</code> property is <code>true</code>, the
63       * path is considered to be context-relative within the current web
64       * application (even if we are in a named module).  It will be prefixed by
65       * the context path to create a server-relative URL.</li>
66       *
67       * <li>If the <code>contextRelative</code> property is false, the path is
68       * considered to be the module-relative portion of the URL. It will be
69       * used as the replacement for the <code>$P</code> marker in the
70       * <code>forwardPattern</code> property defined on the {@link
71       * ControllerConfig} element for our current module. For the default
72       * <code>forwardPattern</code> value of <code>$C$M$P</code>, the resulting
73       * server-relative URL will be the concatenation of the context path, the
74       * module prefix, and the <code>path</code> from this
75       * <code>ForwardConfig</code>.
76       *
77       * </li>
78       *
79       * </ul>
80       */
81      protected String path = null;
82  
83      /**
84       * <p>The prefix of the module to which this <code>ForwardConfig</code>
85       * entry points, which must start with a slash ("/") character.  </p>
86       * <p>Usage note: If a forward config is used in a hyperlink, and a module
87       * is specified, the path must lead to another action and not directly to
88       * a page. This is in keeping with rule that in a modular application all
89       * links must be to an action rather than a page. </p>
90       */
91      protected String module = null;
92  
93      /**
94       * Should a redirect be used to transfer control to the specified path?
95       */
96      protected boolean redirect = false;
97  
98      /**
99       * <p>The name of a <code>commons-chain</code> command which should be
100      * looked up and executed before Struts dispatches control to the view
101      * represented by this config.</p>
102      */
103     protected String command = null;
104 
105     /**
106      * <p>The name of a <code>commons-chain</code> catalog in which
107      * <code>command</code> should be looked up.  If this value is undefined,
108      * then the command will be looked up in the "default" catalog.  This
109      * value has no meaning except in the context of the <code>command</code>
110      * property.</p>
111      */
112     protected String catalog = null;
113 
114     // ----------------------------------------------------------- Constructors
115 
116     /**
117      * Construct a new instance with default values.
118      */
119     public ForwardConfig() {
120         super();
121     }
122 
123     /**
124      * Construct a new instance with the specified values.
125      *
126      * @param name     Name of this forward
127      * @param path     Path to which control should be forwarded or
128      *                 redirected
129      * @param redirect Should we do a redirect?
130      */
131     public ForwardConfig(String name, String path, boolean redirect) {
132         super();
133         setName(name);
134         setPath(path);
135         setRedirect(redirect);
136     }
137 
138     /**
139      * <p>Construct a new instance with the specified values.</p>
140      *
141      * @param name     Name of this forward
142      * @param path     Path to which control should be forwarded or
143      *                 redirected
144      * @param redirect Should we do a redirect?
145      * @param module   Module prefix, if any
146      */
147     public ForwardConfig(String name, String path, boolean redirect,
148         String module) {
149         super();
150         setName(name);
151         setPath(path);
152         setRedirect(redirect);
153         setModule(module);
154     }
155 
156     /**
157      * <p>Construct a new instance based on the values of another
158      * ForwardConfig.</p>
159      *
160      * @param copyMe A ForwardConfig instance to copy
161      * @since Struts 1.3.6
162      */
163     public ForwardConfig(ForwardConfig copyMe) {
164         this(copyMe.getName(), copyMe.getPath(), copyMe.getRedirect(),
165             copyMe.getModule());
166     }
167 
168     public String getExtends() {
169         return (this.inherit);
170     }
171 
172     public void setExtends(String inherit) {
173         if (configured) {
174             throw new IllegalStateException("Configuration is frozen");
175         }
176 
177         this.inherit = inherit;
178     }
179 
180     public boolean isExtensionProcessed() {
181         return extensionProcessed;
182     }
183 
184     public String getName() {
185         return (this.name);
186     }
187 
188     public void setName(String name) {
189         if (configured) {
190             throw new IllegalStateException("Configuration is frozen");
191         }
192 
193         this.name = name;
194     }
195 
196     public String getPath() {
197         return (this.path);
198     }
199 
200     public void setPath(String path) {
201         if (configured) {
202             throw new IllegalStateException("Configuration is frozen");
203         }
204 
205         this.path = path;
206     }
207 
208     public String getModule() {
209         return (this.module);
210     }
211 
212     public void setModule(String module) {
213         if (configured) {
214             throw new IllegalStateException("Configuration is frozen");
215         }
216 
217         this.module = module;
218     }
219 
220     public boolean getRedirect() {
221         return (this.redirect);
222     }
223 
224     public void setRedirect(boolean redirect) {
225         if (configured) {
226             throw new IllegalStateException("Configuration is frozen");
227         }
228 
229         this.redirect = redirect;
230     }
231 
232     public String getCommand() {
233         return (this.command);
234     }
235 
236     public void setCommand(String command) {
237         if (configured) {
238             throw new IllegalStateException("Configuration is frozen");
239         }
240 
241         this.command = command;
242     }
243 
244     public String getCatalog() {
245         return (this.catalog);
246     }
247 
248     public void setCatalog(String catalog) {
249         if (configured) {
250             throw new IllegalStateException("Configuration is frozen");
251         }
252 
253         this.catalog = catalog;
254     }
255 
256     // ------------------------------------------------------ Protected Methods
257 
258     /**
259      * <p>Traces the hierarchy of this object to check if any of the ancestors
260      * are extending this instance.</p>
261      *
262      * @param moduleConfig The {@link ModuleConfig} that this config is from.
263      * @param actionConfig The {@link ActionConfig} that this config is from,
264      *                     if applicable.  This parameter must be null if this
265      *                     forward config is a global forward.
266      * @return true if circular inheritance was detected.
267      */
268     protected boolean checkCircularInheritance(ModuleConfig moduleConfig,
269         ActionConfig actionConfig) {
270         String ancestorName = getExtends();
271 
272         if (ancestorName == null) {
273             return false;
274         }
275 
276         // Find our ancestor
277         ForwardConfig ancestor = null;
278 
279         // First check the action config
280         if (actionConfig != null) {
281             ancestor = actionConfig.findForwardConfig(ancestorName);
282 
283             // If we found *this*, set ancestor to null to check for a global def
284             if (ancestor == this) {
285                 ancestor = null;
286             }
287         }
288 
289         // Then check the global forwards
290         if (ancestor == null) {
291             ancestor = moduleConfig.findForwardConfig(ancestorName);
292 
293             if (ancestor != null) {
294                 // If the ancestor is a global forward, set actionConfig
295                 //  to null so further searches are only done among
296                 //  global forwards.
297                 actionConfig = null;
298             }
299         }
300 
301         while (ancestor != null) {
302             // Check if an ancestor is extending *this*
303             if (ancestor == this) {
304                 return true;
305             }
306 
307             // Get our ancestor's ancestor
308             ancestorName = ancestor.getExtends();
309 
310             // check against ancestors extending same named ancestors
311             if (ancestor.getName().equals(ancestorName)) {
312                 // If the ancestor is extending a config with the same name
313                 //  make sure we look for its ancestor in the global forwards.
314                 //  If we're already at that level, we return false.
315                 if (actionConfig == null) {
316                     return false;
317                 } else {
318                     // Set actionConfig = null to force us to look for global
319                     //  forwards
320                     actionConfig = null;
321                 }
322             }
323 
324             ancestor = null;
325 
326             // First check the action config
327             if (actionConfig != null) {
328                 ancestor = actionConfig.findForwardConfig(ancestorName);
329             }
330 
331             // Then check the global forwards
332             if (ancestor == null) {
333                 ancestor = moduleConfig.findForwardConfig(ancestorName);
334 
335                 if (ancestor != null) {
336                     // Limit further checks to moduleConfig.
337                     actionConfig = null;
338                 }
339             }
340         }
341 
342         return false;
343     }
344 
345     // --------------------------------------------------------- Public Methods
346 
347     /**
348      * <p>Inherit values that have not been overridden from the provided
349      * config object.  Subclasses overriding this method should verify that
350      * the given parameter is of a class that contains a property it is trying
351      * to inherit:</p>
352      *
353      * <pre>
354      * if (config instanceof MyCustomConfig) {
355      *     MyCustomConfig myConfig =
356      *         (MyCustomConfig) config;
357      *
358      *     if (getMyCustomProp() == null) {
359      *         setMyCustomProp(myConfig.getMyCustomProp());
360      *     }
361      * }
362      * </pre>
363      *
364      * <p>If the given <code>config</code> is extending another object, those
365      * extensions should be resolved before it's used as a parameter to this
366      * method.</p>
367      *
368      * @param config The object that this instance will be inheriting its
369      *               values from.
370      * @see #processExtends(ModuleConfig, ActionConfig)
371      */
372     public void inheritFrom(ForwardConfig config)
373         throws ClassNotFoundException, IllegalAccessException,
374             InstantiationException, InvocationTargetException {
375         if (configured) {
376             throw new IllegalStateException("Configuration is frozen");
377         }
378 
379         // Inherit values that have not been overridden
380         if (getCatalog() == null) {
381             setCatalog(config.getCatalog());
382         }
383 
384         if (getCommand() == null) {
385             setCommand(config.getCommand());
386         }
387 
388         if (getModule() == null) {
389             setModule(config.getModule());
390         }
391 
392         if (getName() == null) {
393             setName(config.getName());
394         }
395 
396         if (getPath() == null) {
397             setPath(config.getPath());
398         }
399 
400         if (!getRedirect()) {
401             setRedirect(config.getRedirect());
402         }
403 
404         inheritProperties(config);
405     }
406 
407     /**
408      * <p>Inherit configuration information from the ForwardConfig that this
409      * instance is extending.  This method verifies that any forward config
410      * object that it inherits from has also had its processExtends() method
411      * called.</p>
412      *
413      * @param moduleConfig The {@link ModuleConfig} that this config is from.
414      * @param actionConfig The {@link ActionConfig} that this config is from,
415      *                     if applicable.  This must be null for global
416      *                     forwards.
417      * @see #inheritFrom(ForwardConfig)
418      */
419     public void processExtends(ModuleConfig moduleConfig,
420         ActionConfig actionConfig)
421         throws ClassNotFoundException, IllegalAccessException,
422             InstantiationException, InvocationTargetException {
423         if (configured) {
424             throw new IllegalStateException("Configuration is frozen");
425         }
426 
427         String ancestorName = getExtends();
428 
429         if ((!extensionProcessed) && (ancestorName != null)) {
430             ForwardConfig baseConfig = null;
431 
432             // We only check the action config if we're not a global forward
433             boolean checkActionConfig =
434                 (this != moduleConfig.findForwardConfig(getName()));
435 
436             // ... and the action config was provided
437             checkActionConfig &= (actionConfig != null);
438 
439             // ... and we're not extending a config with the same name
440             // (because if we are, that means we're an action-level forward
441             //  extending a global forward).
442             checkActionConfig &= !ancestorName.equals(getName());
443 
444             // We first check in the action config's forwards
445             if (checkActionConfig) {
446                 baseConfig = actionConfig.findForwardConfig(ancestorName);
447             }
448 
449             // Then check the global forwards
450             if (baseConfig == null) {
451                 baseConfig = moduleConfig.findForwardConfig(ancestorName);
452             }
453 
454             if (baseConfig == null) {
455                 throw new NullPointerException("Unable to find " + "forward '"
456                     + ancestorName + "' to extend.");
457             }
458 
459             // Check for circular inheritance and make sure the base config's
460             //  own extends have been processed already
461             if (checkCircularInheritance(moduleConfig, actionConfig)) {
462                 throw new IllegalArgumentException(
463                     "Circular inheritance detected for forward " + getName());
464             }
465 
466             if (!baseConfig.isExtensionProcessed()) {
467                 baseConfig.processExtends(moduleConfig, actionConfig);
468             }
469 
470             // copy values from the base config
471             inheritFrom(baseConfig);
472         }
473 
474         extensionProcessed = true;
475     }
476 
477     /**
478      * Return a String representation of this object.
479      */
480     public String toString() {
481         StringBuilder sb = new StringBuilder("ForwardConfig[");
482 
483         sb.append("name=");
484         sb.append(this.name);
485         sb.append(",path=");
486         sb.append(this.path);
487         sb.append(",redirect=");
488         sb.append(this.redirect);
489         sb.append(",module=");
490         sb.append(this.module);
491         sb.append(",extends=");
492         sb.append(this.inherit);
493         sb.append(",catalog=");
494         sb.append(this.catalog);
495         sb.append(",command=");
496         sb.append(this.command);
497         sb.append("]");
498 
499         return (sb.toString());
500     }
501 }