001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.pluto.driver.url.impl;
018:
019: import java.io.UnsupportedEncodingException;
020: import java.net.URLEncoder;
021: import java.util.Iterator;
022: import java.util.Map;
023: import java.util.StringTokenizer;
024:
025: import javax.portlet.PortletMode;
026: import javax.portlet.WindowState;
027: import javax.servlet.http.HttpServletRequest;
028:
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031: import org.apache.pluto.driver.url.PortalURL;
032: import org.apache.pluto.driver.url.PortalURLParameter;
033: import org.apache.pluto.driver.url.PortalURLParser;
034: import org.apache.pluto.util.StringUtils;
035:
036: /**
037: * @version 1.0
038: * @since Sep 30, 2004
039: */
040: public class PortalURLParserImpl implements PortalURLParser {
041:
042: /** Logger. */
043: private static final Log LOG = LogFactory
044: .getLog(PortalURLParserImpl.class);
045:
046: /** The singleton parser instance. */
047: private static final PortalURLParser PARSER = new PortalURLParserImpl();
048:
049: // Constants used for Encoding/Decoding ------------------------------------
050:
051: private static final String PREFIX = "__";
052: private static final String DELIM = "_";
053: private static final String PORTLET_ID = "pd";
054: private static final String ACTION = "ac";
055: private static final String RENDER_PARAM = "rp";
056: private static final String WINDOW_STATE = "ws";
057: private static final String PORTLET_MODE = "pm";
058: private static final String VALUE_DELIM = "0x0";
059:
060: private static final String[][] ENCODINGS = new String[][] {
061: new String[] { "_", "0x1" }, new String[] { ".", "0x2" },
062: new String[] { "/", "0x3" }, new String[] { "\r", "0x4" },
063: new String[] { "\n", "0x5" }, new String[] { "<", "0x6" },
064: new String[] { ">", "0x7" }, new String[] { " ", "0x8" },
065: new String[] { "#", "0x9" }, };
066:
067: // Constructor -------------------------------------------------------------
068:
069: /**
070: * Private constructor that prevents external instantiation.
071: */
072: private PortalURLParserImpl() {
073: // Do nothing.
074: }
075:
076: /**
077: * Returns the singleton parser instance.
078: * @return the singleton parser instance.
079: */
080: public static PortalURLParser getParser() {
081: return PARSER;
082: }
083:
084: // Public Methods ----------------------------------------------------------
085:
086: /**
087: * Parse a servlet request to a portal URL.
088: * @param request the servlet request to parse.
089: * @return the portal URL.
090: */
091: public PortalURL parse(HttpServletRequest request) {
092:
093: if (LOG.isDebugEnabled()) {
094: LOG.debug("Parsing URL: " + request.getRequestURI());
095: }
096:
097: String contextPath = request.getContextPath();
098: String servletName = request.getServletPath();
099:
100: // Construct portal URL using info retrieved from servlet request.
101: PortalURL portalURL = new RelativePortalURLImpl(contextPath,
102: servletName);
103:
104: // Support added for filter. Should we seperate into a different impl?
105: String pathInfo = request.getPathInfo();
106: if (pathInfo == null) {
107: if ((servletName.indexOf(".jsp") != -1)
108: && !servletName.endsWith(".jsp")) {
109: int idx = servletName.indexOf(".jsp") + ".jsp".length();
110: pathInfo = servletName.substring(idx);
111: servletName = servletName.substring(0, idx);
112: portalURL = new RelativePortalURLImpl(contextPath,
113: servletName);
114: } else {
115: return portalURL;
116: }
117: }
118:
119: if (LOG.isDebugEnabled()) {
120: LOG.debug("Parsing request pathInfo: " + pathInfo);
121: }
122: StringBuffer renderPath = new StringBuffer();
123: StringTokenizer st = new StringTokenizer(pathInfo, "/", false);
124: while (st.hasMoreTokens()) {
125:
126: String token = st.nextToken();
127:
128: // Part of the render path: append to renderPath.
129: if (!token.startsWith(PREFIX)) {
130: // renderPath.append(token);
131: //Fix for PLUTO-243
132: renderPath.append('/').append(token);
133: }
134: // Action window definition: portalURL.setActionWindow().
135: else if (token.startsWith(PREFIX + ACTION)) {
136: portalURL
137: .setActionWindow(decodeControlParameter(token)[0]);
138: }
139: // Window state definition: portalURL.setWindowState().
140: else if (token.startsWith(PREFIX + WINDOW_STATE)) {
141: String[] decoded = decodeControlParameter(token);
142: portalURL.setWindowState(decoded[0], new WindowState(
143: decoded[1]));
144: }
145: // Portlet mode definition: portalURL.setPortletMode().
146: else if (token.startsWith(PREFIX + PORTLET_MODE)) {
147: String[] decoded = decodeControlParameter(token);
148: portalURL.setPortletMode(decoded[0], new PortletMode(
149: decoded[1]));
150: }
151: // Portal URL parameter: portalURL.addParameter().
152: else {
153: String value = null;
154: if (st.hasMoreTokens()) {
155: value = st.nextToken();
156: }
157:
158: // Defect PLUTO-361
159: // ADDED
160: PortalURLParameter param = decodeParameter(token, value);
161: if (param != null) {
162: portalURL.addParameter(param);
163: }
164: // REMOVED
165: // portalURL.addParameter(decodeParameter(token, value));
166: }
167: }
168: if (renderPath.length() > 0) {
169: portalURL.setRenderPath(renderPath.toString());
170: }
171:
172: // Return the portal URL.
173: return portalURL;
174: }
175:
176: /**
177: * Converts a portal URL to a URL string.
178: * @param portalURL the portal URL to convert.
179: * @return a URL string representing the portal URL.
180: */
181: public String toString(PortalURL portalURL) {
182:
183: StringBuffer buffer = new StringBuffer();
184:
185: // Append the server URI and the servlet path.
186: buffer.append(
187: portalURL.getServletPath().startsWith("/") ? "" : "/")
188: .append(portalURL.getServletPath());
189:
190: // Start the pathInfo with the path to the render URL (page).
191: if (portalURL.getRenderPath() != null) {
192: buffer.append("/").append(portalURL.getRenderPath());
193: }
194:
195: // Append the action window definition, if it exists.
196: if (portalURL.getActionWindow() != null) {
197: buffer.append("/");
198: buffer.append(PREFIX).append(ACTION).append(
199: encodeCharacters(portalURL.getActionWindow()));
200: }
201:
202: // Append portlet mode definitions.
203: for (Iterator it = portalURL.getPortletModes().entrySet()
204: .iterator(); it.hasNext();) {
205: Map.Entry entry = (Map.Entry) it.next();
206: buffer.append("/").append(
207: encodeControlParameter(PORTLET_MODE, entry.getKey()
208: .toString(), entry.getValue().toString()));
209: }
210:
211: // Append window state definitions.
212: for (Iterator it = portalURL.getWindowStates().entrySet()
213: .iterator(); it.hasNext();) {
214: Map.Entry entry = (Map.Entry) it.next();
215: buffer.append("/").append(
216: encodeControlParameter(WINDOW_STATE, entry.getKey()
217: .toString(), entry.getValue().toString()));
218: }
219:
220: // Append action and render parameters.
221: StringBuffer query = new StringBuffer("?");
222: boolean firstParam = true;
223: for (Iterator it = portalURL.getParameters().iterator(); it
224: .hasNext();) {
225:
226: PortalURLParameter param = (PortalURLParameter) it.next();
227:
228: // Encode action params in the query appended at the end of the URL.
229: if (portalURL.getActionWindow() != null
230: && portalURL.getActionWindow().equals(
231: param.getWindowId())) {
232: for (int i = 0; i < param.getValues().length; i++) {
233: // FIX for PLUTO-247
234: if (firstParam) {
235: firstParam = false;
236: } else {
237: query.append("&");
238: }
239: query
240: .append(encodeQueryParam(param.getName()))
241: .append("=")
242: .append(
243: encodeQueryParam(param.getValues()[i]));
244: }
245: }
246:
247: // Encode render params as a part of the URL.
248: else if (param.getValues() != null
249: && param.getValues().length > 0) {
250: String valueString = encodeMultiValues(param
251: .getValues());
252: if (valueString.length() > 0) {
253: buffer.append("/").append(
254: encodeControlParameter(RENDER_PARAM, param
255: .getWindowId(), param.getName()));
256: buffer.append("/").append(valueString);
257: }
258: }
259: }
260:
261: // Construct the string representing the portal URL.
262: // Fix for PLUTO-247 - check if query string contains parameters
263: if (query.length() > 1) {
264: return buffer.append(query).toString();
265: }
266:
267: return buffer.toString();
268: }
269:
270: private String encodeQueryParam(String param) {
271: try {
272: return URLEncoder.encode(param, "UTF-8");
273: } catch (UnsupportedEncodingException e) {
274: // If this happens, we've got bigger problems.
275: throw new RuntimeException(e);
276: }
277: }
278:
279: // Private Encoding/Decoding Methods ---------------------------------------
280:
281: /**
282: * Encode a control parameter.
283: * @param type the type of the control parameter, which may be:
284: * portlet mode, window state, or render parameter.
285: * @param windowId the portlet window ID.
286: * @param name the name to encode.
287: */
288: private String encodeControlParameter(String type, String windowId,
289: String name) {
290: StringBuffer buffer = new StringBuffer();
291: buffer.append(PREFIX).append(type).append(
292: encodeCharacters(windowId)).append(DELIM).append(name);
293: return buffer.toString();
294: }
295:
296: /**
297: * Encode a string array containing multiple values into a single string.
298: * This method is used to encode multiple render parameter values.
299: * @param values the string array to encode.
300: * @return a single string containing all the values.
301: */
302: private String encodeMultiValues(String[] values) {
303: StringBuffer buffer = new StringBuffer();
304: for (int i = 0; i < values.length; i++) {
305: buffer.append(values[i] != null ? values[i] : "");
306: if (i + 1 < values.length) {
307: buffer.append(VALUE_DELIM);
308: }
309: }
310: return encodeCharacters(buffer.toString());
311: }
312:
313: /**
314: * Encode special characters contained in the string value.
315: * @param string the string value to encode.
316: * @return the encoded string.
317: */
318: private String encodeCharacters(String string) {
319: for (int i = 0; i < ENCODINGS.length; i++) {
320: string = StringUtils.replace(string, ENCODINGS[i][0],
321: ENCODINGS[i][1]);
322: }
323: return string;
324: }
325:
326: /**
327: * Decode a control parameter.
328: * @param control the control parameter to decode.
329: * @return values a pair of decoded values.
330: */
331: private String[] decodeControlParameter(String control) {
332: String[] valuePair = new String[2];
333: control = control.substring((PREFIX + PORTLET_ID).length());
334: int index = control.indexOf(DELIM);
335: if (index >= 0) {
336: valuePair[0] = control.substring(0, index);
337: valuePair[0] = decodeCharacters(valuePair[0]);
338: if (index + 1 <= control.length()) {
339: valuePair[1] = control.substring(index + 1);
340: valuePair[1] = decodeCharacters(valuePair[1]);
341: } else {
342: valuePair[1] = "";
343: }
344: } else {
345: valuePair[0] = decodeCharacters(control);
346: }
347: return valuePair;
348: }
349:
350: /**
351: * Decode a name-value pair into a portal URL parameter.
352: * @param name the parameter name.
353: * @param value the parameter value.
354: * @return the decoded portal URL parameter.
355: */
356: private PortalURLParameter decodeParameter(String name, String value) {
357:
358: if (LOG.isDebugEnabled()) {
359: LOG.debug("Decoding parameter: name=" + name + ", value="
360: + value);
361: }
362:
363: // Defect PLUTO-361
364: // ADDED: if the length is less than this, there is no parameter...
365: if (name.length() < (PREFIX + PORTLET_ID).length()) {
366: return null;
367: }
368:
369: // Decode the name into window ID and parameter name.
370: String noPrefix = name
371: .substring((PREFIX + PORTLET_ID).length());
372: String windowId = noPrefix
373: .substring(0, noPrefix.indexOf(DELIM));
374: String paramName = noPrefix
375: .substring(noPrefix.indexOf(DELIM) + 1);
376:
377: // Decode special characters in window ID and parameter value.
378: windowId = decodeCharacters(windowId);
379: if (value != null) {
380: value = decodeCharacters(value);
381: }
382:
383: // Split multiple values into a value array.
384: String[] paramValues = value.split(VALUE_DELIM);
385:
386: // Construct portal URL parameter and return.
387: return new PortalURLParameter(windowId, paramName, paramValues);
388: }
389:
390: /**
391: * Decode special characters contained in the string value.
392: * @param string the string value to decode.
393: * @return the decoded string.
394: */
395: private String decodeCharacters(String string) {
396: for (int i = 0; i < ENCODINGS.length; i++) {
397: string = StringUtils.replace(string, ENCODINGS[i][1],
398: ENCODINGS[i][0]);
399: }
400: return string;
401: }
402:
403: }
|