View Javadoc

1   /*
2    * $Id: URL.java 557614 2007-07-19 13:28:49Z jholmes $
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;s: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="The action to generate the 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 }