View Javadoc

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