KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > struts > util > RequestUtils


1 /*
2  * $Id: RequestUtils.java 76098 2004-11-17 07:07:32Z mrdon $
3  *
4  * Copyright 1999-2004 The Apache Software Foundation.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18
19 package org.apache.struts.util;
20
21 import java.net.MalformedURLException JavaDoc;
22 import java.net.URL JavaDoc;
23 import java.util.Collections JavaDoc;
24 import java.util.Enumeration JavaDoc;
25 import java.util.HashMap JavaDoc;
26 import java.util.Hashtable JavaDoc;
27 import java.util.Locale JavaDoc;
28 import java.util.Map JavaDoc;
29
30 import javax.servlet.ServletContext JavaDoc;
31 import javax.servlet.ServletException JavaDoc;
32 import javax.servlet.http.HttpServletRequest JavaDoc;
33 import javax.servlet.http.HttpSession JavaDoc;
34 import javax.servlet.jsp.JspException JavaDoc;
35 import javax.servlet.jsp.PageContext JavaDoc;
36
37 import org.apache.commons.beanutils.BeanUtils;
38 import org.apache.commons.beanutils.DynaBean;
39 import org.apache.commons.logging.Log;
40 import org.apache.commons.logging.LogFactory;
41 import org.apache.struts.Globals;
42 import org.apache.struts.action.ActionErrors;
43 import org.apache.struts.action.ActionForm;
44 import org.apache.struts.action.ActionMapping;
45 import org.apache.struts.action.ActionMessages;
46 import org.apache.struts.action.ActionServlet;
47 import org.apache.struts.action.ActionServletWrapper;
48 import org.apache.struts.action.DynaActionForm;
49 import org.apache.struts.action.DynaActionFormClass;
50 import org.apache.struts.config.ActionConfig;
51 import org.apache.struts.config.FormBeanConfig;
52 import org.apache.struts.config.ForwardConfig;
53 import org.apache.struts.config.ModuleConfig;
54 import org.apache.struts.taglib.TagUtils;
55 import org.apache.struts.upload.MultipartRequestHandler;
56 import org.apache.struts.upload.MultipartRequestWrapper;
57
58 /**
59  * <p>General purpose utility methods related to processing a servlet request
60  * in the Struts controller framework.</p>
61  *
62  * @version $Rev: 76098 $ $Date: 2004-11-17 07:07:32 +0000 (Wed, 17 Nov 2004) $
63  */

64 public class RequestUtils {
65
66
67     // ------------------------------------------------------- Static Variables
68

69
70     /**
71      * <p>Commons Logging instance.</p>
72      */

73     protected static Log log = LogFactory.getLog(RequestUtils.class);
74
75
76     // --------------------------------------------------------- Public Methods
77

78
79     /**
80      * <p>Create and return an absolute URL for the specified context-relative
81      * path, based on the server and context information in the specified
82      * request.</p>
83      *
84      * @param request The servlet request we are processing
85      * @param path The context-relative path (must start with '/')
86      *
87      * @return absolute URL based on context-relative path
88      *
89      * @exception MalformedURLException if we cannot create an absolute URL
90      */

91     public static URL JavaDoc absoluteURL(HttpServletRequest JavaDoc request, String JavaDoc path)
92             throws MalformedURLException JavaDoc {
93
94         return (new URL JavaDoc(serverURL(request), request.getContextPath() + path));
95
96     }
97
98
99     /**
100      * <p>Return the <code>Class</code> object for the specified fully qualified
101      * class name, from this web application's class loader.</p>
102      *
103      * @param className Fully qualified class name to be loaded
104      * @return Class object
105      *
106      * @exception ClassNotFoundException if the class cannot be found
107      */

108     public static Class JavaDoc applicationClass(String JavaDoc className) throws ClassNotFoundException JavaDoc {
109
110         // Look up the class loader to be used
111
ClassLoader JavaDoc classLoader = Thread.currentThread().getContextClassLoader();
112         if (classLoader == null) {
113             classLoader = RequestUtils.class.getClassLoader();
114         }
115
116         // Attempt to load the specified class
117
return (classLoader.loadClass(className));
118
119     }
120
121
122     /**
123      * <p>Return a new instance of the specified fully qualified class name,
124      * after loading the class from this web application's class loader.
125      * The specified class <strong>MUST</strong> have a public zero-arguments
126      * constructor.</p>
127      *
128      * @param className Fully qualified class name to use
129      *
130      * @return new instance of class
131      * @exception ClassNotFoundException if the class cannot be found
132      * @exception IllegalAccessException if the class or its constructor
133      * is not accessible
134      * @exception InstantiationException if this class represents an
135      * abstract class, an interface, an array class, a primitive type,
136      * or void
137      * @exception InstantiationException if this class has no
138      * zero-arguments constructor
139      */

140     public static Object JavaDoc applicationInstance(String JavaDoc className)
141             throws ClassNotFoundException JavaDoc, IllegalAccessException JavaDoc, InstantiationException JavaDoc {
142
143         return (applicationClass(className).newInstance());
144
145     }
146
147     /**
148      * <p>Create (if necessary) and return an <code>ActionForm</code> instance appropriate
149      * for this request. If no <code>ActionForm</code> instance is required, return
150      * <code>null</code>.</p>
151      *
152      * @param request The servlet request we are processing
153      * @param mapping The action mapping for this request
154      * @param moduleConfig The configuration for this module
155      * @param servlet The action servlet
156      *
157      * @return ActionForm instance associated with this request
158      */

159     public static ActionForm createActionForm(
160             HttpServletRequest JavaDoc request,
161             ActionMapping mapping,
162             ModuleConfig moduleConfig,
163             ActionServlet servlet) {
164
165         // Is there a form bean associated with this mapping?
166
String JavaDoc attribute = mapping.getAttribute();
167         if (attribute == null) {
168             return (null);
169         }
170
171         // Look up the form bean configuration information to use
172
String JavaDoc name = mapping.getName();
173         FormBeanConfig config = moduleConfig.findFormBeanConfig(name);
174         if (config == null) {
175             log.warn("No FormBeanConfig found under '" + name + "'");
176             return (null);
177         }
178
179         ActionForm instance = lookupActionForm(request, attribute, mapping.getScope());
180
181         // Can we recycle the existing form bean instance (if there is one)?
182
try {
183             if (instance != null && canReuseActionForm(instance, config)) {
184                 return (instance);
185             }
186         } catch(ClassNotFoundException JavaDoc e) {
187             log.error(servlet.getInternal().getMessage("formBean", config.getType()), e);
188             return (null);
189         }
190
191         return createActionForm(config, servlet);
192     }
193
194
195
196     private static ActionForm lookupActionForm(HttpServletRequest JavaDoc request, String JavaDoc attribute, String JavaDoc scope)
197     {
198         // Look up any existing form bean instance
199
if (log.isDebugEnabled()) {
200             log.debug(
201                     " Looking for ActionForm bean instance in scope '"
202                     + scope
203                     + "' under attribute key '"
204                     + attribute
205                     + "'");
206         }
207         ActionForm instance = null;
208         HttpSession JavaDoc session = null;
209         if ("request".equals(scope)) {
210             instance = (ActionForm) request.getAttribute(attribute);
211         } else {
212             session = request.getSession();
213             instance = (ActionForm) session.getAttribute(attribute);
214         }
215
216         return (instance);
217     }
218
219     /**
220      * <p>Determine whether <code>instance</code> of <code>ActionForm</code> is
221      * suitable for re-use as an instance of the form described by
222      * <code>config</code>.</p>
223      * @param instance an instance of <code>ActionForm</code> which was found,
224      * probably in either request or session scope.
225      * @param config the configuration for the ActionForm which is needed.
226      * @return true if the instance found is "compatible" with the type required
227      * in the <code>FormBeanConfig</code>; false if not, or if <code>instance</code>
228      * is null.
229      * @throws ClassNotFoundException if the <code>type</code> property of
230      * <code>config</code> is not a valid Class name.
231      */

232     private static boolean canReuseActionForm(ActionForm instance, FormBeanConfig config)
233             throws ClassNotFoundException JavaDoc
234     {
235         if (instance == null) {
236             return (false);
237         }
238
239         boolean canReuse = false;
240         String JavaDoc formType = null;
241         String JavaDoc className = null;
242
243         if (config.getDynamic()) {
244             className = ((DynaBean) instance).getDynaClass().getName();
245             canReuse = className.equals(config.getName());
246             formType = "DynaActionForm";
247         } else {
248             Class JavaDoc configClass = applicationClass(config.getType());
249             className = instance.getClass().getName();
250             canReuse = configClass.isAssignableFrom(instance.getClass());
251             formType = "ActionForm";
252         }
253
254         if (log.isDebugEnabled()) {
255             log.debug(
256                     " Can recycle existing "
257                     + formType
258                     + " instance "
259                     + "of type '"
260                     + className
261                     + "'?: "
262                     + canReuse);
263             log.trace(" --> " + instance);
264         }
265         return (canReuse);
266     }
267
268     /**
269      * <p>Create and return an <code>ActionForm</code> instance appropriate
270      * to the information in <code>config</code>.</p>
271      *
272      * <p>Does not perform any checks to see if an existing ActionForm exists
273      * which could be reused.</p>
274      *
275      * @param config The configuration for the Form bean which is to be created.
276      * @param servlet The action servlet
277      *
278      * @return ActionForm instance associated with this request
279      */

280     public static ActionForm createActionForm(FormBeanConfig config, ActionServlet servlet)
281     {
282         if (config == null)
283         {
284             return (null);
285         }
286
287         ActionForm instance = null;
288
289         // Create and return a new form bean instance
290
try {
291
292             instance = config.createActionForm(servlet);
293             if (log.isDebugEnabled()) {
294                 log.debug(
295                         " Creating new "
296                         + (config.getDynamic() ? "DynaActionForm" : "ActionForm")
297                         + " instance of type '"
298                         + config.getType()
299                         + "'");
300                 log.trace(" --> " + instance);
301             }
302
303         } catch(Throwable JavaDoc t) {
304             log.error(servlet.getInternal().getMessage("formBean", config.getType()), t);
305         }
306
307         return (instance);
308
309     }
310
311
312     /**
313      * <p>Look up and return current user locale, based on the specified parameters.</p>
314      *
315      * @param request The request used to lookup the Locale
316      * @param locale Name of the session attribute for our user's Locale. If this is
317      * <code>null</code>, the default locale key is used for the lookup.
318      * @return current user locale
319      * @since Struts 1.2
320      */

321     public static Locale JavaDoc getUserLocale(HttpServletRequest JavaDoc request, String JavaDoc locale) {
322
323         Locale JavaDoc userLocale = null;
324         HttpSession JavaDoc session = request.getSession(false);
325
326         if (locale == null) {
327             locale = Globals.LOCALE_KEY;
328         }
329
330         // Only check session if sessions are enabled
331
if (session != null) {
332             userLocale = (Locale JavaDoc) session.getAttribute(locale);
333         }
334
335         if (userLocale == null) {
336             // Returns Locale based on Accept-Language header or the server default
337
userLocale = request.getLocale();
338         }
339
340         return userLocale;
341
342     }
343
344
345     /**
346      * <p>Populate the properties of the specified JavaBean from the specified
347      * HTTP request, based on matching each parameter name against the
348      * corresponding JavaBeans "property setter" methods in the bean's class.
349      * Suitable conversion is done for argument types as described under
350      * <code>convert()</code>.</p>
351      *
352      * @param bean The JavaBean whose properties are to be set
353      * @param request The HTTP request whose parameters are to be used
354      * to populate bean properties
355      *
356      * @exception ServletException if an exception is thrown while setting
357      * property values
358      */

359     public static void populate(Object JavaDoc bean, HttpServletRequest JavaDoc request) throws ServletException JavaDoc {
360
361         populate(bean, null, null, request);
362
363     }
364
365
366     /**
367      * <p>Populate the properties of the specified JavaBean from the specified
368      * HTTP request, based on matching each parameter name (plus an optional
369      * prefix and/or suffix) against the corresponding JavaBeans "property
370      * setter" methods in the bean's class. Suitable conversion is done for
371      * argument types as described under <code>setProperties</code>.</p>
372      *
373      * <p>If you specify a non-null <code>prefix</code> and a non-null
374      * <code>suffix</code>, the parameter name must match <strong>both</strong>
375      * conditions for its value(s) to be used in populating bean properties.
376      * If the request's content type is "multipart/form-data" and the
377      * method is "POST", the <code>HttpServletRequest</code> object will be wrapped in
378      * a <code>MultipartRequestWrapper</code object.</p>
379      *
380      * @param bean The JavaBean whose properties are to be set
381      * @param prefix The prefix (if any) to be prepend to bean property
382      * names when looking for matching parameters
383      * @param suffix The suffix (if any) to be appended to bean property
384      * names when looking for matching parameters
385      * @param request The HTTP request whose parameters are to be used
386      * to populate bean properties
387      *
388      * @exception ServletException if an exception is thrown while setting
389      * property values
390      */

391     public static void populate(
392             Object JavaDoc bean,
393             String JavaDoc prefix,
394             String JavaDoc suffix,
395             HttpServletRequest JavaDoc request)
396             throws ServletException JavaDoc {
397
398         // Build a list of relevant request parameters from this request
399
HashMap JavaDoc properties = new HashMap JavaDoc();
400         // Iterator of parameter names
401
Enumeration JavaDoc names = null;
402         // Map for multipart parameters
403
Map JavaDoc multipartParameters = null;
404
405         String JavaDoc contentType = request.getContentType();
406         String JavaDoc method = request.getMethod();
407         boolean isMultipart = false;
408
409         if ((contentType != null)
410                 && (contentType.startsWith("multipart/form-data"))
411                 && (method.equalsIgnoreCase("POST"))) {
412
413             // Get the ActionServletWrapper from the form bean
414
ActionServletWrapper servlet;
415             if (bean instanceof ActionForm) {
416                 servlet = ((ActionForm) bean).getServletWrapper();
417             } else {
418                 throw new ServletException JavaDoc(
419                         "bean that's supposed to be "
420                         + "populated from a multipart request is not of type "
421                         + "\"org.apache.struts.action.ActionForm\", but type "
422                         + "\""
423                         + bean.getClass().getName()
424                         + "\"");
425             }
426
427             // Obtain a MultipartRequestHandler
428
MultipartRequestHandler multipartHandler = getMultipartHandler(request);
429
430             // Set the multipart request handler for our ActionForm.
431
// If the bean isn't an ActionForm, an exception would have been
432
// thrown earlier, so it's safe to assume that our bean is
433
// in fact an ActionForm.
434
((ActionForm) bean).setMultipartRequestHandler(multipartHandler);
435
436             if (multipartHandler != null) {
437                 isMultipart = true;
438                 // Set servlet and mapping info
439
servlet.setServletFor(multipartHandler);
440                 multipartHandler.setMapping(
441                         (ActionMapping) request.getAttribute(Globals.MAPPING_KEY));
442                 // Initialize multipart request class handler
443
multipartHandler.handleRequest(request);
444                 //stop here if the maximum length has been exceeded
445
Boolean JavaDoc maxLengthExceeded =
446                         (Boolean JavaDoc) request.getAttribute(
447                                 MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED);
448                 if ((maxLengthExceeded != null) && (maxLengthExceeded.booleanValue())) {
449                     return;
450                 }
451                 //retrieve form values and put into properties
452
multipartParameters = getAllParametersForMultipartRequest(
453                         request, multipartHandler);
454                 names = Collections.enumeration(multipartParameters.keySet());
455             }
456         }
457
458         if (!isMultipart) {
459             names = request.getParameterNames();
460         }
461
462         while (names.hasMoreElements()) {
463             String JavaDoc name = (String JavaDoc) names.nextElement();
464             String JavaDoc stripped = name;
465             if (prefix != null) {
466                 if (!stripped.startsWith(prefix)) {
467                     continue;
468                 }
469                 stripped = stripped.substring(prefix.length());
470             }
471             if (suffix != null) {
472                 if (!stripped.endsWith(suffix)) {
473                     continue;
474                 }
475                 stripped = stripped.substring(0, stripped.length() - suffix.length());
476             }
477             Object JavaDoc parameterValue = null;
478             if (isMultipart) {
479                 parameterValue = multipartParameters.get(name);
480             } else {
481                 parameterValue = request.getParameterValues(name);
482             }
483
484             // Populate parameters, except "standard" struts attributes
485
// such as 'org.apache.struts.action.CANCEL'
486
if (!(stripped.startsWith("org.apache.struts."))) {
487                 properties.put(stripped, parameterValue);
488             }
489         }
490
491         // Set the corresponding properties of our bean
492
try {
493             BeanUtils.populate(bean, properties);
494         } catch(Exception JavaDoc e) {
495             throw new ServletException JavaDoc("BeanUtils.populate", e);
496         }
497
498     }
499
500
501     /**
502      * <p>Try to locate a multipart request handler for this request. First, look
503      * for a mapping-specific handler stored for us under an attribute. If one
504      * is not present, use the global multipart handler, if there is one.</p>
505      *
506      * @param request The HTTP request for which the multipart handler should
507      * be found.
508      * @return the multipart handler to use, or null if none is
509      * found.
510      *
511      * @exception ServletException if any exception is thrown while attempting
512      * to locate the multipart handler.
513      */

514     private static MultipartRequestHandler getMultipartHandler(HttpServletRequest JavaDoc request)
515             throws ServletException JavaDoc {
516
517         MultipartRequestHandler multipartHandler = null;
518         String JavaDoc multipartClass = (String JavaDoc) request.getAttribute(Globals.MULTIPART_KEY);
519         request.removeAttribute(Globals.MULTIPART_KEY);
520
521         // Try to initialize the mapping specific request handler
522
if (multipartClass != null) {
523             try {
524                 multipartHandler = (MultipartRequestHandler) applicationInstance(multipartClass);
525             } catch(ClassNotFoundException JavaDoc cnfe) {
526                 log.error(
527                         "MultipartRequestHandler class \""
528                         + multipartClass
529                         + "\" in mapping class not found, "
530                         + "defaulting to global multipart class");
531             } catch(InstantiationException JavaDoc ie) {
532                 log.error(
533                         "InstantiationException when instantiating "
534                         + "MultipartRequestHandler \""
535                         + multipartClass
536                         + "\", "
537                         + "defaulting to global multipart class, exception: "
538                         + ie.getMessage());
539             } catch(IllegalAccessException JavaDoc iae) {
540                 log.error(
541                         "IllegalAccessException when instantiating "
542                         + "MultipartRequestHandler \""
543                         + multipartClass
544                         + "\", "
545                         + "defaulting to global multipart class, exception: "
546                         + iae.getMessage());
547             }
548
549             if (multipartHandler != null) {
550                 return multipartHandler;
551             }
552         }
553
554         ModuleConfig moduleConfig =
555                 ModuleUtils.getInstance().getModuleConfig(request);
556
557         multipartClass = moduleConfig.getControllerConfig().getMultipartClass();
558
559         // Try to initialize the global request handler
560
if (multipartClass != null) {
561             try {
562                 multipartHandler = (MultipartRequestHandler) applicationInstance(multipartClass);
563
564             } catch(ClassNotFoundException JavaDoc cnfe) {
565                 throw new ServletException JavaDoc(
566                         "Cannot find multipart class \""
567                         + multipartClass
568                         + "\""
569                         + ", exception: "
570                         + cnfe.getMessage());
571
572             } catch(InstantiationException JavaDoc ie) {
573                 throw new ServletException JavaDoc(
574                         "InstantiationException when instantiating "
575                         + "multipart class \""
576                         + multipartClass
577                         + "\", exception: "
578                         + ie.getMessage());
579
580             } catch(IllegalAccessException JavaDoc iae) {
581                 throw new ServletException JavaDoc(
582                         "IllegalAccessException when instantiating "
583                         + "multipart class \""
584                         + multipartClass
585                         + "\", exception: "
586                         + iae.getMessage());
587             }
588
589             if (multipartHandler != null) {
590                 return multipartHandler;
591             }
592         }
593
594         return multipartHandler;
595     }
596
597
598     /**
599      *<p>Create a <code>Map</code> containing all of the parameters supplied for a multipart
600      * request, keyed by parameter name. In addition to text and file elements
601      * from the multipart body, query string parameters are included as well.</p>
602      *
603      * @param request The (wrapped) HTTP request whose parameters are to be
604      * added to the map.
605      * @param multipartHandler The multipart handler used to parse the request.
606      *
607      * @return the map containing all parameters for this multipart request.
608      */

609     private static Map JavaDoc getAllParametersForMultipartRequest(
610             HttpServletRequest JavaDoc request,
611             MultipartRequestHandler multipartHandler) {
612
613         Map JavaDoc parameters = new HashMap JavaDoc();
614         Hashtable JavaDoc elements = multipartHandler.getAllElements();
615         Enumeration JavaDoc e = elements.keys();
616         while (e.hasMoreElements()) {
617             String JavaDoc key = (String JavaDoc) e.nextElement();
618             parameters.put(key, elements.get(key));
619         }
620
621         if (request instanceof MultipartRequestWrapper) {
622             request = ((MultipartRequestWrapper) request).getRequest();
623             e = request.getParameterNames();
624             while (e.hasMoreElements()) {
625                 String JavaDoc key = (String JavaDoc) e.nextElement();
626                 parameters.put(key, request.getParameterValues(key));
627             }
628         } else {
629             log.debug("Gathering multipart parameters for unwrapped request");
630         }
631
632         return parameters;
633     }
634
635
636     /**
637      * <p>Compute the printable representation of a URL, leaving off the
638      * scheme/host/port part if no host is specified. This will typically
639      * be the case for URLs that were originally created from relative
640      * or context-relative URIs.</p>
641      *
642      * @param url URL to render in a printable representation
643      * @return printable representation of a URL
644      */

645     public static String JavaDoc printableURL(URL JavaDoc url) {
646
647         if (url.getHost() != null) {
648             return (url.toString());
649         }
650
651         String JavaDoc file = url.getFile();
652         String JavaDoc ref = url.getRef();
653         if (ref == null) {
654             return (file);
655         } else {
656             StringBuffer JavaDoc sb = new StringBuffer JavaDoc(file);
657             sb.append('#');
658             sb.append(ref);
659             return (sb.toString());
660         }
661
662     }
663
664
665     /**
666      * <p>Return the context-relative URL that corresponds to the specified
667      * {@link ActionConfig}, relative to the module associated
668      * with the current modules's {@link ModuleConfig}.</p>
669      *
670      * @param request The servlet request we are processing
671      * @param action ActionConfig to be evaluated
672      * @param pattern URL pattern used to map the controller servlet
673
674      * @return context-relative URL relative to the module
675      *
676      * @since Struts 1.1
677      */

678     public static String JavaDoc actionURL(
679             HttpServletRequest JavaDoc request,
680             ActionConfig action,
681             String JavaDoc pattern) {
682
683         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
684         if (pattern.endsWith("/*")) {
685             sb.append(pattern.substring(0, pattern.length() - 2));
686             sb.append(action.getPath());
687
688         } else if (pattern.startsWith("*.")) {
689             ModuleConfig appConfig =
690                     ModuleUtils.getInstance().getModuleConfig(request);
691             sb.append(appConfig.getPrefix());
692             sb.append(action.getPath());
693             sb.append(pattern.substring(1));
694
695         } else {
696             throw new IllegalArgumentException JavaDoc(pattern);
697         }
698
699         return sb.toString();
700
701     }
702   /**
703      * <p>Return the context-relative URL that corresponds to the specified
704      * <code>ForwardConfig</code>. The URL is calculated based on the properties
705      * of the {@link ForwardConfig} instance as follows:</p>
706      * <ul>
707      * <li>If the <code>contextRelative</code> property is set, it is
708      * assumed that the <code>path</code> property contains a path
709      * that is already context-relative:
710      * <ul>
711      * <li>If the <code>path</code> property value starts with a slash,
712      * it is returned unmodified.</li>
713      * <li>If the <code>path</code> property value does not start
714      * with a slash, a slash is prepended.</li>
715      * </ul></li>
716      * <li>Acquire the <code>forwardPattern</code> property from the
717      * <code>ControllerConfig</code> for the application module used
718      * to process this request. If no pattern was configured, default
719      * to a pattern of <code>$M$P</code>, which is compatible with the
720      * hard-coded mapping behavior in Struts 1.0.</li>
721      * <li>Process the acquired <code>forwardPattern</code>, performing the
722      * following substitutions:
723      * <ul>
724      * <li><strong>$M</strong> - Replaced by the module prefix for the
725      * application module processing this request.</li>
726      * <li><strong>$P</strong> - Replaced by the <code>path</code>
727      * property of the specified {@link ForwardConfig}, prepended
728      * with a slash if it does not start with one.</li>
729      * <li><strong>$$</strong> - Replaced by a single dollar sign
730      * character.</li>
731      * <li><strong>$x</strong> (where "x" is any charater not listed
732      * above) - Silently omit these two characters from the result
733      * value. (This has the side effect of causing all other
734      * $+letter combinations to be reserved.)</li>
735      * </ul></li>
736      * </ul>
737      *
738      * @param request The servlet request we are processing
739      * @param forward ForwardConfig to be evaluated
740      *
741      * @return context-relative URL
742      * @since Struts 1.1
743      */

744     public static String JavaDoc forwardURL(HttpServletRequest JavaDoc request, ForwardConfig forward) {
745          return forwardURL(request,forward,null);
746     }
747
748     /**
749      * <p>Return the context-relative URL that corresponds to the specified
750      * <code>ForwardConfig</code>. The URL is calculated based on the properties
751      * of the {@link ForwardConfig} instance as follows:</p>
752      * <ul>
753      * <li>If the <code>contextRelative</code> property is set, it is
754      * assumed that the <code>path</code> property contains a path
755      * that is already context-relative:
756      * <ul>
757      * <li>If the <code>path</code> property value starts with a slash,
758      * it is returned unmodified.</li>
759      * <li>If the <code>path</code> property value does not start
760      * with a slash, a slash is prepended.</li>
761      * </ul></li>
762      * <li>Acquire the <code>forwardPattern</code> property from the
763      * <code>ControllerConfig</code> for the application module used
764      * to process this request. If no pattern was configured, default
765      * to a pattern of <code>$M$P</code>, which is compatible with the
766      * hard-coded mapping behavior in Struts 1.0.</li>
767      * <li>Process the acquired <code>forwardPattern</code>, performing the
768      * following substitutions:
769      * <ul>
770      * <li><strong>$M</strong> - Replaced by the module prefix for the
771      * application module processing this request.</li>
772      * <li><strong>$P</strong> - Replaced by the <code>path</code>
773      * property of the specified {@link ForwardConfig}, prepended
774      * with a slash if it does not start with one.</li>
775      * <li><strong>$$</strong> - Replaced by a single dollar sign
776      * character.</li>
777      * <li><strong>$x</strong> (where "x" is any charater not listed
778      * above) - Silently omit these two characters from the result
779      * value. (This has the side effect of causing all other
780      * $+letter combinations to be reserved.)</li>
781      * </ul></li>
782      * </ul>
783      *
784      * @param request The servlet request we are processing
785      * @param forward ForwardConfig to be evaluated
786    * @param moduleConfig Base forward on this module config.
787      *
788      * @return context-relative URL
789      * @since Struts 1.2
790      */

791     public static String JavaDoc forwardURL(HttpServletRequest JavaDoc request, ForwardConfig forward, ModuleConfig moduleConfig) {
792         //load the current moduleConfig, if null
793
if(moduleConfig == null) {
794             moduleConfig = ModuleUtils.getInstan