View Javadoc

    1   /*
    2    * $Id: URL.java 497654 2007-01-19 00:21:57Z rgielen $
    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.struts2.components;
    22
    23  import java.io.IOException;
    24  import java.io.Writer;
    25  import java.util.Collections;
    26  import java.util.Iterator;
    27  import java.util.LinkedHashMap;
    28  import java.util.Map;
    29
    30  import javax.servlet.http.HttpServletRequest;
    31  import javax.servlet.http.HttpServletResponse;
    32
    33  import org.apache.commons.logging.Log;
    34  import org.apache.commons.logging.LogFactory;
    35  import org.apache.struts2.views.annotations.StrutsTag;
    36  import org.apache.struts2.views.annotations.StrutsTagAttribute;
    37  import org.apache.struts2.StrutsException;
    38  import org.apache.struts2.StrutsConstants;
    39  import org.apache.struts2.dispatcher.Dispatcher;
    40  import org.apache.struts2.portlet.context.PortletActionContext;
    41  import org.apache.struts2.portlet.util.PortletUrlHelper;
    42  import org.apache.struts2.views.util.UrlHelper;
    43
    44  import com.opensymphony.xwork2.inject.Inject;
    45  import com.opensymphony.xwork2.util.ValueStack;
    46
    47  /***
    48   * <!-- START SNIPPET: javadoc -->
    49   *
    50   * <p>This tag is used to create a URL.</p>
    51   *
    52   * <p>You can use the "param" tag inside the body to provide
    53   * additional request parameters.</p>
    54   *
    55   * <b>NOTE:</b>
    56   * <p>When includeParams is 'all' or 'get', the parameter defined in param tag will take
    57   * precedence and will not be overriden if they exists in the parameter submitted. For
    58   * example, in Example 3 below, if there is a id parameter in the url where the page this
    59   * tag is included like http://<host>:<port>/<context>/editUser.action?id=3333&name=John
    60   * the generated url will be http://<host>:<port>/context>/editUser.action?id=22&name=John
    61   * cause the parameter defined in the param tag will take precedence.</p>
    62   *
    63   * <!-- END SNIPPET: javadoc -->
    64   *
    65   *
    66   * <!-- START SNIPPET: params -->
    67   *
    68   * <ul>
    69   *      <li>action (String) - (value or action choose either one, if both exist value takes precedence) action's name (alias) <li>
    70   *      <li>value (String) - (value or action choose either one, if both exist value takes precedence) the url itself</li>
    71   *      <li>scheme (String) - http scheme (http, https) default to the scheme this request is in</li>
    72   *      <li>namespace - action's namespace</li>
    73   *      <li>method (String) - action's method, default to execute() </li>
    74   *      <li>encode (Boolean) - url encode the generated url. Default is true</li>
    75   *      <li>includeParams (String) - The includeParams attribute may have the value 'none', 'get' or 'all'. Default is 'get'.
    76   *                                   none - include no parameters in the URL
    77   *                                   get  - include only GET parameters in the URL (default)
    78   *                                   all  - include both GET and POST parameters in the URL
    79   *      </li>
    80   *      <li>includeContext (Boolean) - determine wheather to include the web app context path. Default is true.</li>
    81   * </ul>
    82   *
    83   * <!-- END SNIPPET: params -->
    84   *
    85   * <p/> <b>Examples</b>
    86   * <pre>
    87   * <!-- START SNIPPET: example -->
    88   *
    89   * &lt;-- Example 1 --&gt;
    90   * &lt;s:url value="editGadget.action"&gt;
    91   *     &lt;s:param name="id" value="%{selected}" /&gt;
    92   * &lt;/s:url&gt;
    93   *
    94   * &lt;-- Example 2 --&gt;
    95   * &lt;s:url action="editGadget"&gt;
    96   *     &lt;s:param name="id" value="%{selected}" /&gt;
    97   * &lt;/s:url&gt;
    98   *
    99   * &lt;-- Example 3--&gt;
    100  * &lt;s:url includeParams="get"  &gt;
    101  *     &lt:param name="id" value="%{'22'}" /&gt;
    102  * &lt;/s:url&gt;
    103  *
    104  * <!-- END SNIPPET: example -->
    105  * </pre>
    106  *
    107  * @see Param
    108  *
    109  */
    110 @StrutsTag(name="url", tldTagClass="org.apache.struts2.views.jsp.URLTag", description="This tag is used to create a URL")
    111 public class URL extends Component {
    112     private static final Log LOG = LogFactory.getLog(URL.class);
    113
    114     /***
    115      * The includeParams attribute may have the value 'none', 'get' or 'all'.
    116      * It is used when the url tag is used without a value attribute.
    117      * Its value is looked up on the ValueStack
    118      * If no includeParams is specified then 'get' is used.
    119      * none - include no parameters in the URL
    120      * get  - include only GET parameters in the URL (default)
    121      * all  - include both GET and POST parameters in the URL
    122      */
    123     public static final String NONE = "none";
    124     public static final String GET = "get";
    125     public static final String ALL = "all";
    126
    127     private HttpServletRequest req;
    128     private HttpServletResponse res;
    129
    130     protected String includeParams;
    131     protected String scheme;
    132     protected String value;
    133     protected String action;
    134     protected String namespace;
    135     protected String method;
    136     protected boolean encode = true;
    137     protected boolean includeContext = true;
    138     protected String portletMode;
    139     protected String windowState;
    140     protected String portletUrlType;
    141     protected String anchor;
    142     protected String urlIncludeParams;
    143     protected ExtraParameterProvider extraParameterProvider;
    144
    145     public URL(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
    146         super(stack);
    147         this.req = req;
    148         this.res = res;
    149     }
    150
    151     @Inject(StrutsConstants.STRUTS_URL_INCLUDEPARAMS)
    152     public void setUrlIncludeParams(String urlIncludeParams) {
    153         this.urlIncludeParams = urlIncludeParams;
    154     }
    155
    156     @Inject(required=false)
    157     public void setExtraParameterProvider(ExtraParameterProvider provider) {
    158         this.extraParameterProvider = provider;
    159     }
    160
    161     public boolean start(Writer writer) {
    162         boolean result = super.start(writer);
    163
    164         if (value != null) {
    165             value = findString(value);
    166         }
    167
    168         // no explicit url set so attach params from current url, do
    169         // this at start so body params can override any of these they wish.
    170         try {
    171             // ww-1266
    172             String includeParams = (urlIncludeParams != null ? urlIncludeParams.toLowerCase() : GET);
    173
    174             if (this.includeParams != null) {
    175                 includeParams = findString(this.includeParams);
    176             }
    177
    178             if (NONE.equalsIgnoreCase(includeParams)) {
    179                 mergeRequestParameters(value, parameters, Collections.EMPTY_MAP);
    180             } else if (ALL.equalsIgnoreCase(includeParams)) {
    181                 mergeRequestParameters(value, parameters, req.getParameterMap());
    182
    183                 // for ALL also include GET parameters
    184                 includeGetParameters();
    185                 includeExtraParameters();
    186             } else if (GET.equalsIgnoreCase(includeParams) || (includeParams == null && value == null && action == null)) {
    187                 includeGetParameters();
    188                 includeExtraParameters();
    189             } else if (includeParams != null) {
    190                 LOG.warn("Unknown value for includeParams parameter to URL tag: " + includeParams);
    191             }
    192         } catch (Exception e) {
    193             LOG.warn("Unable to put request parameters (" + req.getQueryString() + ") into parameter map.", e);
    194         }
    195
    196
    197         return result;
    198     }
    199
    200     private void includeExtraParameters() {
    201         if (extraParameterProvider != null) {
    202             mergeRequestParameters(value, parameters, extraParameterProvider.getExtraParameters());
    203         }
    204     }
    205     private void includeGetParameters() {
    206         if(!(Dispatcher.getInstance().isPortletSupportActive() && PortletActionContext.isPortletRequest())) {
    207             String query = extractQueryString();
    208             mergeRequestParameters(value, parameters, UrlHelper.parseQueryString(query));
    209         }
    210     }
    211
    212     private String extractQueryString() {
    213         // Parse the query string to make sure that the parameters come from the query, and not some posted data
    214         String query = req.getQueryString();
    215         if (query == null) {
    216             query = (String) req.getAttribute("javax.servlet.forward.query_string");
    217         }
    218
    219         if (query != null) {
    220             // Remove possible #foobar suffix
    221             int idx = query.lastIndexOf('#');
    222
    223             if (idx != -1) {
    224                 query = query.substring(0, idx);
    225             }
    226         }
    227         return query;
    228     }
    229
    230     public boolean end(Writer writer, String body) {
    231         String scheme = req.getScheme();
    232
    233         if (this.scheme != null) {
    234             scheme = this.scheme;
    235         }
    236
    237         String result;
    238         if (value == null && action != null) {
    239             if(Dispatcher.getInstance().isPortletSupportActive() && PortletActionContext.isPortletRequest()) {
    240                 result = PortletUrlHelper.buildUrl(action, namespace, parameters, portletUrlType, portletMode, windowState);
    241             }
    242             else {
    243                 result = determineActionURL(action, namespace, method, req, res, parameters, scheme, includeContext, encode);
    244             }
    245         } else {
    246             if(Dispatcher.getInstance().isPortletSupportActive() && PortletActionContext.isPortletRequest()) {
    247                 result = PortletUrlHelper.buildResourceUrl(value, parameters);
    248             }
    249             else {
    250                 String _value = value;
    251
    252                 // We don't include the request parameters cause they would have been
    253                 // prioritised before this [in start(Writer) method]
    254                 if (_value != null && _value.indexOf("?") > 0) {
    255                     _value = _value.substring(0, _value.indexOf("?"));
    256                 }
    257                 result = UrlHelper.buildUrl(_value, req, res, parameters, scheme, includeContext, encode);
    258             }
    259         }
    260         if ( anchor != null && anchor.length() > 0 ) {
    261             result += '#' + anchor;
    262         }
    263
    264         String id = getId();
    265
    266         if (id != null) {
    267             getStack().getContext().put(id, result);
    268
    269             // add to the request and page scopes as well
    270             req.setAttribute(id, result);
    271         } else {
    272             try {
    273                 writer.write(result);
    274             } catch (IOException e) {
    275                 throw new StrutsException("IOError: " + e.getMessage(), e);
    276             }
    277         }
    278         return super.end(writer, body);
    279     }
    280
    281     @StrutsTagAttribute(description="The includeParams attribute may have the value 'none', 'get' or 'all'", defaultValue="get")
    282     public void setIncludeParams(String includeParams) {
    283         this.includeParams = includeParams;
    284     }
    285
    286     @StrutsTagAttribute(description="Set scheme attribute")
    287     public void setScheme(String scheme) {
    288         this.scheme = scheme;
    289     }
    290
    291     @StrutsTagAttribute(description="The target value to use, if not using action")
    292     public void setValue(String value) {
    293         this.value = value;
    294     }
    295
    296     @StrutsTagAttribute(description="he action generate url for, if not using value")
    297     public void setAction(String action) {
    298         this.action = action;
    299     }
    300
    301     @StrutsTagAttribute(description="The namespace to use")
    302     public void setNamespace(String namespace) {
    303         this.namespace = namespace;
    304     }
    305
    306     @StrutsTagAttribute(description="The method of action to use")
    307     public void setMethod(String method) {
    308         this.method = method;
    309     }
    310
    311     @StrutsTagAttribute(description="Whether to encode parameters", type="Boolean", defaultValue="true")
    312     public void setEncode(boolean encode) {
    313         this.encode = encode;
    314     }
    315
    316     @StrutsTagAttribute(description="Whether actual context should be included in url", type="Boolean", defaultValue="true")
    317     public void setIncludeContext(boolean includeContext) {
    318         this.includeContext = includeContext;
    319     }
    320
    321     @StrutsTagAttribute(description="The resulting portlet mode")
    322     public void setPortletMode(String portletMode) {
    323         this.portletMode = portletMode;
    324     }
    325
    326     @StrutsTagAttribute(description="The resulting portlet window state")
    327     public void setWindowState(String windowState) {
    328         this.windowState = windowState;
    329     }
    330
    331     @StrutsTagAttribute(description="Specifies if this should be a portlet render or action url")
    332     public void setPortletUrlType(String portletUrlType) {
    333         this.portletUrlType = portletUrlType;
    334     }
    335
    336     @StrutsTagAttribute(description="The anchor for this URL")
    337     public void setAnchor(String anchor) {
    338         this.anchor = anchor;
    339     }
    340
    341
    342     /***
    343      * Merge request parameters into current parameters. If a parameter is
    344      * already present, than the request parameter in the current request and value atrribute
    345      * will not override its value.
    346      *
    347      * The priority is as follows:-
    348      * <ul>
    349      *  <li>parameter from the current request (least priority)</li>
    350      *  <li>parameter form the value attribute (more priority)</li>
    351      *  <li>parameter from the param tag (most priority)</li>
    352      * </ul>
    353      *
    354      * @param value the value attribute (url to be generated by this component)
    355      * @param parameters component parameters
    356      * @param contextParameters request parameters
    357      */
    358     protected void mergeRequestParameters(String value, Map parameters, Map contextParameters){
    359
    360         Map mergedParams = new LinkedHashMap(contextParameters);
    361
    362         // Merge contextParameters (from current request) with parameters specified in value attribute
    363         // eg. value="someAction.action?id=someId&venue=someVenue"
    364         // where the parameters specified in value attribute takes priority.
    365
    366         if (value != null && value.trim().length() > 0 && value.indexOf("?") > 0) {
    367             mergedParams = new LinkedHashMap();
    368
    369             String queryString = value.substring(value.indexOf("?")+1);
    370
    371             mergedParams = UrlHelper.parseQueryString(queryString);
    372             for (Iterator iterator = contextParameters.entrySet().iterator(); iterator.hasNext();) {
    373                 Map.Entry entry = (Map.Entry) iterator.next();
    374                 Object key = entry.getKey();
    375
    376                 if (!mergedParams.containsKey(key)) {
    377                     mergedParams.put(key, entry.getValue());
    378                 }
    379             }
    380         }
    381
    382
    383         // Merge parameters specified in value attribute
    384         // eg. value="someAction.action?id=someId&venue=someVenue"
    385         // with parameters specified though param tag
    386         // eg. <param name="id" value="%{'someId'}" />
    387         // where parameters specified through param tag takes priority.
    388
    389         for (Iterator iterator = mergedParams.entrySet().iterator(); iterator.hasNext();) {
    390             Map.Entry entry = (Map.Entry) iterator.next();
    391             Object key = entry.getKey();
    392
    393             if (!parameters.containsKey(key)) {
    394                 parameters.put(key, entry.getValue());
    395             }
    396         }
    397     }
    398
    399     public static interface ExtraParameterProvider {
    400         public Map getExtraParameters();
    401     }
    402 }