1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.struts.util;
22
23 import org.apache.commons.beanutils.BeanUtils;
24 import org.apache.commons.beanutils.PropertyUtils;
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27 import org.apache.struts.Globals;
28 import org.apache.struts.action.ActionForm;
29 import org.apache.struts.action.ActionMapping;
30 import org.apache.struts.action.ActionServlet;
31 import org.apache.struts.action.ActionServletWrapper;
32 import org.apache.struts.config.ActionConfig;
33 import org.apache.struts.config.FormBeanConfig;
34 import org.apache.struts.config.ForwardConfig;
35 import org.apache.struts.config.ModuleConfig;
36 import org.apache.struts.upload.FormFile;
37 import org.apache.struts.upload.MultipartRequestHandler;
38 import org.apache.struts.upload.MultipartRequestWrapper;
39
40 import javax.servlet.ServletContext;
41 import javax.servlet.ServletException;
42 import javax.servlet.http.HttpServletRequest;
43 import javax.servlet.http.HttpSession;
44
45 import java.lang.reflect.InvocationTargetException;
46 import java.net.MalformedURLException;
47 import java.net.URL;
48
49 import java.util.ArrayList;
50 import java.util.Collections;
51 import java.util.Enumeration;
52 import java.util.HashMap;
53 import java.util.Hashtable;
54 import java.util.List;
55 import java.util.Locale;
56 import java.util.Map;
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: 524895 $ $Date: 2007-04-02 14:29:21 -0500 (Mon, 02 Apr 2007) $
63 */
64 public class RequestUtils {
65
66
67 /**
68 * <p>Commons Logging instance.</p>
69 */
70 protected static Log log = LogFactory.getLog(RequestUtils.class);
71
72
73
74 /**
75 * <p>Create and return an absolute URL for the specified context-relative
76 * path, based on the server and context information in the specified
77 * request.</p>
78 *
79 * @param request The servlet request we are processing
80 * @param path The context-relative path (must start with '/')
81 * @return absolute URL based on context-relative path
82 * @throws MalformedURLException if we cannot create an absolute URL
83 */
84 public static URL absoluteURL(HttpServletRequest request, String path)
85 throws MalformedURLException {
86 return (new URL(serverURL(request), request.getContextPath() + path));
87 }
88
89 /**
90 * <p>Return the <code>Class</code> object for the specified fully
91 * qualified class name, from this web application's class loader.</p>
92 *
93 * @param className Fully qualified class name to be loaded
94 * @return Class object
95 * @throws ClassNotFoundException if the class cannot be found
96 */
97 public static Class applicationClass(String className)
98 throws ClassNotFoundException {
99 return applicationClass(className, null);
100 }
101
102 /**
103 * <p>Return the <code>Class</code> object for the specified fully
104 * qualified class name, from this web application's class loader.</p>
105 *
106 * @param className Fully qualified class name to be loaded
107 * @param classLoader The desired classloader to use
108 * @return Class object
109 * @throws ClassNotFoundException if the class cannot be found
110 */
111 public static Class applicationClass(String className,
112 ClassLoader classLoader)
113 throws ClassNotFoundException {
114 if (classLoader == null) {
115
116 classLoader = Thread.currentThread().getContextClassLoader();
117
118 if (classLoader == null) {
119 classLoader = RequestUtils.class.getClassLoader();
120 }
121 }
122
123
124 return (classLoader.loadClass(className));
125 }
126
127 /**
128 * <p>Return a new instance of the specified fully qualified class name,
129 * after loading the class from this web application's class loader. The
130 * specified class <strong>MUST</strong> have a public zero-arguments
131 * constructor.</p>
132 *
133 * @param className Fully qualified class name to use
134 * @return new instance of class
135 * @throws ClassNotFoundException if the class cannot be found
136 * @throws IllegalAccessException if the class or its constructor is not
137 * accessible
138 * @throws InstantiationException if this class represents an abstract
139 * class, an interface, an array class, a
140 * primitive type, or void
141 * @throws InstantiationException if this class has no zero-arguments
142 * constructor
143 */
144 public static Object applicationInstance(String className)
145 throws ClassNotFoundException, IllegalAccessException,
146 InstantiationException {
147 return applicationInstance(className, null);
148 }
149
150 /**
151 * <p>Return a new instance of the specified fully qualified class name,
152 * after loading the class from this web application's class loader. The
153 * specified class <strong>MUST</strong> have a public zero-arguments
154 * constructor.</p>
155 *
156 * @param className Fully qualified class name to use
157 * @param classLoader The desired classloader to use
158 * @return new instance of class
159 * @throws ClassNotFoundException if the class cannot be found
160 * @throws IllegalAccessException if the class or its constructor is not
161 * accessible
162 * @throws InstantiationException if this class represents an abstract
163 * class, an interface, an array class, a
164 * primitive type, or void
165 * @throws InstantiationException if this class has no zero-arguments
166 * constructor
167 */
168 public static Object applicationInstance(String className,
169 ClassLoader classLoader)
170 throws ClassNotFoundException, IllegalAccessException,
171 InstantiationException {
172 return (applicationClass(className, classLoader).newInstance());
173 }
174
175 /**
176 * <p>Create (if necessary) and return an <code>ActionForm</code> instance
177 * appropriate for this request. If no <code>ActionForm</code> instance
178 * is required, return <code>null</code>.</p>
179 *
180 * @param request The servlet request we are processing
181 * @param mapping The action mapping for this request
182 * @param moduleConfig The configuration for this module
183 * @param servlet The action servlet
184 * @return ActionForm instance associated with this request
185 */
186 public static ActionForm createActionForm(HttpServletRequest request,
187 ActionMapping mapping, ModuleConfig moduleConfig, ActionServlet servlet) {
188
189 String attribute = mapping.getAttribute();
190
191 if (attribute == null) {
192 return (null);
193 }
194
195
196 String name = mapping.getName();
197 FormBeanConfig config = moduleConfig.findFormBeanConfig(name);
198
199 if (config == null) {
200 log.warn("No FormBeanConfig found under '" + name + "'");
201
202 return (null);
203 }
204
205 ActionForm instance =
206 lookupActionForm(request, attribute, mapping.getScope());
207
208
209 if ((instance != null) && config.canReuse(instance)) {
210 return (instance);
211 }
212
213 return createActionForm(config, servlet);
214 }
215
216 private static ActionForm lookupActionForm(HttpServletRequest request,
217 String attribute, String scope) {
218
219 if (log.isDebugEnabled()) {
220 log.debug(" Looking for ActionForm bean instance in scope '"
221 + scope + "' under attribute key '" + attribute + "'");
222 }
223
224 ActionForm instance = null;
225 HttpSession session = null;
226
227 if ("request".equals(scope)) {
228 instance = (ActionForm) request.getAttribute(attribute);
229 } else {
230 session = request.getSession();
231 instance = (ActionForm) session.getAttribute(attribute);
232 }
233
234 return (instance);
235 }
236
237 /**
238 * <p>Create and return an <code>ActionForm</code> instance appropriate to
239 * the information in <code>config</code>.</p>
240 *
241 * <p>Does not perform any checks to see if an existing ActionForm exists
242 * which could be reused.</p>
243 *
244 * @param config The configuration for the Form bean which is to be
245 * created.
246 * @param servlet The action servlet
247 * @return ActionForm instance associated with this request
248 */
249 public static ActionForm createActionForm(FormBeanConfig config,
250 ActionServlet servlet) {
251 if (config == null) {
252 return (null);
253 }
254
255 ActionForm instance = null;
256
257
258 try {
259 instance = config.createActionForm(servlet);
260
261 if (log.isDebugEnabled()) {
262 log.debug(" Creating new "
263 + (config.getDynamic() ? "DynaActionForm" : "ActionForm")
264 + " instance of type '" + config.getType() + "'");
265 log.trace(" --> " + instance);
266 }
267 } catch (Throwable t) {
268 log.error(servlet.getInternal().getMessage("formBean",
269 config.getType()), t);
270 }
271
272 return (instance);
273 }
274
275 /**
276 * <p>Retrieves the servlet mapping pattern for the specified {@link ActionServlet}.</p>
277 *
278 * @return the servlet mapping
279 * @see Globals#SERVLET_KEY
280 * @since Struts 1.3.6
281 */
282 public static String getServletMapping(ActionServlet servlet) {
283 ServletContext servletContext = servlet.getServletConfig().getServletContext();
284 return (String)servletContext.getAttribute(Globals.SERVLET_KEY);
285 }
286
287 /**
288 * <p>Look up and return current user locale, based on the specified
289 * parameters.</p>
290 *
291 * @param request The request used to lookup the Locale
292 * @param locale Name of the session attribute for our user's Locale. If
293 * this is <code>null</code>, the default locale key is
294 * used for the lookup.
295 * @return current user locale
296 * @since Struts 1.2
297 */
298 public static Locale getUserLocale(HttpServletRequest request, String locale) {
299 Locale userLocale = null;
300 HttpSession session = request.getSession(false);
301
302 if (locale == null) {
303 locale = Globals.LOCALE_KEY;
304 }
305
306
307 if (session != null) {
308 userLocale = (Locale) session.getAttribute(locale);
309 }
310
311 if (userLocale == null) {
312
313 userLocale = request.getLocale();
314 }
315
316 return userLocale;
317 }
318
319 /**
320 * <p>Populate the properties of the specified JavaBean from the specified
321 * HTTP request, based on matching each parameter name against the
322 * corresponding JavaBeans "property setter" methods in the bean's class.
323 * Suitable conversion is done for argument types as described under
324 * <code>convert()</code>.</p>
325 *
326 * @param bean The JavaBean whose properties are to be set
327 * @param request The HTTP request whose parameters are to be used to
328 * populate bean properties
329 * @throws ServletException if an exception is thrown while setting
330 * property values
331 */
332 public static void populate(Object bean, HttpServletRequest request)
333 throws ServletException {
334 populate(bean, null, null, request);
335 }
336
337 /**
338 * <p>Populate the properties of the specified JavaBean from the specified
339 * HTTP request, based on matching each parameter name (plus an optional
340 * prefix and/or suffix) against the corresponding JavaBeans "property
341 * setter" methods in the bean's class. Suitable conversion is done for
342 * argument types as described under <code>setProperties</code>.</p>
343 *
344 * <p>If you specify a non-null <code>prefix</code> and a non-null
345 * <code>suffix</code>, the parameter name must match
346 * <strong>both</strong> conditions for its value(s) to be used in
347 * populating bean properties. If the request's content type is
348 * "multipart/form-data" and the method is "POST", the
349 * <code>HttpServletRequest</code> object will be wrapped in a
350 * <code>MultipartRequestWrapper</code object.</p>
351 *
352 * @param bean The JavaBean whose properties are to be set
353 * @param prefix The prefix (if any) to be prepend to bean property names
354 * when looking for matching parameters
355 * @param suffix The suffix (if any) to be appended to bean property
356 * names when looking for matching parameters
357 * @param request The HTTP request whose parameters are to be used to
358 * populate bean properties
359 * @throws ServletException if an exception is thrown while setting
360 * property values
361 */
362 public static void populate(Object bean, String prefix, String suffix,
363 HttpServletRequest request)
364 throws ServletException {
365
366 HashMap properties = new HashMap();
367
368
369 Enumeration names = null;
370
371
372 Map multipartParameters = null;
373
374 String contentType = request.getContentType();
375 String method = request.getMethod();
376 boolean isMultipart = false;
377
378 if (bean instanceof ActionForm) {
379 ((ActionForm) bean).setMultipartRequestHandler(null);
380 }
381
382 MultipartRequestHandler multipartHandler = null;
383 if ((contentType != null)
384 && (contentType.startsWith("multipart/form-data"))
385 && (method.equalsIgnoreCase("POST"))) {
386
387 ActionServletWrapper servlet;
388
389 if (bean instanceof ActionForm) {
390 servlet = ((ActionForm) bean).getServletWrapper();
391 } else {
392 throw new ServletException("bean that's supposed to be "
393 + "populated from a multipart request is not of type "
394 + "\"org.apache.struts.action.ActionForm\", but type "
395 + "\"" + bean.getClass().getName() + "\"");
396 }
397
398
399 multipartHandler = getMultipartHandler(request);
400
401 if (multipartHandler != null) {
402 isMultipart = true;
403
404
405 servlet.setServletFor(multipartHandler);
406 multipartHandler.setMapping((ActionMapping) request
407 .getAttribute(Globals.MAPPING_KEY));
408
409
410 multipartHandler.handleRequest(request);
411
412
413 Boolean maxLengthExceeded =
414 (Boolean) request.getAttribute(MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED);
415
416 if ((maxLengthExceeded != null)
417 && (maxLengthExceeded.booleanValue())) {
418 ((ActionForm) bean).setMultipartRequestHandler(multipartHandler);
419 return;
420 }
421
422
423 multipartParameters =
424 getAllParametersForMultipartRequest(request,
425 multipartHandler);
426 names = Collections.enumeration(multipartParameters.keySet());
427 }
428 }
429
430 if (!isMultipart) {
431 names = request.getParameterNames();
432 }
433
434 while (names.hasMoreElements()) {
435 String name = (String) names.nextElement();
436 String stripped = name;
437
438 if (prefix != null) {
439 if (!stripped.startsWith(prefix)) {
440 continue;
441 }
442
443 stripped = stripped.substring(prefix.length());
444 }
445
446 if (suffix != null) {
447 if (!stripped.endsWith(suffix)) {
448 continue;
449 }
450
451 stripped =
452 stripped.substring(0, stripped.length() - suffix.length());
453 }
454
455 Object parameterValue = null;
456
457 if (isMultipart) {
458 parameterValue = multipartParameters.get(name);
459 parameterValue = rationalizeMultipleFileProperty(bean, name, parameterValue);
460 } else {
461 parameterValue = request.getParameterValues(name);
462 }
463
464
465
466 if (!(stripped.startsWith("org.apache.struts."))) {
467 properties.put(stripped, parameterValue);
468 }
469 }
470
471
472 try {
473 BeanUtils.populate(bean, properties);
474 } catch (Exception e) {
475 throw new ServletException("BeanUtils.populate", e);
476 } finally {
477 if (multipartHandler != null) {
478
479
480
481
482 ((ActionForm) bean).setMultipartRequestHandler(multipartHandler);
483 }
484 }
485 }
486
487 /**
488 * <p>If the given form bean can accept multiple FormFile objects but the user only uploaded a single, then
489 * the property will not match the form bean type. This method performs some simple checks to try to accommodate
490 * that situation.</p>
491 * @param bean
492 * @param name
493 * @param parameterValue
494 * @return
495 * @throws ServletException if the introspection has any errors.
496 */
497 private static Object rationalizeMultipleFileProperty(Object bean, String name, Object parameterValue) throws ServletException {
498 if (!(parameterValue instanceof FormFile)) return parameterValue;
499
500 FormFile formFileValue = (FormFile) parameterValue;
501 try {
502 Class propertyType = PropertyUtils.getPropertyType(bean, name);
503
504 if (propertyType.isAssignableFrom(List.class)) {
505 ArrayList list = new ArrayList(1);
506 list.add(formFileValue);
507 return list;
508 }
509
510 if (propertyType.isArray() && propertyType.getComponentType().equals(FormFile.class)) {
511 return new FormFile[] { formFileValue };
512 }
513
514 } catch (IllegalAccessException e) {
515 throw new ServletException(e);
516 } catch (InvocationTargetException e) {
517 throw new ServletException(e);
518 } catch (NoSuchMethodException e) {
519 throw new ServletException(e);
520 }
521
522
523 return parameterValue;
524
525 }
526
527 /**
528 * <p>Try to locate a multipart request handler for this request. First,
529 * look for a mapping-specific handler stored for us under an attribute.
530 * If one is not present, use the global multipart handler, if there is
531 * one.</p>
532 *
533 * @param request The HTTP request for which the multipart handler should
534 * be found.
535 * @return the multipart handler to use, or null if none is found.
536 * @throws ServletException if any exception is thrown while attempting to
537 * locate the multipart handler.
538 */
539 private static MultipartRequestHandler getMultipartHandler(
540 HttpServletRequest request)
541 throws ServletException {
542 MultipartRequestHandler multipartHandler = null;
543 String multipartClass =
544 (String) request.getAttribute(Globals.MULTIPART_KEY);
545
546 request.removeAttribute(Globals.MULTIPART_KEY);
547
548
549 if (multipartClass != null) {
550 try {
551 multipartHandler =
552 (MultipartRequestHandler) applicationInstance(multipartClass);
553 } catch (ClassNotFoundException cnfe) {
554 log.error("MultipartRequestHandler class \"" + multipartClass
555 + "\" in mapping class not found, "
556 + "defaulting to global multipart class");
557 } catch (InstantiationException ie) {
558 log.error("InstantiationException when instantiating "
559 + "MultipartRequestHandler \"" + multipartClass + "\", "
560 + "defaulting to global multipart class, exception: "
561 + ie.getMessage());
562 } catch (IllegalAccessException iae) {
563 log.error("IllegalAccessException when instantiating "
564 + "MultipartRequestHandler \"" + multipartClass + "\", "
565 + "defaulting to global multipart class, exception: "
566 + iae.getMessage());
567 }
568
569 if (multipartHandler != null) {
570 return multipartHandler;
571 }
572 }
573
574 ModuleConfig moduleConfig =
575 ModuleUtils.getInstance().getModuleConfig(request);
576
577 multipartClass = moduleConfig.getControllerConfig().getMultipartClass();
578
579
580 if (multipartClass != null) {
581 try {
582 multipartHandler =
583 (MultipartRequestHandler) applicationInstance(multipartClass);
584 } catch (ClassNotFoundException cnfe) {
585 throw new ServletException("Cannot find multipart class \""
586 + multipartClass + "\"" + ", exception: "
587 + cnfe.getMessage());
588 } catch (InstantiationException ie) {
589 throw new ServletException(
590 "InstantiationException when instantiating "
591 + "multipart class \"" + multipartClass + "\", exception: "
592 + ie.getMessage());
593 } catch (IllegalAccessException iae) {
594 throw new ServletException(
595 "IllegalAccessException when instantiating "
596 + "multipart class \"" + multipartClass + "\", exception: "
597 + iae.getMessage());
598 }
599
600 if (multipartHandler != null) {
601 return multipartHandler;
602 }
603 }
604
605 return multipartHandler;
606 }
607
608 /**
609 * <p>Create a <code>Map</code> containing all of the parameters supplied
610 * for a multipart request, keyed by parameter name. In addition to text
611 * and file elements from the multipart body, query string parameters are
612 * included as well.</p>
613 *
614 * @param request The (wrapped) HTTP request whose parameters are
615 * to be added to the map.
616 * @param multipartHandler The multipart handler used to parse the
617 * request.
618 * @return the map containing all parameters for this multipart request.
619 */
620 private static Map getAllParametersForMultipartRequest(
621 HttpServletRequest request, MultipartRequestHandler multipartHandler) {
622 Map parameters = new HashMap();
623 Hashtable elements = multipartHandler.getAllElements();
624 Enumeration e = elements.keys();
625
626 while (e.hasMoreElements()) {
627 String key = (String) e.nextElement();
628
629 parameters.put(key, elements.get(key));
630 }
631
632 if (request instanceof MultipartRequestWrapper) {
633 request =
634 (HttpServletRequest) ((MultipartRequestWrapper) request)
635 .getRequest();
636 e = request.getParameterNames();
637
638 while (e.hasMoreElements()) {
639 String key = (String) e.nextElement();
640
641 parameters.put(key, request.getParameterValues(key));
642 }
643 } else {
644 log.debug("Gathering multipart parameters for unwrapped request");
645 }
646
647 return parameters;
648 }
649
650 /**
651 * <p>Compute the printable representation of a URL, leaving off the
652 * scheme/host/port part if no host is specified. This will typically be
653 * the case for URLs that were originally created from relative or
654 * context-relative URIs.</p>
655 *
656 * @param url URL to render in a printable representation
657 * @return printable representation of a URL
658 */
659 public static String printableURL(URL url) {
660 if (url.getHost() != null) {
661 return (url.toString());
662 }
663
664 String file = url.getFile();
665 String ref = url.getRef();
666
667 if (ref == null) {
668 return (file);
669 } else {
670 StringBuffer sb = new StringBuffer(file);
671
672 sb.append('#');
673 sb.append(ref);
674
675 return (sb.toString());
676 }
677 }
678
679 /**
680 * <p>Return the context-relative URL that corresponds to the specified
681 * {@link ActionConfig}, relative to the module associated with the
682 * current modules's {@link ModuleConfig}.</p>
683 *
684 * @param request The servlet request we are processing
685 * @param action ActionConfig to be evaluated
686 * @param pattern URL pattern used to map the controller servlet
687 * @return context-relative URL relative to the module
688 * @since Struts 1.1
689 */
690 public static String actionURL(HttpServletRequest request,
691 ActionConfig action, String pattern) {
692 StringBuffer sb = new StringBuffer();
693
694 if (pattern.endsWith("/*")) {
695 sb.append(pattern.substring(0, pattern.length() - 2));
696 sb.append(action.getPath());
697 } else if (pattern.startsWith("*.")) {
698 ModuleConfig appConfig =
699 ModuleUtils.getInstance().getModuleConfig(request);
700
701 sb.append(appConfig.getPrefix());
702 sb.append(action.getPath());
703 sb.append(pattern.substring(1));
704 } else {
705 throw new IllegalArgumentException(pattern);
706 }
707
708 return sb.toString();
709 }
710
711 /**
712 * <p>Return the context-relative URL that corresponds to the specified
713 * <code>ForwardConfig</code>. The URL is calculated based on the
714 * properties of the {@link ForwardConfig} instance as follows:</p>
715 *
716 * <ul>
717 *
718 *
719 * <li>If the <code>contextRelative</code> property is set, it is assumed
720 * that the <code>path</code> property contains a path that is already
721 * context-relative:
722 *
723 * <ul>
724 *
725 * <li>If the <code>path</code> property value starts with a slash, it is
726 * returned unmodified.</li> <li>If the <code>path</code> property value
727 * does not start with a slash, a slash is prepended.</li>
728 *
729 * </ul></li>
730 *
731 * <li>Acquire the <code>forwardPattern</code> property from the
732 * <code>ControllerConfig</code> for the application module used to
733 * process this request. If no pattern was configured, default to a
734 * pattern of <code>$M$P</code>, which is compatible with the hard-coded
735 * mapping behavior in Struts 1.0.</li>
736 *
737 * <li>Process the acquired <code>forwardPattern</code>, performing the
738 * following substitutions:
739 *
740 * <ul>
741 *
742 * <li><strong>$M</strong> - Replaced by the module prefix for the
743 * application module processing this request.</li>
744 *
745 * <li><strong>$P</strong> - Replaced by the <code>path</code> property of
746 * the specified {@link ForwardConfig}, prepended with a slash if it does
747 * not start with one.</li>
748 *
749 * <li><strong>$$</strong> - Replaced by a single dollar sign
750 * character.</li>
751 *
752 * <li><strong>$x</strong> (where "x" is any charater not listed above) -
753 * Silently omit these two characters from the result value. (This has
754 * the side effect of causing all other $+letter combinations to be
755 * reserved.)</li>
756 *
757 * </ul></li>
758 *
759 * </ul>
760 *
761 * @param request The servlet request we are processing
762 * @param forward ForwardConfig to be evaluated
763 * @return context-relative URL
764 * @since Struts 1.1
765 */
766 public static String forwardURL(HttpServletRequest request,
767 ForwardConfig forward) {
768 return forwardURL(request, forward, null);
769 }
770
771 /**
772 * <p>Return the context-relative URL that corresponds to the specified
773 * <code>ForwardConfig</code>. The URL is calculated based on the
774 * properties of the {@link ForwardConfig} instance as follows:</p>
775 *
776 * <ul>
777 *
778 * <li>If the <code>contextRelative</code> property is set, it is assumed
779 * that the <code>path</code> property contains a path that is already
780 * context-relative: <ul>
781 *
782 * <li>If the <code>path</code> property value starts with a slash, it is
783 * returned unmodified.</li> <li>If the <code>path</code> property value
784 * does not start with a slash, a slash is prepended.</li>
785 *
786 * </ul></li>
787 *
788 * <li>Acquire the <code>forwardPattern</code> property from the
789 * <code>ControllerConfig</code> for the application module used to
790 * process this request. If no pattern was configured, default to a
791 * pattern of <code>$M$P</code>, which is compatible with the hard-coded
792 * mapping behavior in Struts 1.0.</li>
793 *
794 * <li>Process the acquired <code>forwardPattern</code>, performing the
795 * following substitutions: <ul> <li><strong>$M</strong> - Replaced by the
796 * module prefix for the application module processing this request.</li>
797 *
798 * <li><strong>$P</strong> - Replaced by the <code>path</code> property of
799 * the specified {@link ForwardConfig}, prepended with a slash if it does
800 * not start with one.</li>
801 *
802 * <li><strong>$$</strong> - Replaced by a single dollar sign
803 * character.</li>
804 *
805 * <li><strong>$x</strong> (where "x" is any charater not listed above) -
806 * Silently omit these two characters from the result value. (This has
807 * the side effect of causing all other $+letter combinations to be
808 * reserved.)</li>
809 *
810 * </ul></li></ul>
811 *
812 * @param request The servlet request we are processing
813 * @param forward ForwardConfig to be evaluated
814 * @param moduleConfig Base forward on this module config.
815 * @return context-relative URL
816 * @since Struts 1.2
817 */
818 public static String forwardURL(HttpServletRequest request,
819 ForwardConfig forward, ModuleConfig moduleConfig) {
820
821 if (moduleConfig == null) {
822 moduleConfig = ModuleUtils.getInstance().getModuleConfig(request);
823 }
824
825 String path = forward.getPath();
826
827
828 String prefix = moduleConfig.getPrefix();
829
830
831 if (forward.getModule() != null) {
832 prefix = forward.getModule();
833
834 if ("/".equals(prefix)) {
835 prefix = "";
836 }
837 }
838
839 StringBuffer sb = new StringBuffer();
840
841
842 String forwardPattern =
843 moduleConfig.getControllerConfig().getForwardPattern();
844
845 if (forwardPattern == null) {
846
847 sb.append(prefix);
848
849
850 if (!path.startsWith("/")) {
851 sb.append("/");
852 }
853
854 sb.append(path);
855 } else {
856 boolean dollar = false;
857
858 for (int i = 0; i < forwardPattern.length(); i++) {
859 char ch = forwardPattern.charAt(i);
860
861 if (dollar) {
862 switch (ch) {
863 case 'M':
864 sb.append(prefix);
865
866 break;
867
868 case 'P':
869
870
871 if (!path.startsWith("/")) {
872 sb.append("/");
873 }
874
875 sb.append(path);
876
877 break;
878
879 case '$':
880 sb.append('$');
881
882 break;
883
884 default:
885 ;
886 }
887
888 dollar = false;
889
890 continue;
891 } else if (ch == '$') {
892 dollar = true;
893 } else {
894 sb.append(ch);
895 }
896 }
897 }
898
899 return (sb.toString());
900 }
901
902 /**
903 * <p>Return the URL representing the current request. This is equivalent
904 * to <code>HttpServletRequest.getRequestURL</code> in Servlet 2.3.</p>
905 *
906 * @param request The servlet request we are processing
907 * @return URL representing the current request
908 * @throws MalformedURLException if a URL cannot be created
909 */
910 public static URL requestURL(HttpServletRequest request)
911 throws MalformedURLException {
912 StringBuffer url = requestToServerUriStringBuffer(request);
913
914 return (new URL(url.toString()));
915 }
916
917 /**
918 * <p>Return the URL representing the scheme, server, and port number of
919 * the current request. Server-relative URLs can be created by simply
920 * appending the server-relative path (starting with '/') to this.</p>
921 *
922 * @param request The servlet request we are processing
923 * @return URL representing the scheme, server, and port number of the
924 * current request
925 * @throws MalformedURLException if a URL cannot be created
926 */
927 public static URL serverURL(HttpServletRequest request)
928 throws MalformedURLException {
929 StringBuffer url = requestToServerStringBuffer(request);
930
931 return (new URL(url.toString()));
932 }
933
934 /**
935 * <p>Return the string representing the scheme, server, and port number
936 * of the current request. Server-relative URLs can be created by simply
937 * appending the server-relative path (starting with '/') to this.</p>
938 *
939 * @param request The servlet request we are processing
940 * @return URL representing the scheme, server, and port number of the
941 * current request
942 * @since Struts 1.2.0
943 */
944 public static StringBuffer requestToServerUriStringBuffer(
945 HttpServletRequest request) {
946 StringBuffer serverUri =
947 createServerUriStringBuffer(request.getScheme(),
948 request.getServerName(), request.getServerPort(),
949 request.getRequestURI());
950
951 return serverUri;
952 }
953
954 /**
955 * <p>Return <code>StringBuffer</code> representing the scheme, server,
956 * and port number of the current request. Server-relative URLs can be
957 * created by simply appending the server-relative path (starting with
958 * '/') to this.</p>
959 *
960 * @param request The servlet request we are processing
961 * @return URL representing the scheme, server, and port number of the
962 * current request
963 * @since Struts 1.2.0
964 */
965 public static StringBuffer requestToServerStringBuffer(
966 HttpServletRequest request) {
967 return createServerStringBuffer(request.getScheme(),
968 request.getServerName(), request.getServerPort());
969 }
970
971 /**
972 * <p>Return <code>StringBuffer</code> representing the scheme, server,
973 * and port number of the current request.</p>
974 *
975 * @param scheme The scheme name to use
976 * @param server The server name to use
977 * @param port The port value to use
978 * @return StringBuffer in the form scheme: server: port
979 * @since Struts 1.2.0
980 */
981 public static StringBuffer createServerStringBuffer(String scheme,
982 String server, int port) {
983 StringBuffer url = new StringBuffer();
984
985 if (port < 0) {
986 port = 80;
987 }
988
989 url.append(scheme);
990 url.append("://");
991 url.append(server);
992
993 if ((scheme.equals("http") && (port != 80))
994 || (scheme.equals("https") && (port != 443))) {
995 url.append(':');
996 url.append(port);
997 }
998
999 return url;
1000 }
1001
1002 /**
1003 * <p>Return <code>StringBuffer</code> representing the scheme, server,
1004 * and port number of the current request.</p>
1005 *
1006 * @param scheme The scheme name to use
1007 * @param server The server name to use
1008 * @param port The port value to use
1009 * @param uri The uri value to use
1010 * @return StringBuffer in the form scheme: server: port
1011 * @since Struts 1.2.0
1012 */
1013 public static StringBuffer createServerUriStringBuffer(String scheme,
1014 String server, int port, String uri) {
1015 StringBuffer serverUri = createServerStringBuffer(scheme, server, port);
1016
1017 serverUri.append(uri);
1018
1019 return serverUri;
1020 }
1021
1022 /**
1023 * <p>Returns the true path of the destination action if the specified forward
1024 * is an action-aliased URL. This method version forms the URL based on
1025 * the current request; selecting the current module if the forward does not
1026 * explicitly contain a module path.</p>
1027 *
1028 * @param forward the forward config
1029 * @param request the current request
1030 * @param servlet the servlet handling the current request
1031 * @return the context-relative URL of the action if the forward has an action identifier; otherwise <code>null</code>.
1032 * @since Struts 1.3.6
1033 */
1034 public static String actionIdURL(ForwardConfig forward, HttpServletRequest request, ActionServlet servlet) {
1035 ModuleConfig moduleConfig = null;
1036 if (forward.getModule() != null) {
1037 String prefix = forward.getModule();
1038 moduleConfig = ModuleUtils.getInstance().getModuleConfig(prefix, servlet.getServletContext());
1039 } else {
1040 moduleConfig = ModuleUtils.getInstance().getModuleConfig(request);
1041 }
1042 return actionIdURL(forward.getPath(), moduleConfig, servlet);
1043 }
1044
1045 /**
1046 * <p>Returns the true path of the destination action if the specified forward
1047 * is an action-aliased URL. This method version forms the URL based on
1048 * the specified module.
1049 *
1050 * @param originalPath the action-aliased path
1051 * @param moduleConfig the module config for this request
1052 * @param servlet the servlet handling the current request
1053 * @return the context-relative URL of the action if the path has an action identifier; otherwise <code>null</code>.
1054 * @since Struts 1.3.6
1055 */
1056 public static String actionIdURL(String originalPath, ModuleConfig moduleConfig, ActionServlet servlet) {
1057 if (originalPath.startsWith("http") || originalPath.startsWith("/")) {
1058 return null;
1059 }
1060
1061
1062
1063 String actionId = null;
1064 String qs = null;
1065 int qpos = originalPath.indexOf("?");
1066 if (qpos == -1) {
1067 actionId = originalPath;
1068 } else {
1069 actionId = originalPath.substring(0, qpos);
1070 qs = originalPath.substring(qpos);
1071 }
1072
1073
1074 ActionConfig actionConfig = moduleConfig.findActionConfigId(actionId);
1075 if (actionConfig == null) {
1076 if (log.isDebugEnabled()) {
1077 log.debug("No actionId found for " + actionId);
1078 }
1079 return null;
1080 }
1081
1082 String path = actionConfig.getPath();
1083 String mapping = RequestUtils.getServletMapping(servlet);
1084 StringBuffer actionIdPath = new StringBuffer();
1085
1086
1087 if (mapping.startsWith("*")) {
1088 actionIdPath.append(path);
1089 actionIdPath.append(mapping.substring(1));
1090 } else if (mapping.startsWith("/")) {
1091 mapping = mapping.substring(0, mapping.length() - 1);
1092 if (mapping.endsWith("/") && path.startsWith("/")) {
1093 actionIdPath.append(mapping);
1094 actionIdPath.append(path.substring(1));
1095 } else {
1096 actionIdPath.append(mapping);
1097 actionIdPath.append(path);
1098 }
1099 } else {
1100 log.warn("Unknown servlet mapping pattern");
1101 actionIdPath.append(path);
1102 }
1103
1104
1105 if (qs != null) {
1106 actionIdPath.append(qs);
1107 }
1108
1109
1110 if (log.isDebugEnabled()) {
1111 log.debug(originalPath + " unaliased to " + actionIdPath.toString());
1112 }
1113 return actionIdPath.toString();
1114 }
1115 }