View Javadoc

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