1 | /* |
2 | * Copyright 2004 The Apache Software Foundation. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | package org.apache.pluto.driver.url; |
17 | |
18 | import java.util.Iterator; |
19 | import java.util.Map; |
20 | import java.util.StringTokenizer; |
21 | |
22 | import javax.portlet.PortletMode; |
23 | import javax.portlet.WindowState; |
24 | import javax.servlet.http.HttpServletRequest; |
25 | |
26 | import org.apache.pluto.util.StringUtils; |
27 | import org.apache.commons.logging.Log; |
28 | import org.apache.commons.logging.LogFactory; |
29 | |
30 | /** |
31 | * @author <a href="mailto:zheng@apache.org">ZHENG Zhong</a> |
32 | * @author <a href="mailto:ddewolf@apache.org">David H. DeWolf</a> |
33 | * @version 1.0 |
34 | * @since Sep 30, 2004 |
35 | */ |
36 | class PortalURLParser { |
37 | |
38 | /** Logger. */ |
39 | private static final Log LOG = LogFactory.getLog(PortalURLParser.class); |
40 | |
41 | /** The singleton parser instance. */ |
42 | private static final PortalURLParser PARSER = new PortalURLParser(); |
43 | |
44 | |
45 | // Constants used for Encoding/Decoding ------------------------------------ |
46 | |
47 | private static final String PREFIX = "__"; |
48 | private static final String DELIM = "_"; |
49 | private static final String PORTLET_ID = "pd"; |
50 | private static final String ACTION = "ac"; |
51 | private static final String RENDER_PARAM = "rp"; |
52 | private static final String WINDOW_STATE = "ws"; |
53 | private static final String PORTLET_MODE = "pm"; |
54 | private static final String VALUE_DELIM = "0x0"; |
55 | |
56 | private static final String[][] ENCODINGS = new String[][] { |
57 | new String[] { "_", "0x1" }, |
58 | new String[] { ".", "0x2" }, |
59 | new String[] { "/", "0x3" }, |
60 | new String[] { "\r", "0x4" }, |
61 | new String[] { "\n", "0x5" }, |
62 | new String[] { "<", "0x6" }, |
63 | new String[] { ">", "0x7" }, |
64 | new String[] { " ", "0x8" }, |
65 | }; |
66 | |
67 | // Constructor ------------------------------------------------------------- |
68 | |
69 | /** |
70 | * Private constructor that prevents external instantiation. |
71 | */ |
72 | private PortalURLParser() { |
73 | // Do nothing. |
74 | } |
75 | |
76 | /** |
77 | * Returns the singleton parser instance. |
78 | * @return the singleton parser instance. |
79 | */ |
80 | public static PortalURLParser getParser() { |
81 | return PARSER; |
82 | } |
83 | |
84 | |
85 | // Public Methods ---------------------------------------------------------- |
86 | |
87 | /** |
88 | * Parse a servlet request to a portal URL. |
89 | * @param request the servlet request to parse. |
90 | * @return the portal URL. |
91 | */ |
92 | public PortalURL parse(HttpServletRequest request) { |
93 | |
94 | if (LOG.isDebugEnabled()) { |
95 | LOG.debug("Parsing URL: " + request.getRequestURI()); |
96 | } |
97 | |
98 | String protocol = request.isSecure() ? "https://" : "http://"; |
99 | String server = request.getServerName(); |
100 | int port = request.getServerPort(); |
101 | String contextPath = request.getContextPath(); |
102 | String servletName = request.getServletPath(); |
103 | |
104 | // Construct portal URL using info retrieved from servlet request. |
105 | PortalURL portalURL = null; |
106 | if ((request.isSecure() && port != 443) |
107 | || (!request.isSecure() && port != 80)) { |
108 | portalURL = new PortalURL(protocol, server, port, contextPath, servletName); |
109 | } else { |
110 | portalURL = new PortalURL(protocol, server, contextPath, servletName); |
111 | } |
112 | |
113 | String pathInfo = request.getPathInfo(); |
114 | if (pathInfo == null) { |
115 | return portalURL; |
116 | } |
117 | |
118 | if (LOG.isDebugEnabled()) { |
119 | LOG.debug("Parsing request pathInfo: " + pathInfo); |
120 | } |
121 | StringBuffer renderPath = new StringBuffer(); |
122 | StringTokenizer st = new StringTokenizer(pathInfo, "/", false); |
123 | while (st.hasMoreTokens()) { |
124 | |
125 | String token = st.nextToken(); |
126 | |
127 | // Part of the render path: append to renderPath. |
128 | if (!token.startsWith(PREFIX)) { |
129 | renderPath.append(token); |
130 | } |
131 | // Action window definition: portalURL.setActionWindow(). |
132 | else if (token.startsWith(PREFIX + ACTION)) { |
133 | portalURL.setActionWindow(decodeControlParameter(token)[0]); |
134 | } |
135 | // Window state definition: portalURL.setWindowState(). |
136 | else if (token.startsWith(PREFIX + WINDOW_STATE)) { |
137 | String[] decoded = decodeControlParameter(token); |
138 | portalURL.setWindowState(decoded[0], new WindowState(decoded[1])); |
139 | } |
140 | // Portlet mode definition: portalURL.setPortletMode(). |
141 | else if (token.startsWith(PREFIX + PORTLET_MODE)) { |
142 | String[] decoded = decodeControlParameter(token); |
143 | portalURL.setPortletMode(decoded[0], new PortletMode(decoded[1])); |
144 | } |
145 | // Portal URL parameter: portalURL.addParameter(). |
146 | else { |
147 | String value = null; |
148 | if (st.hasMoreTokens()) { |
149 | value = st.nextToken(); |
150 | } |
151 | portalURL.addParameter(decodeParameter(token, value)); |
152 | } |
153 | } |
154 | if (renderPath.length() > 0) { |
155 | portalURL.setRenderPath(renderPath.toString()); |
156 | } |
157 | |
158 | // Return the portal URL. |
159 | return portalURL; |
160 | } |
161 | |
162 | |
163 | /** |
164 | * Converts a portal URL to a URL string. |
165 | * @param portalURL the portal URL to convert. |
166 | * @return a URL string representing the portal URL. |
167 | */ |
168 | public String toString(PortalURL portalURL) { |
169 | |
170 | StringBuffer buffer = new StringBuffer(); |
171 | |
172 | // Append the server URI and the servlet path. |
173 | buffer.append(portalURL.getServerURI()) |
174 | .append(portalURL.getServletPath()); |
175 | |
176 | // Start the pathInfo with the path to the render URL (page). |
177 | if (portalURL.getRenderPath() != null) { |
178 | buffer.append("/").append(portalURL.getRenderPath()); |
179 | } |
180 | |
181 | // Append the action window definition, if it exists. |
182 | if (portalURL.getActionWindow() != null) { |
183 | buffer.append("/"); |
184 | buffer.append(PREFIX).append(ACTION) |
185 | .append(encodeCharacters(portalURL.getActionWindow())); |
186 | } |
187 | |
188 | // Append portlet mode definitions. |
189 | for (Iterator it = portalURL.getPortletModes().entrySet().iterator(); |
190 | it.hasNext(); ) { |
191 | Map.Entry entry = (Map.Entry) it.next(); |
192 | buffer.append("/").append( |
193 | encodeControlParameter(PORTLET_MODE, entry.getKey().toString(), |
194 | entry.getValue().toString())); |
195 | } |
196 | |
197 | // Append window state definitions. |
198 | for (Iterator it = portalURL.getWindowStates().entrySet().iterator(); |
199 | it.hasNext(); ) { |
200 | Map.Entry entry = (Map.Entry) it.next(); |
201 | buffer.append("/").append( |
202 | encodeControlParameter(WINDOW_STATE, entry.getKey().toString(), |
203 | entry.getValue().toString())); |
204 | } |
205 | |
206 | // Append action and render parameters. |
207 | StringBuffer query = new StringBuffer("?"); |
208 | for (Iterator it = portalURL.getParameters().iterator(); |
209 | it.hasNext(); ) { |
210 | |
211 | PortalURLParameter param = (PortalURLParameter) it.next(); |
212 | |
213 | // Encode action params in the query appended at the end of the URL. |
214 | if (portalURL.getActionWindow() != null |
215 | && portalURL.getActionWindow().equals(param.getWindowId())) { |
216 | for (int i = 0; i < param.getValues().length; i++) { |
217 | query.append("&").append(param.getName()).append("=") |
218 | .append(param.getValues()[i]); |
219 | } |
220 | } |
221 | |
222 | // Encode render params as a part of the URL. |
223 | else if (param.getValues() != null |
224 | && param.getValues().length > 0) { |
225 | String valueString = encodeMultiValues(param.getValues()); |
226 | if (valueString.length() > 0) { |
227 | buffer.append("/").append( |
228 | encodeControlParameter(RENDER_PARAM, param.getWindowId(), |
229 | param.getName())); |
230 | buffer.append("/").append(valueString); |
231 | } |
232 | } |
233 | } |
234 | |
235 | // Construct the string representing the portal URL. |
236 | return buffer.append(query).toString(); |
237 | } |
238 | |
239 | |
240 | // Private Encoding/Decoding Methods --------------------------------------- |
241 | |
242 | /** |
243 | * Encode a control parameter. |
244 | * @param type the type of the control parameter, which may be: |
245 | * portlet mode, window state, or render parameter. |
246 | * @param windowId the portlet window ID. |
247 | * @param name the name to encode. |
248 | */ |
249 | private String encodeControlParameter(String type, |
250 | String windowId, |
251 | String name) { |
252 | StringBuffer buffer = new StringBuffer(); |
253 | buffer.append(PREFIX).append(type) |
254 | .append(encodeCharacters(windowId)) |
255 | .append(DELIM).append(name); |
256 | return buffer.toString(); |
257 | } |
258 | |
259 | /** |
260 | * Encode a string array containing multiple values into a single string. |
261 | * This method is used to encode multiple render parameter values. |
262 | * @param values the string array to encode. |
263 | * @return a single string containing all the values. |
264 | */ |
265 | private String encodeMultiValues(String[] values) { |
266 | StringBuffer buffer = new StringBuffer(); |
267 | for (int i = 0; i < values.length; i++) { |
268 | buffer.append(values[i] != null ? values[i] : ""); |
269 | if (i + 1 < values.length) { |
270 | buffer.append(VALUE_DELIM); |
271 | } |
272 | } |
273 | return encodeCharacters(buffer.toString()); |
274 | } |
275 | |
276 | /** |
277 | * Encode special characters contained in the string value. |
278 | * @param string the string value to encode. |
279 | * @return the encoded string. |
280 | */ |
281 | private String encodeCharacters(String string) { |
282 | for (int i = 0; i < ENCODINGS.length; i++) { |
283 | string = StringUtils.replace(string, |
284 | ENCODINGS[i][0], |
285 | ENCODINGS[i][1]); |
286 | } |
287 | return string; |
288 | } |
289 | |
290 | |
291 | /** |
292 | * Decode a control parameter. |
293 | * @param control the control parameter to decode. |
294 | * @return values a pair of decoded values. |
295 | */ |
296 | private String[] decodeControlParameter(String control) { |
297 | String[] valuePair = new String[2]; |
298 | control = control.substring((PREFIX + PORTLET_ID).length()); |
299 | int index = control.indexOf(DELIM); |
300 | if (index >= 0) { |
301 | valuePair[0] = control.substring(0, index); |
302 | valuePair[0] = decodeCharacters(valuePair[0]); |
303 | if (index + 1 <= control.length()) { |
304 | valuePair[1] = control.substring(index + 1); |
305 | valuePair[1] = decodeCharacters(valuePair[1]); |
306 | } else { |
307 | valuePair[1] = ""; |
308 | } |
309 | } else { |
310 | valuePair[0] = decodeCharacters(control); |
311 | } |
312 | return valuePair; |
313 | } |
314 | |
315 | /** |
316 | * Decode a name-value pair into a portal URL parameter. |
317 | * @param name the parameter name. |
318 | * @param value the parameter value. |
319 | * @return the decoded portal URL parameter. |
320 | */ |
321 | private PortalURLParameter decodeParameter(String name, String value) { |
322 | |
323 | if (LOG.isDebugEnabled()) { |
324 | LOG.debug("Decoding parameter: name=" + name |
325 | + ", value=" + value); |
326 | } |
327 | |
328 | // Decode the name into window ID and parameter name. |
329 | String noPrefix = name.substring((PREFIX + PORTLET_ID).length()); |
330 | String windowId = noPrefix.substring(0, noPrefix.indexOf(DELIM)); |
331 | String paramName = noPrefix.substring(noPrefix.indexOf(DELIM) + 1); |
332 | |
333 | // Decode special characters in window ID and parameter value. |
334 | windowId = decodeCharacters(windowId); |
335 | if (value != null) { |
336 | value = decodeCharacters(value); |
337 | } |
338 | |
339 | // Split multiple values into a value array. |
340 | String[] paramValues = value.split(VALUE_DELIM); |
341 | |
342 | // Construct portal URL parameter and return. |
343 | return new PortalURLParameter(windowId, paramName, paramValues); |
344 | } |
345 | |
346 | /** |
347 | * Decode special characters contained in the string value. |
348 | * @param string the string value to decode. |
349 | * @return the decoded string. |
350 | */ |
351 | private String decodeCharacters(String string) { |
352 | for (int i = 0; i < ENCODINGS.length; i++) { |
353 | string = StringUtils.replace(string, |
354 | ENCODINGS[i][1], |
355 | ENCODINGS[i][0]); |
356 | } |
357 | return string; |
358 | } |
359 | |
360 | } |
361 | |