View Javadoc

1   /*
2    * $Id: PortletUrlHelper.java 651946 2008-04-27 13:41:38Z apetrelli $
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  
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.struts2.StrutsException;
39  import org.apache.struts2.portlet.PortletActionConstants;
40  import org.apache.struts2.portlet.context.PortletActionContext;
41  
42  import com.opensymphony.xwork2.util.TextUtils;
43  import com.opensymphony.xwork2.util.logging.Logger;
44  import com.opensymphony.xwork2.util.logging.LoggerFactory;
45  
46  /***
47   * Helper class for creating Portlet URLs. Portlet URLs are fundamentally different from regular
48   * servlet URLs since they never target the application itself; all requests go through the portlet
49   * container and must therefore be programatically constructed using the
50   * {@link javax.portlet.RenderResponse#createActionURL()} and
51   * {@link javax.portlet.RenderResponse#createRenderURL()} APIs.
52   *
53   */
54  public class PortletUrlHelper {
55      public static final String ENCODING = "UTF-8";
56  
57      private static final Logger LOG = LoggerFactory.getLogger(PortletUrlHelper.class);
58  
59      /***
60       * Create a portlet URL with for the specified action and namespace.
61       *
62       * @param action The action the URL should invoke.
63       * @param namespace The namespace of the action to invoke.
64       * @param method The method of the action to invoke.
65       * @param params The parameters of the URL.
66       * @param type The type of the url, either <tt>action</tt> or <tt>render</tt>
67       * @param mode The PortletMode of the URL.
68       * @param state The WindowState of the URL.
69       * @return The URL String.
70       */
71      public static String buildUrl(String action, String namespace, String method, Map params,
72              String type, String mode, String state) {
73          return buildUrl(action, namespace, method, params, null, type, mode, state,
74                  true, true);
75      }
76  
77      /***
78       * Create a portlet URL with for the specified action and namespace.
79       *
80       * @see #buildUrl(String, String, Map, String, String, String)
81       */
82      public static String buildUrl(String action, String namespace, String method, Map params,
83              String scheme, String type, String portletMode, String windowState,
84              boolean includeContext, boolean encodeResult) {
85      	StringBuffer resultingAction = new StringBuffer();
86          RenderRequest request = PortletActionContext.getRenderRequest();
87          RenderResponse response = PortletActionContext.getRenderResponse();
88          LOG.debug("Creating url. Action = " + action + ", Namespace = "
89                  + namespace + ", Type = " + type);
90          namespace = prependNamespace(namespace, portletMode);
91          if (!TextUtils.stringSet(portletMode)) {
92              portletMode = PortletActionContext.getRenderRequest().getPortletMode().toString();
93          }
94          String result = null;
95          int paramStartIndex = action.indexOf('?');
96          if (paramStartIndex > 0) {
97              String value = action;
98              action = value.substring(0, value.indexOf('?'));
99              String queryStr = value.substring(paramStartIndex + 1);
100             StringTokenizer tok = new StringTokenizer(queryStr, "&");
101             while (tok.hasMoreTokens()) {
102                 String paramVal = tok.nextToken();
103                 String key = paramVal.substring(0, paramVal.indexOf('='));
104                 String val = paramVal.substring(paramVal.indexOf('=') + 1);
105                 params.put(key, new String[] { val });
106             }
107         }
108         if (TextUtils.stringSet(namespace)) {
109             resultingAction.append(namespace);
110             if(!action.startsWith("/") && !namespace.endsWith("/")) {
111                 resultingAction.append("/");
112             }
113         }
114         resultingAction.append(action);
115         if(TextUtils.stringSet(method)) {
116         	resultingAction.append("!").append(method);
117         }
118         LOG.debug("Resulting actionPath: " + resultingAction);
119         params.put(PortletActionConstants.ACTION_PARAM, new String[] { resultingAction.toString() });
120 
121         PortletURL url = null;
122         if ("action".equalsIgnoreCase(type)) {
123             LOG.debug("Creating action url");
124             url = response.createActionURL();
125         } else {
126             LOG.debug("Creating render url");
127             url = response.createRenderURL();
128         }
129 
130         params.put(PortletActionConstants.MODE_PARAM, portletMode);
131         url.setParameters(ensureParamsAreStringArrays(params));
132 
133         if ("HTTPS".equalsIgnoreCase(scheme)) {
134             try {
135                 url.setSecure(true);
136             } catch (PortletSecurityException e) {
137                 LOG.error("Cannot set scheme to https", e);
138             }
139         }
140         try {
141             url.setPortletMode(getPortletMode(request, portletMode));
142             url.setWindowState(getWindowState(request, windowState));
143         } catch (Exception e) {
144             LOG.error("Unable to set mode or state:" + e.getMessage(), e);
145         }
146         result = url.toString();
147         // TEMP BUG-WORKAROUND FOR DOUBLE ESCAPING OF AMPERSAND
148         if(result.indexOf("&amp;") >= 0) {
149             result = result.replace("&amp;", "&");
150         }
151         return result;
152 
153     }
154 
155     /***
156      *
157      * Prepend the namespace configuration for the specified namespace and PortletMode.
158      *
159      * @param namespace The base namespace.
160      * @param portletMode The PortletMode.
161      *
162      * @return prepended namespace.
163      */
164     private static String prependNamespace(String namespace, String portletMode) {
165         StringBuffer sb = new StringBuffer();
166         PortletMode mode = PortletActionContext.getRenderRequest().getPortletMode();
167         if(TextUtils.stringSet(portletMode)) {
168             mode = new PortletMode(portletMode);
169         }
170         String portletNamespace = PortletActionContext.getPortletNamespace();
171         String modeNamespace = (String)PortletActionContext.getModeNamespaceMap().get(mode);
172         LOG.debug("PortletNamespace: " + portletNamespace + ", modeNamespace: " + modeNamespace);
173         if(TextUtils.stringSet(portletNamespace)) {
174             sb.append(portletNamespace);
175         }
176         if(TextUtils.stringSet(modeNamespace)) {
177             if(!modeNamespace.startsWith("/")) {
178                 sb.append("/");
179             }
180             sb.append(modeNamespace);
181         }
182         if(TextUtils.stringSet(namespace)) {
183             if(!namespace.startsWith("/")) {
184                 sb.append("/");
185             }
186             sb.append(namespace);
187         }
188         LOG.debug("Resulting namespace: " + sb);
189         return sb.toString();
190     }
191 
192     /***
193      * Encode an url to a non Struts action resource, like stylesheet, image or
194      * servlet.
195      *
196      * @param value
197      * @return encoded url to non Struts action resources.
198      */
199     public static String buildResourceUrl(String value, Map params) {
200         StringBuffer sb = new StringBuffer();
201         // Relative URLs are not allowed in a portlet
202         if (!value.startsWith("/")) {
203             sb.append("/");
204         }
205         sb.append(value);
206         if(params != null && params.size() > 0) {
207             sb.append("?");
208             Iterator it = params.keySet().iterator();
209             try {
210             while(it.hasNext()) {
211                 String key = (String)it.next();
212                 String val = (String)params.get(key);
213 
214                 sb.append(URLEncoder.encode(key, ENCODING)).append("=");
215                 sb.append(URLEncoder.encode(val, 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 params) {
237         Map result = null;
238         if (params != null) {
239             result = new LinkedHashMap(params.size());
240             Iterator it = params.keySet().iterator();
241             while (it.hasNext()) {
242                 Object key = it.next();
243                 Object val = params.get(key);
244                 if (val instanceof String[]) {
245                     result.put(key, val);
246                 } else {
247                     result.put(key, 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 }