001: /*
002: * $Id: URL.java 569304 2007-08-24 09:12:20Z nilsga $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts2.components;
022:
023: import java.io.IOException;
024: import java.io.Writer;
025: import java.util.Collections;
026: import java.util.Iterator;
027: import java.util.LinkedHashMap;
028: import java.util.Map;
029:
030: import javax.servlet.http.HttpServletRequest;
031: import javax.servlet.http.HttpServletResponse;
032:
033: import org.apache.commons.logging.Log;
034: import org.apache.commons.logging.LogFactory;
035: import org.apache.struts2.views.annotations.StrutsTag;
036: import org.apache.struts2.views.annotations.StrutsTagAttribute;
037: import org.apache.struts2.StrutsException;
038: import org.apache.struts2.StrutsConstants;
039: import org.apache.struts2.dispatcher.Dispatcher;
040: import org.apache.struts2.portlet.context.PortletActionContext;
041: import org.apache.struts2.portlet.util.PortletUrlHelper;
042: import org.apache.struts2.views.util.UrlHelper;
043:
044: import com.opensymphony.xwork2.inject.Inject;
045: import com.opensymphony.xwork2.util.ValueStack;
046:
047: /**
048: * <!-- START SNIPPET: javadoc -->
049: *
050: * <p>This tag is used to create a URL.</p>
051: *
052: * <p>You can use the "param" tag inside the body to provide
053: * additional request parameters.</p>
054: *
055: * <b>NOTE:</b>
056: * <p>By default request parameters will be separated using escaped ampersands (i.e., &amp;).
057: * This is necessary for XHTML compliance, however, when using the URL generated by this tag
058: * with the <s:property> tag, the <b>escapeAmp</b> attribute should be used to disable
059: * ampersand escaping.</p>
060: *
061: * <b>NOTE:</b>
062: * <p>When includeParams is 'all' or 'get', the parameter defined in param tag will take
063: * precedence and will not be overriden if they exists in the parameter submitted. For
064: * example, in Example 3 below, if there is a id parameter in the url where the page this
065: * tag is included like http://<host>:<port>/<context>/editUser.action?id=3333&name=John
066: * the generated url will be http://<host>:<port>/context>/editUser.action?id=22&name=John
067: * cause the parameter defined in the param tag will take precedence.</p>
068: *
069: * <!-- END SNIPPET: javadoc -->
070: *
071: *
072: * <!-- START SNIPPET: params -->
073: *
074: * <ul>
075: * <li>action (String) - (value or action choose either one, if both exist value takes precedence) action's name (alias) <li>
076: * <li>value (String) - (value or action choose either one, if both exist value takes precedence) the url itself</li>
077: * <li>scheme (String) - http scheme (http, https) defaults to the scheme this request is in</li>
078: * <li>namespace - action's namespace</li>
079: * <li>method (String) - action's method name, defaults to 'execute'</li>
080: * <li>encode (Boolean) - url encode the generated url. Defaults to 'true'.</li>
081: * <li>includeParams (String) - The includeParams attribute may have the value 'none', 'get' or 'all'. Defaults to 'get'.
082: * none - include no parameters in the URL
083: * get - include only GET parameters in the URL (default)
084: * all - include both GET and POST parameters in the URL
085: * </li>
086: * <li>includeContext (Boolean) - Specifies whether to include the web app context path. Defaults to 'true'.</li>
087: * <li>escapeAmp (Boolean) - Specifies whether to escape ampersand (&) to (&amp;) or not. Defaults to 'true'.</li>
088: * <li>portletMode (String) - The resulting portlet mode.</li>
089: * <li>windowState (String) - The resulting portlet window state.</li>
090: * <li>portletUrlType (String) - Specifies if this should be a portlet render or action URL.</li>
091: * <li>forceAddSchemeHostAndPort (Boolean) - Specifies whether to force the addition of scheme, host and port or not.</li>
092: * </ul>
093: *
094: * <!-- END SNIPPET: params -->
095: *
096: * <p/> <b>Examples</b>
097: * <pre>
098: * <!-- START SNIPPET: example -->
099: *
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,
159: HttpServletResponse res) {
160: super (stack);
161: this .req = req;
162: this .res = res;
163: }
164:
165: @Inject(StrutsConstants.STRUTS_URL_INCLUDEPARAMS)
166: public void setUrlIncludeParams(String urlIncludeParams) {
167: this .urlIncludeParams = urlIncludeParams;
168: }
169:
170: @Inject(required=false)
171: public void setExtraParameterProvider(
172: ExtraParameterProvider provider) {
173: this .extraParameterProvider = provider;
174: }
175:
176: public boolean start(Writer writer) {
177: boolean result = super .start(writer);
178:
179: if (value != null) {
180: value = findString(value);
181: }
182:
183: // no explicit url set so attach params from current url, do
184: // this at start so body params can override any of these they wish.
185: try {
186: // ww-1266
187: String includeParams = (urlIncludeParams != null ? urlIncludeParams
188: .toLowerCase()
189: : GET);
190:
191: if (this .includeParams != null) {
192: includeParams = findString(this .includeParams);
193: }
194:
195: if (NONE.equalsIgnoreCase(includeParams)) {
196: mergeRequestParameters(value, parameters,
197: Collections.EMPTY_MAP);
198: } else if (ALL.equalsIgnoreCase(includeParams)) {
199: mergeRequestParameters(value, parameters, req
200: .getParameterMap());
201:
202: // for ALL also include GET parameters
203: includeGetParameters();
204: includeExtraParameters();
205: } else if (GET.equalsIgnoreCase(includeParams)
206: || (includeParams == null && value == null && action == null)) {
207: includeGetParameters();
208: includeExtraParameters();
209: } else if (includeParams != null) {
210: LOG
211: .warn("Unknown value for includeParams parameter to URL tag: "
212: + includeParams);
213: }
214: } catch (Exception e) {
215: LOG
216: .warn("Unable to put request parameters ("
217: + req.getQueryString()
218: + ") into parameter map.", e);
219: }
220:
221: return result;
222: }
223:
224: private void includeExtraParameters() {
225: if (extraParameterProvider != null) {
226: mergeRequestParameters(value, parameters,
227: extraParameterProvider.getExtraParameters());
228: }
229: }
230:
231: private void includeGetParameters() {
232: if (!(Dispatcher.getInstance().isPortletSupportActive() && PortletActionContext
233: .isPortletRequest())) {
234: String query = extractQueryString();
235: mergeRequestParameters(value, parameters, UrlHelper
236: .parseQueryString(query));
237: }
238: }
239:
240: private String extractQueryString() {
241: // Parse the query string to make sure that the parameters come from the query, and not some posted data
242: String query = req.getQueryString();
243: if (query == null) {
244: query = (String) req
245: .getAttribute("javax.servlet.forward.query_string");
246: }
247:
248: if (query != null) {
249: // Remove possible #foobar suffix
250: int idx = query.lastIndexOf('#');
251:
252: if (idx != -1) {
253: query = query.substring(0, idx);
254: }
255: }
256: return query;
257: }
258:
259: public boolean end(Writer writer, String body) {
260: String scheme = req.getScheme();
261:
262: if (this .scheme != null) {
263: scheme = this .scheme;
264: }
265:
266: String result;
267: if (value == null && action != null) {
268: if (Dispatcher.getInstance().isPortletSupportActive()
269: && PortletActionContext.isPortletRequest()) {
270: result = PortletUrlHelper.buildUrl(action, namespace,
271: method, parameters, portletUrlType,
272: portletMode, windowState);
273: } else {
274: result = determineActionURL(action, namespace, method,
275: req, res, parameters, scheme, includeContext,
276: encode, forceAddSchemeHostAndPort, escapeAmp);
277: }
278: } else {
279: if (Dispatcher.getInstance().isPortletSupportActive()
280: && PortletActionContext.isPortletRequest()) {
281: result = PortletUrlHelper.buildResourceUrl(value,
282: parameters);
283: } else {
284: String _value = value;
285:
286: // We don't include the request parameters cause they would have been
287: // prioritised before this [in start(Writer) method]
288: if (_value != null && _value.indexOf("?") > 0) {
289: _value = _value.substring(0, _value.indexOf("?"));
290: }
291: result = UrlHelper.buildUrl(_value, req, res,
292: parameters, scheme, includeContext, encode,
293: forceAddSchemeHostAndPort, escapeAmp);
294: }
295: }
296: if (anchor != null && anchor.length() > 0) {
297: result += '#' + anchor;
298: }
299:
300: String id = getId();
301:
302: if (id != null) {
303: getStack().getContext().put(id, result);
304:
305: // add to the request and page scopes as well
306: req.setAttribute(id, result);
307: } else {
308: try {
309: writer.write(result);
310: } catch (IOException e) {
311: throw new StrutsException("IOError: " + e.getMessage(),
312: e);
313: }
314: }
315: return super .end(writer, body);
316: }
317:
318: @StrutsTagAttribute(description="The includeParams attribute may have the value 'none', 'get' or 'all'",defaultValue="get")
319: public void setIncludeParams(String includeParams) {
320: this .includeParams = includeParams;
321: }
322:
323: @StrutsTagAttribute(description="Set scheme attribute")
324: public void setScheme(String scheme) {
325: this .scheme = scheme;
326: }
327:
328: @StrutsTagAttribute(description="The target value to use, if not using action")
329: public void setValue(String value) {
330: this .value = value;
331: }
332:
333: @StrutsTagAttribute(description="The action to generate the URL for, if not using value")
334: public void setAction(String action) {
335: this .action = action;
336: }
337:
338: @StrutsTagAttribute(description="The namespace to use")
339: public void setNamespace(String namespace) {
340: this .namespace = namespace;
341: }
342:
343: @StrutsTagAttribute(description="The method of action to use")
344: public void setMethod(String method) {
345: this .method = method;
346: }
347:
348: @StrutsTagAttribute(description="Whether to encode parameters",type="Boolean",defaultValue="true")
349: public void setEncode(boolean encode) {
350: this .encode = encode;
351: }
352:
353: @StrutsTagAttribute(description="Whether actual context should be included in URL",type="Boolean",defaultValue="true")
354: public void setIncludeContext(boolean includeContext) {
355: this .includeContext = includeContext;
356: }
357:
358: @StrutsTagAttribute(description="The resulting portlet mode")
359: public void setPortletMode(String portletMode) {
360: this .portletMode = portletMode;
361: }
362:
363: @StrutsTagAttribute(description="The resulting portlet window state")
364: public void setWindowState(String windowState) {
365: this .windowState = windowState;
366: }
367:
368: @StrutsTagAttribute(description="Specifies if this should be a portlet render or action URL. Default is \"render\". To create an action URL, use \"action\".")
369: public void setPortletUrlType(String portletUrlType) {
370: this .portletUrlType = portletUrlType;
371: }
372:
373: @StrutsTagAttribute(description="The anchor for this URL")
374: public void setAnchor(String anchor) {
375: this .anchor = anchor;
376: }
377:
378: @StrutsTagAttribute(description="Specifies whether to escape ampersand (&) to (&) or not",type="Boolean",defaultValue="true")
379: public void setEscapeAmp(boolean escapeAmp) {
380: this .escapeAmp = escapeAmp;
381: }
382:
383: @StrutsTagAttribute(description="Specifies whether to force the addition of scheme, host and port or not",type="Boolean",defaultValue="false")
384: public void setForceAddSchemeHostAndPort(
385: boolean forceAddSchemeHostAndPort) {
386: this .forceAddSchemeHostAndPort = forceAddSchemeHostAndPort;
387: }
388:
389: /**
390: * Merge request parameters into current parameters. If a parameter is
391: * already present, than the request parameter in the current request and value atrribute
392: * will not override its value.
393: *
394: * The priority is as follows:-
395: * <ul>
396: * <li>parameter from the current request (least priority)</li>
397: * <li>parameter form the value attribute (more priority)</li>
398: * <li>parameter from the param tag (most priority)</li>
399: * </ul>
400: *
401: * @param value the value attribute (url to be generated by this component)
402: * @param parameters component parameters
403: * @param contextParameters request parameters
404: */
405: protected void mergeRequestParameters(String value, Map parameters,
406: Map contextParameters) {
407:
408: Map mergedParams = new LinkedHashMap(contextParameters);
409:
410: // Merge contextParameters (from current request) with parameters specified in value attribute
411: // eg. value="someAction.action?id=someId&venue=someVenue"
412: // where the parameters specified in value attribute takes priority.
413:
414: if (value != null && value.trim().length() > 0
415: && value.indexOf("?") > 0) {
416: mergedParams = new LinkedHashMap();
417:
418: String queryString = value
419: .substring(value.indexOf("?") + 1);
420:
421: mergedParams = UrlHelper.parseQueryString(queryString);
422: for (Iterator iterator = contextParameters.entrySet()
423: .iterator(); iterator.hasNext();) {
424: Map.Entry entry = (Map.Entry) iterator.next();
425: Object key = entry.getKey();
426:
427: if (!mergedParams.containsKey(key)) {
428: mergedParams.put(key, entry.getValue());
429: }
430: }
431: }
432:
433: // Merge parameters specified in value attribute
434: // eg. value="someAction.action?id=someId&venue=someVenue"
435: // with parameters specified though param tag
436: // eg. <param name="id" value="%{'someId'}" />
437: // where parameters specified through param tag takes priority.
438:
439: for (Iterator iterator = mergedParams.entrySet().iterator(); iterator
440: .hasNext();) {
441: Map.Entry entry = (Map.Entry) iterator.next();
442: Object key = entry.getKey();
443:
444: if (!parameters.containsKey(key)) {
445: parameters.put(key, entry.getValue());
446: }
447: }
448: }
449:
450: public static interface ExtraParameterProvider {
451: public Map getExtraParameters();
452: }
453: }
|