View Javadoc

1   /*
2    * $Id: PortletUrlHelper.java 719714 2008-11-21 21:09:13Z 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  
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         // TEMP BUG-WORKAROUND FOR DOUBLE ESCAPING OF AMPERSAND
149         if(result.indexOf("&amp;") >= 0) {
150             result = result.replace("&amp;", "&");
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         // Relative URLs are not allowed in a portlet
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 }