001: /*
002: * $Id: PortletUrlHelper.java 574349 2007-09-10 19:55:07Z 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.portlet.util;
022:
023: import java.io.UnsupportedEncodingException;
024: import java.net.URLEncoder;
025: import java.util.Iterator;
026: import java.util.LinkedHashMap;
027: import java.util.Map;
028: import java.util.StringTokenizer;
029:
030: import javax.portlet.PortletMode;
031: import javax.portlet.PortletSecurityException;
032: import javax.portlet.PortletURL;
033: import javax.portlet.RenderRequest;
034: import javax.portlet.RenderResponse;
035: import javax.portlet.WindowState;
036:
037: import org.apache.commons.logging.Log;
038: import org.apache.commons.logging.LogFactory;
039: import org.apache.struts2.StrutsException;
040: import org.apache.struts2.portlet.PortletActionConstants;
041: import org.apache.struts2.portlet.context.PortletActionContext;
042: import com.opensymphony.xwork2.util.TextUtils;
043:
044: /**
045: * Helper class for creating Portlet URLs. Portlet URLs are fundamentally different from regular
046: * servlet URLs since they never target the application itself; all requests go through the portlet
047: * container and must therefore be programatically constructed using the
048: * {@link javax.portlet.RenderResponse#createActionURL()} and
049: * {@link javax.portlet.RenderResponse#createRenderURL()} APIs.
050: *
051: */
052: public class PortletUrlHelper {
053: public static final String ENCODING = "UTF-8";
054:
055: private static final Log LOG = LogFactory
056: .getLog(PortletUrlHelper.class);
057:
058: /**
059: * Create a portlet URL with for the specified action and namespace.
060: *
061: * @param action The action the URL should invoke.
062: * @param namespace The namespace of the action to invoke.
063: * @param method The method of the action to invoke.
064: * @param params The parameters of the URL.
065: * @param type The type of the url, either <tt>action</tt> or <tt>render</tt>
066: * @param mode The PortletMode of the URL.
067: * @param state The WindowState of the URL.
068: * @return The URL String.
069: */
070: public static String buildUrl(String action, String namespace,
071: String method, Map params, String type, String mode,
072: String state) {
073: return buildUrl(action, namespace, method, params, null, type,
074: mode, state, true, true);
075: }
076:
077: /**
078: * Create a portlet URL with for the specified action and namespace.
079: *
080: * @see #buildUrl(String, String, Map, String, String, String)
081: */
082: public static String buildUrl(String action, String namespace,
083: String method, Map params, String scheme, String type,
084: String portletMode, String windowState,
085: boolean includeContext, boolean encodeResult) {
086: StringBuffer resultingAction = new StringBuffer();
087: RenderRequest request = PortletActionContext.getRenderRequest();
088: RenderResponse response = PortletActionContext
089: .getRenderResponse();
090: LOG.debug("Creating url. Action = " + action + ", Namespace = "
091: + namespace + ", Type = " + type);
092: namespace = prependNamespace(namespace, portletMode);
093: if (!TextUtils.stringSet(portletMode)) {
094: portletMode = PortletActionContext.getRenderRequest()
095: .getPortletMode().toString();
096: }
097: String result = null;
098: int paramStartIndex = action.indexOf('?');
099: if (paramStartIndex > 0) {
100: String value = action;
101: action = value.substring(0, value.indexOf('?'));
102: String queryStr = value.substring(paramStartIndex + 1);
103: StringTokenizer tok = new StringTokenizer(queryStr, "&");
104: while (tok.hasMoreTokens()) {
105: String paramVal = tok.nextToken();
106: String key = paramVal.substring(0, paramVal
107: .indexOf('='));
108: String val = paramVal
109: .substring(paramVal.indexOf('=') + 1);
110: params.put(key, new String[] { val });
111: }
112: }
113: if (TextUtils.stringSet(namespace)) {
114: resultingAction.append(namespace);
115: if (!action.startsWith("/") && !namespace.endsWith("/")) {
116: resultingAction.append("/");
117: }
118: }
119: resultingAction.append(action);
120: if (TextUtils.stringSet(method)) {
121: resultingAction.append("!").append(method);
122: }
123: LOG.debug("Resulting actionPath: " + resultingAction);
124: params.put(PortletActionConstants.ACTION_PARAM,
125: new String[] { resultingAction.toString() });
126:
127: PortletURL url = null;
128: if ("action".equalsIgnoreCase(type)) {
129: LOG.debug("Creating action url");
130: url = response.createActionURL();
131: } else {
132: LOG.debug("Creating render url");
133: url = response.createRenderURL();
134: }
135:
136: params.put(PortletActionConstants.MODE_PARAM, portletMode);
137: url.setParameters(ensureParamsAreStringArrays(params));
138:
139: if ("HTTPS".equalsIgnoreCase(scheme)) {
140: try {
141: url.setSecure(true);
142: } catch (PortletSecurityException e) {
143: LOG.error("Cannot set scheme to https", e);
144: }
145: }
146: try {
147: url.setPortletMode(getPortletMode(request, portletMode));
148: url.setWindowState(getWindowState(request, windowState));
149: } catch (Exception e) {
150: LOG.error("Unable to set mode or state:" + e.getMessage(),
151: e);
152: }
153: result = url.toString();
154: // TEMP BUG-WORKAROUND FOR DOUBLE ESCAPING OF AMPERSAND
155: if (result.indexOf("&") >= 0) {
156: result = result.replace("&", "&");
157: }
158: return result;
159:
160: }
161:
162: /**
163: *
164: * Prepend the namespace configuration for the specified namespace and PortletMode.
165: *
166: * @param namespace The base namespace.
167: * @param portletMode The PortletMode.
168: *
169: * @return prepended namespace.
170: */
171: private static String prependNamespace(String namespace,
172: String portletMode) {
173: StringBuffer sb = new StringBuffer();
174: PortletMode mode = PortletActionContext.getRenderRequest()
175: .getPortletMode();
176: if (TextUtils.stringSet(portletMode)) {
177: mode = new PortletMode(portletMode);
178: }
179: String portletNamespace = PortletActionContext
180: .getPortletNamespace();
181: String modeNamespace = (String) PortletActionContext
182: .getModeNamespaceMap().get(mode);
183: LOG.debug("PortletNamespace: " + portletNamespace
184: + ", modeNamespace: " + modeNamespace);
185: if (TextUtils.stringSet(portletNamespace)) {
186: sb.append(portletNamespace);
187: }
188: if (TextUtils.stringSet(modeNamespace)) {
189: if (!modeNamespace.startsWith("/")) {
190: sb.append("/");
191: }
192: sb.append(modeNamespace);
193: }
194: if (TextUtils.stringSet(namespace)) {
195: if (!namespace.startsWith("/")) {
196: sb.append("/");
197: }
198: sb.append(namespace);
199: }
200: LOG.debug("Resulting namespace: " + sb);
201: return sb.toString();
202: }
203:
204: /**
205: * Encode an url to a non Struts action resource, like stylesheet, image or
206: * servlet.
207: *
208: * @param value
209: * @return encoded url to non Struts action resources.
210: */
211: public static String buildResourceUrl(String value, Map params) {
212: StringBuffer sb = new StringBuffer();
213: // Relative URLs are not allowed in a portlet
214: if (!value.startsWith("/")) {
215: sb.append("/");
216: }
217: sb.append(value);
218: if (params != null && params.size() > 0) {
219: sb.append("?");
220: Iterator it = params.keySet().iterator();
221: try {
222: while (it.hasNext()) {
223: String key = (String) it.next();
224: String val = (String) params.get(key);
225:
226: sb.append(URLEncoder.encode(key, ENCODING)).append(
227: "=");
228: sb.append(URLEncoder.encode(val, ENCODING));
229: if (it.hasNext()) {
230: sb.append("&");
231: }
232: }
233: } catch (UnsupportedEncodingException e) {
234: throw new StrutsException("Encoding " + ENCODING
235: + " not found");
236: }
237: }
238: RenderResponse resp = PortletActionContext.getRenderResponse();
239: RenderRequest req = PortletActionContext.getRenderRequest();
240: return resp.encodeURL(req.getContextPath() + sb.toString());
241: }
242:
243: /**
244: * Will ensure that all entries in <code>params</code> are String arrays,
245: * as requried by the setParameters on the PortletURL.
246: *
247: * @param params The parameters to the URL.
248: * @return A Map with all parameters as String arrays.
249: */
250: public static Map ensureParamsAreStringArrays(Map params) {
251: Map result = null;
252: if (params != null) {
253: result = new LinkedHashMap(params.size());
254: Iterator it = params.keySet().iterator();
255: while (it.hasNext()) {
256: Object key = it.next();
257: Object val = params.get(key);
258: if (val instanceof String[]) {
259: result.put(key, val);
260: } else {
261: result.put(key, new String[] { val.toString() });
262: }
263: }
264: }
265: return result;
266: }
267:
268: /**
269: * Convert the given String to a WindowState object.
270: *
271: * @param portletReq The RenderRequest.
272: * @param windowState The WindowState as a String.
273: * @return The WindowState that mathces the <tt>windowState</tt> String, or if
274: * the Sring is blank, the current WindowState.
275: */
276: private static WindowState getWindowState(RenderRequest portletReq,
277: String windowState) {
278: WindowState state = portletReq.getWindowState();
279: if (TextUtils.stringSet(windowState)) {
280: state = portletReq.getWindowState();
281: if ("maximized".equalsIgnoreCase(windowState)) {
282: state = WindowState.MAXIMIZED;
283: } else if ("normal".equalsIgnoreCase(windowState)) {
284: state = WindowState.NORMAL;
285: } else if ("minimized".equalsIgnoreCase(windowState)) {
286: state = WindowState.MINIMIZED;
287: }
288: }
289: if (state == null) {
290: state = WindowState.NORMAL;
291: }
292: return state;
293: }
294:
295: /**
296: * Convert the given String to a PortletMode object.
297: *
298: * @param portletReq The RenderRequest.
299: * @param portletMode The PortletMode as a String.
300: * @return The PortletMode that mathces the <tt>portletMode</tt> String, or if
301: * the Sring is blank, the current PortletMode.
302: */
303: private static PortletMode getPortletMode(RenderRequest portletReq,
304: String portletMode) {
305: PortletMode mode = portletReq.getPortletMode();
306:
307: if (TextUtils.stringSet(portletMode)) {
308: mode = portletReq.getPortletMode();
309: if ("edit".equalsIgnoreCase(portletMode)) {
310: mode = PortletMode.EDIT;
311: } else if ("view".equalsIgnoreCase(portletMode)) {
312: mode = PortletMode.VIEW;
313: } else if ("help".equalsIgnoreCase(portletMode)) {
314: mode = PortletMode.HELP;
315: }
316: }
317: if (mode == null) {
318: mode = PortletMode.VIEW;
319: }
320: return mode;
321: }
322: }
|