View Javadoc

1   /*
2    * $Id: PortletUrlHelper.java 574349 2007-09-10 19:55:07Z nilsga $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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         // TEMP BUG-WORKAROUND FOR DOUBLE ESCAPING OF AMPERSAND
146         if(result.indexOf("&amp;") >= 0) {
147             result = result.replace("&amp;", "&");
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         // Relative URLs are not allowed in a portlet
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 }