View Javadoc

    1   /*
    2    * $Id: RequestUtils.java 560654 2007-07-29 01:54:02Z niallp $
    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.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: 560654 $ $Date: 2007-07-28 20:54:02 -0500 (Sat, 28 Jul 2007) $
    63   */
    64  public class RequestUtils {
    65      // ------------------------------------------------------- Static Variables
    66
    67      /**
    68       * <p>Commons Logging instance.</p>
    69       */
    70      protected static Log log = LogFactory.getLog(RequestUtils.class);
    71
    72      // --------------------------------------------------------- Public Methods
    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             // Look up the class loader to be used
    116             classLoader = Thread.currentThread().getContextClassLoader();
    117
    118             if (classLoader == null) {
    119                 classLoader = RequestUtils.class.getClassLoader();
    120             }
    121         }
    122
    123         // Attempt to load the specified class
    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         // Is there a form bean associated with this mapping?
    189         String attribute = mapping.getAttribute();
    190
    191         if (attribute == null) {
    192             return (null);
    193         }
    194
    195         // Look up the form bean configuration information to use
    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         // Can we recycle the existing form bean instance (if there is one)?
    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         // Look up any existing form bean instance
    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         // Create and return a new form bean instance
    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         // Only check session if sessions are enabled
    307         if (session != null) {
    308             userLocale = (Locale) session.getAttribute(locale);
    309         }
    310
    311         if (userLocale == null) {
    312             // Returns Locale based on Accept-Language header or the server default
    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         // Build a list of relevant request parameters from this request
    366         HashMap properties = new HashMap();
    367
    368         // Iterator of parameter names
    369         Enumeration names = null;
    370
    371         // Map for multipart parameters
    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             // Get the ActionServletWrapper from the form bean
    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             // Obtain a MultipartRequestHandler
    399             multipartHandler = getMultipartHandler(request);
    400
    401             if (multipartHandler != null) {
    402                 isMultipart = true;
    403
    404                 // Set servlet and mapping info
    405                 servlet.setServletFor(multipartHandler);
    406                 multipartHandler.setMapping((ActionMapping) request
    407                     .getAttribute(Globals.MAPPING_KEY));
    408
    409                 // Initialize multipart request class handler
    410                 multipartHandler.handleRequest(request);
    411
    412                 //stop here if the maximum length has been exceeded
    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                 //retrieve form values and put into properties
    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             // Populate parameters, except "standard" struts attributes
    465             // such as 'org.apache.struts.action.CANCEL'
    466             if (!(stripped.startsWith("org.apache.struts."))) {
    467                 properties.put(stripped, parameterValue);
    468             }
    469         }
    470
    471         // Set the corresponding properties of our bean
    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                 // Set the multipart request handler for our ActionForm.
    479                 // If the bean isn't an ActionForm, an exception would have been
    480                 // thrown earlier, so it's safe to assume that our bean is
    481                 // in fact an ActionForm.
    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)) {
    499             return parameterValue;
    500         }
    501
    502         FormFile formFileValue = (FormFile) parameterValue;
    503         try {
    504             Class propertyType = PropertyUtils.getPropertyType(bean, name);
    505
    506             if (List.class.isAssignableFrom(propertyType)) {
    507                 ArrayList list = new ArrayList(1);
    508                 list.add(formFileValue);
    509                 return list;
    510             }
    511
    512             if (propertyType.isArray() && propertyType.getComponentType().equals(FormFile.class)) {
    513                 return new FormFile[] { formFileValue };
    514             }
    515
    516         } catch (IllegalAccessException e) {
    517             throw new ServletException(e);
    518         } catch (InvocationTargetException e) {
    519             throw new ServletException(e);
    520         } catch (NoSuchMethodException e) {
    521             throw new ServletException(e);
    522         }
    523
    524         // no changes
    525         return parameterValue;
    526
    527     }
    528
    529     /**
    530      * <p>Try to locate a multipart request handler for this request. First,
    531      * look for a mapping-specific handler stored for us under an attribute.
    532      * If one is not present, use the global multipart handler, if there is
    533      * one.</p>
    534      *
    535      * @param request The HTTP request for which the multipart handler should
    536      *                be found.
    537      * @return the multipart handler to use, or null if none is found.
    538      * @throws ServletException if any exception is thrown while attempting to
    539      *                          locate the multipart handler.
    540      */
    541     private static MultipartRequestHandler getMultipartHandler(
    542         HttpServletRequest request)
    543         throws ServletException {
    544         MultipartRequestHandler multipartHandler = null;
    545         String multipartClass =
    546             (String) request.getAttribute(Globals.MULTIPART_KEY);
    547
    548         request.removeAttribute(Globals.MULTIPART_KEY);
    549
    550         // Try to initialize the mapping specific request handler
    551         if (multipartClass != null) {
    552             try {
    553                 multipartHandler =
    554                     (MultipartRequestHandler) applicationInstance(multipartClass);
    555             } catch (ClassNotFoundException cnfe) {
    556                 log.error("MultipartRequestHandler class \"" + multipartClass
    557                     + "\" in mapping class not found, "
    558                     + "defaulting to global multipart class");
    559             } catch (InstantiationException ie) {
    560                 log.error("InstantiationException when instantiating "
    561                     + "MultipartRequestHandler \"" + multipartClass + "\", "
    562                     + "defaulting to global multipart class, exception: "
    563                     + ie.getMessage());
    564             } catch (IllegalAccessException iae) {
    565                 log.error("IllegalAccessException when instantiating "
    566                     + "MultipartRequestHandler \"" + multipartClass + "\", "
    567                     + "defaulting to global multipart class, exception: "
    568                     + iae.getMessage());
    569             }
    570
    571             if (multipartHandler != null) {
    572                 return multipartHandler;
    573             }
    574         }
    575
    576         ModuleConfig moduleConfig =
    577             ModuleUtils.getInstance().getModuleConfig(request);
    578
    579         multipartClass = moduleConfig.getControllerConfig().getMultipartClass();
    580
    581         // Try to initialize the global request handler
    582         if (multipartClass != null) {
    583             try {
    584                 multipartHandler =
    585                     (MultipartRequestHandler) applicationInstance(multipartClass);
    586             } catch (ClassNotFoundException cnfe) {
    587                 throw new ServletException("Cannot find multipart class \""
    588                     + multipartClass + "\"" + ", exception: "
    589                     + cnfe.getMessage());
    590             } catch (InstantiationException ie) {
    591                 throw new ServletException(
    592                     "InstantiationException when instantiating "
    593                     + "multipart class \"" + multipartClass + "\", exception: "
    594                     + ie.getMessage());
    595             } catch (IllegalAccessException iae) {
    596                 throw new ServletException(
    597                     "IllegalAccessException when instantiating "
    598                     + "multipart class \"" + multipartClass + "\", exception: "
    599                     + iae.getMessage());
    600             }
    601
    602             if (multipartHandler != null) {
    603                 return multipartHandler;
    604             }
    605         }
    606
    607         return multipartHandler;
    608     }
    609
    610     /**
    611      * <p>Create a <code>Map</code> containing all of the parameters supplied
    612      * for a multipart request, keyed by parameter name. In addition to text
    613      * and file elements from the multipart body, query string parameters are
    614      * included as well.</p>
    615      *
    616      * @param request          The (wrapped) HTTP request whose parameters are
    617      *                         to be added to the map.
    618      * @param multipartHandler The multipart handler used to parse the
    619      *                         request.
    620      * @return the map containing all parameters for this multipart request.
    621      */
    622     private static Map getAllParametersForMultipartRequest(
    623         HttpServletRequest request, MultipartRequestHandler multipartHandler) {
    624         Map parameters = new HashMap();
    625         Hashtable elements = multipartHandler.getAllElements();
    626         Enumeration e = elements.keys();
    627
    628         while (e.hasMoreElements()) {
    629             String key = (String) e.nextElement();
    630
    631             parameters.put(key, elements.get(key));
    632         }
    633
    634         if (request instanceof MultipartRequestWrapper) {
    635             request =
    636                 (HttpServletRequest) ((MultipartRequestWrapper) request)
    637                 .getRequest();
    638             e = request.getParameterNames();
    639
    640             while (e.hasMoreElements()) {
    641                 String key = (String) e.nextElement();
    642
    643                 parameters.put(key, request.getParameterValues(key));
    644             }
    645         } else {
    646             log.debug("Gathering multipart parameters for unwrapped request");
    647         }
    648
    649         return parameters;
    650     }
    651
    652     /**
    653      * <p>Compute the printable representation of a URL, leaving off the
    654      * scheme/host/port part if no host is specified. This will typically be
    655      * the case for URLs that were originally created from relative or
    656      * context-relative URIs.</p>
    657      *
    658      * @param url URL to render in a printable representation
    659      * @return printable representation of a URL
    660      */
    661     public static String printableURL(URL url) {
    662         if (url.getHost() != null) {
    663             return (url.toString());
    664         }
    665
    666         String file = url.getFile();
    667         String ref = url.getRef();
    668
    669         if (ref == null) {
    670             return (file);
    671         } else {
    672             StringBuffer sb = new StringBuffer(file);
    673
    674             sb.append('#');
    675             sb.append(ref);
    676
    677             return (sb.toString());
    678         }
    679     }
    680
    681     /**
    682      * <p>Return the context-relative URL that corresponds to the specified
    683      * {@link ActionConfig}, relative to the module associated with the
    684      * current modules's {@link ModuleConfig}.</p>
    685      *
    686      * @param request The servlet request we are processing
    687      * @param action  ActionConfig to be evaluated
    688      * @param pattern URL pattern used to map the controller servlet
    689      * @return context-relative URL relative to the module
    690      * @since Struts 1.1
    691      */
    692     public static String actionURL(HttpServletRequest request,
    693         ActionConfig action, String pattern) {
    694         StringBuffer sb = new StringBuffer();
    695
    696         if (pattern.endsWith("/*")) {
    697             sb.append(pattern.substring(0, pattern.length() - 2));
    698             sb.append(action.getPath());
    699         } else if (pattern.startsWith("*.")) {
    700             ModuleConfig appConfig =
    701                 ModuleUtils.getInstance().getModuleConfig(request);
    702
    703             sb.append(appConfig.getPrefix());
    704             sb.append(action.getPath());
    705             sb.append(pattern.substring(1));
    706         } else {
    707             throw new IllegalArgumentException(pattern);
    708         }
    709
    710         return sb.toString();
    711     }
    712
    713     /**
    714      * <p>Return the context-relative URL that corresponds to the specified
    715      * <code>ForwardConfig</code>. The URL is calculated based on the
    716      * properties of the {@link ForwardConfig} instance as follows:</p>
    717      *
    718      * <ul>
    719      *
    720      *
    721      * <li>If the <code>contextRelative</code> property is set, it is assumed
    722      * that the <code>path</code> property contains a path that is already
    723      * context-relative:
    724      *
    725      * <ul>
    726      *
    727      * <li>If the <code>path</code> property value starts with a slash, it is
    728      * returned unmodified.</li> <li>If the <code>path</code> property value
    729      * does not start with a slash, a slash is prepended.</li>
    730      *
    731      * </ul></li>
    732      *
    733      * <li>Acquire the <code>forwardPattern</code> property from the
    734      * <code>ControllerConfig</code> for the application module used to
    735      * process this request. If no pattern was configured, default to a
    736      * pattern of <code>$M$P</code>, which is compatible with the hard-coded
    737      * mapping behavior in Struts 1.0.</li>
    738      *
    739      * <li>Process the acquired <code>forwardPattern</code>, performing the
    740      * following substitutions:
    741      *
    742      * <ul>
    743      *
    744      * <li><strong>$M</strong> - Replaced by the module prefix for the
    745      * application module processing this request.</li>
    746      *
    747      * <li><strong>$P</strong> - Replaced by the <code>path</code> property of
    748      * the specified {@link ForwardConfig}, prepended with a slash if it does
    749      * not start with one.</li>
    750      *
    751      * <li><strong>$$</strong> - Replaced by a single dollar sign
    752      * character.</li>
    753      *
    754      * <li><strong>$x</strong> (where "x" is any charater not listed above) -
    755      * Silently omit these two characters from the result value.  (This has
    756      * the side effect of causing all other $+letter combinations to be
    757      * reserved.)</li>
    758      *
    759      * </ul></li>
    760      *
    761      * </ul>
    762      *
    763      * @param request The servlet request we are processing
    764      * @param forward ForwardConfig to be evaluated
    765      * @return context-relative URL
    766      * @since Struts 1.1
    767      */
    768     public static String forwardURL(HttpServletRequest request,
    769         ForwardConfig forward) {
    770         return forwardURL(request, forward, null);
    771     }
    772
    773     /**
    774      * <p>Return the context-relative URL that corresponds to the specified
    775      * <code>ForwardConfig</code>. The URL is calculated based on the
    776      * properties of the {@link ForwardConfig} instance as follows:</p>
    777      *
    778      * <ul>
    779      *
    780      * <li>If the <code>contextRelative</code> property is set, it is assumed
    781      * that the <code>path</code> property contains a path that is already
    782      * context-relative: <ul>
    783      *
    784      * <li>If the <code>path</code> property value starts with a slash, it is
    785      * returned unmodified.</li> <li>If the <code>path</code> property value
    786      * does not start with a slash, a slash is prepended.</li>
    787      *
    788      * </ul></li>
    789      *
    790      * <li>Acquire the <code>forwardPattern</code> property from the
    791      * <code>ControllerConfig</code> for the application module used to
    792      * process this request. If no pattern was configured, default to a
    793      * pattern of <code>$M$P</code>, which is compatible with the hard-coded
    794      * mapping behavior in Struts 1.0.</li>
    795      *
    796      * <li>Process the acquired <code>forwardPattern</code>, performing the
    797      * following substitutions: <ul> <li><strong>$M</strong> - Replaced by the
    798      * module prefix for the application module processing this request.</li>
    799      *
    800      * <li><strong>$P</strong> - Replaced by the <code>path</code> property of
    801      * the specified {@link ForwardConfig}, prepended with a slash if it does
    802      * not start with one.</li>
    803      *
    804      * <li><strong>$$</strong> - Replaced by a single dollar sign
    805      * character.</li>
    806      *
    807      * <li><strong>$x</strong> (where "x" is any charater not listed above) -
    808      * Silently omit these two characters from the result value.  (This has
    809      * the side effect of causing all other $+letter combinations to be
    810      * reserved.)</li>
    811      *
    812      * </ul></li></ul>
    813      *
    814      * @param request      The servlet request we are processing
    815      * @param forward      ForwardConfig to be evaluated
    816      * @param moduleConfig Base forward on this module config.
    817      * @return context-relative URL
    818      * @since Struts 1.2
    819      */
    820     public static String forwardURL(HttpServletRequest request,
    821         ForwardConfig forward, ModuleConfig moduleConfig) {
    822         //load the current moduleConfig, if null
    823         if (moduleConfig == null) {
    824             moduleConfig = ModuleUtils.getInstance().getModuleConfig(request);
    825         }
    826
    827         String path = forward.getPath();
    828
    829         //load default prefix
    830         String prefix = moduleConfig.getPrefix();
    831
    832         //override prefix if supplied by forward
    833         if (forward.getModule() != null) {
    834             prefix = forward.getModule();
    835
    836             if ("/".equals(prefix)) {
    837                 prefix = "";
    838             }
    839         }
    840
    841         StringBuffer sb = new StringBuffer();
    842
    843         // Calculate a context relative path for this ForwardConfig
    844         String forwardPattern =
    845             moduleConfig.getControllerConfig().getForwardPattern();
    846
    847         if (forwardPattern == null) {
    848             // Performance optimization for previous default behavior
    849             sb.append(prefix);
    850
    851             // smoothly insert a '/' if needed
    852             if (!path.startsWith("/")) {
    853                 sb.append("/");
    854             }
    855
    856             sb.append(path);
    857         } else {
    858             boolean dollar = false;
    859
    860             for (int i = 0; i < forwardPattern.length(); i++) {
    861                 char ch = forwardPattern.charAt(i);
    862
    863                 if (dollar) {
    864                     switch (ch) {
    865                     case 'M':
    866                         sb.append(prefix);
    867
    868                         break;
    869
    870                     case 'P':
    871
    872                         // add '/' if needed
    873                         if (!path.startsWith("/")) {
    874                             sb.append("/");
    875                         }
    876
    877                         sb.append(path);
    878
    879                         break;
    880
    881                     case '$':
    882                         sb.append('$');
    883
    884                         break;
    885
    886                     default:
    887                         ; // Silently swallow
    888                     }
    889
    890                     dollar = false;
    891
    892                     continue;
    893                 } else if (ch == '$') {
    894                     dollar = true;
    895                 } else {
    896                     sb.append(ch);
    897                 }
    898             }
    899         }
    900
    901         return (sb.toString());
    902     }
    903
    904     /**
    905      * <p>Return the URL representing the current request. This is equivalent
    906      * to <code>HttpServletRequest.getRequestURL</code> in Servlet 2.3.</p>
    907      *
    908      * @param request The servlet request we are processing
    909      * @return URL representing the current request
    910      * @throws MalformedURLException if a URL cannot be created
    911      */
    912     public static URL requestURL(HttpServletRequest request)
    913         throws MalformedURLException {
    914         StringBuffer url = requestToServerUriStringBuffer(request);
    915
    916         return (new URL(url.toString()));
    917     }
    918
    919     /**
    920      * <p>Return the URL representing the scheme, server, and port number of
    921      * the current request. Server-relative URLs can be created by simply
    922      * appending the server-relative path (starting with '/') to this.</p>
    923      *
    924      * @param request The servlet request we are processing
    925      * @return URL representing the scheme, server, and port number of the
    926      *         current request
    927      * @throws MalformedURLException if a URL cannot be created
    928      */
    929     public static URL serverURL(HttpServletRequest request)
    930         throws MalformedURLException {
    931         StringBuffer url = requestToServerStringBuffer(request);
    932
    933         return (new URL(url.toString()));
    934     }
    935
    936     /**
    937      * <p>Return the string representing the scheme, server, and port number
    938      * of the current request. Server-relative URLs can be created by simply
    939      * appending the server-relative path (starting with '/') to this.</p>
    940      *
    941      * @param request The servlet request we are processing
    942      * @return URL representing the scheme, server, and port number of the
    943      *         current request
    944      * @since Struts 1.2.0
    945      */
    946     public static StringBuffer requestToServerUriStringBuffer(
    947         HttpServletRequest request) {
    948         StringBuffer serverUri =
    949             createServerUriStringBuffer(request.getScheme(),
    950                 request.getServerName(), request.getServerPort(),
    951                 request.getRequestURI());
    952
    953         return serverUri;
    954     }
    955
    956     /**
    957      * <p>Return <code>StringBuffer</code> representing the scheme, server,
    958      * and port number of the current request. Server-relative URLs can be
    959      * created by simply appending the server-relative path (starting with
    960      * '/') to this.</p>
    961      *
    962      * @param request The servlet request we are processing
    963      * @return URL representing the scheme, server, and port number of the
    964      *         current request
    965      * @since Struts 1.2.0
    966      */
    967     public static StringBuffer requestToServerStringBuffer(
    968         HttpServletRequest request) {
    969         return createServerStringBuffer(request.getScheme(),
    970             request.getServerName(), request.getServerPort());
    971     }
    972
    973     /**
    974      * <p>Return <code>StringBuffer</code> representing the scheme, server,
    975      * and port number of the current request.</p>
    976      *
    977      * @param scheme The scheme name to use
    978      * @param server The server name to use
    979      * @param port   The port value to use
    980      * @return StringBuffer in the form scheme: server: port
    981      * @since Struts 1.2.0
    982      */
    983     public static StringBuffer createServerStringBuffer(String scheme,
    984         String server, int port) {
    985         StringBuffer url = new StringBuffer();
    986
    987         if (port < 0) {
    988             port = 80; // Work around java.net.URL bug
    989         }
    990
    991         url.append(scheme);
    992         url.append("://");
    993         url.append(server);
    994
    995         if ((scheme.equals("http") && (port != 80))
    996             || (scheme.equals("https") && (port != 443))) {
    997             url.append(':');
    998             url.append(port);
    999         }
    1000
    1001         return url;
    1002     }
    1003
    1004     /**
    1005      * <p>Return <code>StringBuffer</code> representing the scheme, server,
    1006      * and port number of the current request.</p>
    1007      *
    1008      * @param scheme The scheme name to use
    1009      * @param server The server name to use
    1010      * @param port   The port value to use
    1011      * @param uri    The uri value to use
    1012      * @return StringBuffer in the form scheme: server: port
    1013      * @since Struts 1.2.0
    1014      */
    1015     public static StringBuffer createServerUriStringBuffer(String scheme,
    1016         String server, int port, String uri) {
    1017         StringBuffer serverUri = createServerStringBuffer(scheme, server, port);
    1018
    1019         serverUri.append(uri);
    1020
    1021         return serverUri;
    1022     }
    1023
    1024     /**
    1025      * <p>Returns the true path of the destination action if the specified forward
    1026      * is an action-aliased URL. This method version forms the URL based on
    1027      * the current request; selecting the current module if the forward does not
    1028      * explicitly contain a module path.</p>
    1029      *
    1030      * @param forward the forward config
    1031      * @param request the current request
    1032      * @param servlet the servlet handling the current request
    1033      * @return the context-relative URL of the action if the forward has an action identifier; otherwise <code>null</code>.
    1034      * @since Struts 1.3.6
    1035      */
    1036     public static String actionIdURL(ForwardConfig forward, HttpServletRequest request, ActionServlet servlet) {
    1037         ModuleConfig moduleConfig = null;
    1038         if (forward.getModule() != null) {
    1039             String prefix = forward.getModule();
    1040             moduleConfig = ModuleUtils.getInstance().getModuleConfig(prefix, servlet.getServletContext());
    1041         } else {
    1042             moduleConfig = ModuleUtils.getInstance().getModuleConfig(request);
    1043         }
    1044         return actionIdURL(forward.getPath(), moduleConfig, servlet);
    1045     }
    1046
    1047     /**
    1048      * <p>Returns the true path of the destination action if the specified forward
    1049      * is an action-aliased URL. This method version forms the URL based on
    1050      * the specified module.
    1051      *
    1052      * @param originalPath the action-aliased path
    1053      * @param moduleConfig the module config for this request
    1054      * @param servlet the servlet handling the current request
    1055      * @return the context-relative URL of the action if the path has an action identifier; otherwise <code>null</code>.
    1056      * @since Struts 1.3.6
    1057      */
    1058     public static String actionIdURL(String originalPath, ModuleConfig moduleConfig, ActionServlet servlet) {
    1059         if (originalPath.startsWith("http") || originalPath.startsWith("/")) {
    1060             return null;
    1061         }
    1062
    1063         // Split the forward path into the resource and query string;
    1064         // it is possible a forward (or redirect) has added parameters.
    1065         String actionId = null;
    1066         String qs = null;
    1067         int qpos = originalPath.indexOf("?");
    1068         if (qpos == -1) {
    1069             actionId = originalPath;
    1070         } else {
    1071             actionId = originalPath.substring(0, qpos);
    1072             qs = originalPath.substring(qpos);
    1073         }
    1074
    1075         // Find the action of the given actionId
    1076         ActionConfig actionConfig = moduleConfig.findActionConfigId(actionId);
    1077         if (actionConfig == null) {
    1078             if (log.isDebugEnabled()) {
    1079                 log.debug("No actionId found for " + actionId);
    1080             }
    1081             return null;
    1082         }
    1083
    1084         String path = actionConfig.getPath();
    1085         String mapping = RequestUtils.getServletMapping(servlet);
    1086         StringBuffer actionIdPath = new StringBuffer();
    1087
    1088         // Form the path based on the servlet mapping pattern
    1089         if (mapping.startsWith("*")) {
    1090             actionIdPath.append(path);
    1091             actionIdPath.append(mapping.substring(1));
    1092         } else if (mapping.startsWith("/")) {  // implied ends with a *
    1093             mapping = mapping.substring(0, mapping.length() - 1);
    1094             if (mapping.endsWith("/") && path.startsWith("/")) {
    1095                 actionIdPath.append(mapping);
    1096                 actionIdPath.append(path.substring(1));
    1097             } else {
    1098                 actionIdPath.append(mapping);
    1099                 actionIdPath.append(path);
    1100             }
    1101         } else {
    1102             log.warn("Unknown servlet mapping pattern");
    1103             actionIdPath.append(path);
    1104         }
    1105
    1106         // Lastly add any query parameters (the ? is part of the query string)
    1107         if (qs != null) {
    1108             actionIdPath.append(qs);
    1109         }
    1110
    1111         // Return the path
    1112         if (log.isDebugEnabled()) {
    1113             log.debug(originalPath + " unaliased to " + actionIdPath.toString());
    1114         }
    1115         return actionIdPath.toString();
    1116     }
    1117 }