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