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