1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;).
57 * This is necessary for XHTML compliance, however, when using the URL generated by this tag
58 * with the <s:property> 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 (&) to (&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 * <-- Example 1 -->
101 * <s:url value="editGadget.action">
102 * <s:param name="id" value="%{selected}" />
103 * </s:url>
104 *
105 * <-- Example 2 -->
106 * <s:url action="editGadget">
107 * <s:param name="id" value="%{selected}" />
108 * </s:url>
109 *
110 * <-- Example 3-->
111 * <s:url includeParams="get" >
112 * <s:param name="id" value="%{'22'}" />
113 * </s:url>
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
182
183 try {
184
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
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
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
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
266
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
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 (&) to (&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
386
387
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
407
408
409
410
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 }