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.portlet.util;
22
23 import java.io.UnsupportedEncodingException;
24 import java.net.URLEncoder;
25 import java.util.Iterator;
26 import java.util.LinkedHashMap;
27 import java.util.Map;
28 import java.util.StringTokenizer;
29
30 import javax.portlet.PortletMode;
31 import javax.portlet.PortletSecurityException;
32 import javax.portlet.PortletURL;
33 import javax.portlet.RenderRequest;
34 import javax.portlet.RenderResponse;
35 import javax.portlet.WindowState;
36
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39 import org.apache.struts2.StrutsException;
40 import org.apache.struts2.portlet.PortletActionConstants;
41 import org.apache.struts2.portlet.context.PortletActionContext;
42 import com.opensymphony.xwork2.util.TextUtils;
43
44 /***
45 * Helper class for creating Portlet URLs. Portlet URLs are fundamentally different from regular
46 * servlet URLs since they never target the application itself; all requests go through the portlet
47 * container and must therefore be programatically constructed using the
48 * {@link javax.portlet.RenderResponse#createActionURL()} and
49 * {@link javax.portlet.RenderResponse#createRenderURL()} APIs.
50 *
51 */
52 public class PortletUrlHelper {
53 public static final String ENCODING = "UTF-8";
54
55 private static final Log LOG = LogFactory.getLog(PortletUrlHelper.class);
56
57 /***
58 * Create a portlet URL with for the specified action and namespace.
59 *
60 * @param action The action the URL should invoke.
61 * @param namespace The namespace of the action to invoke.
62 * @param method The method of the action to invoke.
63 * @param params The parameters of the URL.
64 * @param type The type of the url, either <tt>action</tt> or <tt>render</tt>
65 * @param mode The PortletMode of the URL.
66 * @param state The WindowState of the URL.
67 * @return The URL String.
68 */
69 public static String buildUrl(String action, String namespace, String method, Map params,
70 String type, String mode, String state) {
71 return buildUrl(action, namespace, method, params, null, type, mode, state,
72 true, true);
73 }
74
75 /***
76 * Create a portlet URL with for the specified action and namespace.
77 *
78 * @see #buildUrl(String, String, Map, String, String, String)
79 */
80 public static String buildUrl(String action, String namespace, String method, Map params,
81 String scheme, String type, String portletMode, String windowState,
82 boolean includeContext, boolean encodeResult) {
83 StringBuffer resultingAction = new StringBuffer();
84 RenderRequest request = PortletActionContext.getRenderRequest();
85 RenderResponse response = PortletActionContext.getRenderResponse();
86 LOG.debug("Creating url. Action = " + action + ", Namespace = "
87 + namespace + ", Type = " + type);
88 namespace = prependNamespace(namespace, portletMode);
89 if (!TextUtils.stringSet(portletMode)) {
90 portletMode = PortletActionContext.getRenderRequest().getPortletMode().toString();
91 }
92 String result = null;
93 int paramStartIndex = action.indexOf('?');
94 if (paramStartIndex > 0) {
95 String value = action;
96 action = value.substring(0, value.indexOf('?'));
97 String queryStr = value.substring(paramStartIndex + 1);
98 StringTokenizer tok = new StringTokenizer(queryStr, "&");
99 while (tok.hasMoreTokens()) {
100 String paramVal = tok.nextToken();
101 String key = paramVal.substring(0, paramVal.indexOf('='));
102 String val = paramVal.substring(paramVal.indexOf('=') + 1);
103 params.put(key, new String[] { val });
104 }
105 }
106 if (TextUtils.stringSet(namespace)) {
107 resultingAction.append(namespace);
108 if(!action.startsWith("/") && !namespace.endsWith("/")) {
109 resultingAction.append("/");
110 }
111 }
112 resultingAction.append(action);
113 if(TextUtils.stringSet(method)) {
114 resultingAction.append("!").append(method);
115 }
116 LOG.debug("Resulting actionPath: " + resultingAction);
117 params.put(PortletActionConstants.ACTION_PARAM, new String[] { resultingAction.toString() });
118
119 PortletURL url = null;
120 if ("action".equalsIgnoreCase(type)) {
121 LOG.debug("Creating action url");
122 url = response.createActionURL();
123 } else {
124 LOG.debug("Creating render url");
125 url = response.createRenderURL();
126 }
127
128 params.put(PortletActionConstants.MODE_PARAM, portletMode);
129 url.setParameters(ensureParamsAreStringArrays(params));
130
131 if ("HTTPS".equalsIgnoreCase(scheme)) {
132 try {
133 url.setSecure(true);
134 } catch (PortletSecurityException e) {
135 LOG.error("Cannot set scheme to https", e);
136 }
137 }
138 try {
139 url.setPortletMode(getPortletMode(request, portletMode));
140 url.setWindowState(getWindowState(request, windowState));
141 } catch (Exception e) {
142 LOG.error("Unable to set mode or state:" + e.getMessage(), e);
143 }
144 result = url.toString();
145
146 if(result.indexOf("&") >= 0) {
147 result = result.replace("&", "&");
148 }
149 return result;
150
151 }
152
153 /***
154 *
155 * Prepend the namespace configuration for the specified namespace and PortletMode.
156 *
157 * @param namespace The base namespace.
158 * @param portletMode The PortletMode.
159 *
160 * @return prepended namespace.
161 */
162 private static String prependNamespace(String namespace, String portletMode) {
163 StringBuffer sb = new StringBuffer();
164 PortletMode mode = PortletActionContext.getRenderRequest().getPortletMode();
165 if(TextUtils.stringSet(portletMode)) {
166 mode = new PortletMode(portletMode);
167 }
168 String portletNamespace = PortletActionContext.getPortletNamespace();
169 String modeNamespace = (String)PortletActionContext.getModeNamespaceMap().get(mode);
170 LOG.debug("PortletNamespace: " + portletNamespace + ", modeNamespace: " + modeNamespace);
171 if(TextUtils.stringSet(portletNamespace)) {
172 sb.append(portletNamespace);
173 }
174 if(TextUtils.stringSet(modeNamespace)) {
175 if(!modeNamespace.startsWith("/")) {
176 sb.append("/");
177 }
178 sb.append(modeNamespace);
179 }
180 if(TextUtils.stringSet(namespace)) {
181 if(!namespace.startsWith("/")) {
182 sb.append("/");
183 }
184 sb.append(namespace);
185 }
186 LOG.debug("Resulting namespace: " + sb);
187 return sb.toString();
188 }
189
190 /***
191 * Encode an url to a non Struts action resource, like stylesheet, image or
192 * servlet.
193 *
194 * @param value
195 * @return encoded url to non Struts action resources.
196 */
197 public static String buildResourceUrl(String value, Map params) {
198 StringBuffer sb = new StringBuffer();
199
200 if (!value.startsWith("/")) {
201 sb.append("/");
202 }
203 sb.append(value);
204 if(params != null && params.size() > 0) {
205 sb.append("?");
206 Iterator it = params.keySet().iterator();
207 try {
208 while(it.hasNext()) {
209 String key = (String)it.next();
210 String val = (String)params.get(key);
211
212 sb.append(URLEncoder.encode(key, ENCODING)).append("=");
213 sb.append(URLEncoder.encode(val, ENCODING));
214 if(it.hasNext()) {
215 sb.append("&");
216 }
217 }
218 } catch (UnsupportedEncodingException e) {
219 throw new StrutsException("Encoding "+ENCODING+" not found");
220 }
221 }
222 RenderResponse resp = PortletActionContext.getRenderResponse();
223 RenderRequest req = PortletActionContext.getRenderRequest();
224 return resp.encodeURL(req.getContextPath() + sb.toString());
225 }
226
227 /***
228 * Will ensure that all entries in <code>params</code> are String arrays,
229 * as requried by the setParameters on the PortletURL.
230 *
231 * @param params The parameters to the URL.
232 * @return A Map with all parameters as String arrays.
233 */
234 public static Map ensureParamsAreStringArrays(Map params) {
235 Map result = null;
236 if (params != null) {
237 result = new LinkedHashMap(params.size());
238 Iterator it = params.keySet().iterator();
239 while (it.hasNext()) {
240 Object key = it.next();
241 Object val = params.get(key);
242 if (val instanceof String[]) {
243 result.put(key, val);
244 } else {
245 result.put(key, new String[] { val.toString() });
246 }
247 }
248 }
249 return result;
250 }
251
252 /***
253 * Convert the given String to a WindowState object.
254 *
255 * @param portletReq The RenderRequest.
256 * @param windowState The WindowState as a String.
257 * @return The WindowState that mathces the <tt>windowState</tt> String, or if
258 * the Sring is blank, the current WindowState.
259 */
260 private static WindowState getWindowState(RenderRequest portletReq,
261 String windowState) {
262 WindowState state = portletReq.getWindowState();
263 if (TextUtils.stringSet(windowState)) {
264 state = portletReq.getWindowState();
265 if ("maximized".equalsIgnoreCase(windowState)) {
266 state = WindowState.MAXIMIZED;
267 } else if ("normal".equalsIgnoreCase(windowState)) {
268 state = WindowState.NORMAL;
269 } else if ("minimized".equalsIgnoreCase(windowState)) {
270 state = WindowState.MINIMIZED;
271 }
272 }
273 if(state == null) {
274 state = WindowState.NORMAL;
275 }
276 return state;
277 }
278
279 /***
280 * Convert the given String to a PortletMode object.
281 *
282 * @param portletReq The RenderRequest.
283 * @param portletMode The PortletMode as a String.
284 * @return The PortletMode that mathces the <tt>portletMode</tt> String, or if
285 * the Sring is blank, the current PortletMode.
286 */
287 private static PortletMode getPortletMode(RenderRequest portletReq,
288 String portletMode) {
289 PortletMode mode = portletReq.getPortletMode();
290
291 if (TextUtils.stringSet(portletMode)) {
292 mode = portletReq.getPortletMode();
293 if ("edit".equalsIgnoreCase(portletMode)) {
294 mode = PortletMode.EDIT;
295 } else if ("view".equalsIgnoreCase(portletMode)) {
296 mode = PortletMode.VIEW;
297 } else if ("help".equalsIgnoreCase(portletMode)) {
298 mode = PortletMode.HELP;
299 }
300 }
301 if(mode == null) {
302 mode = PortletMode.VIEW;
303 }
304 return mode;
305 }
306 }