/*
* ConcourseConnect
* Copyright 2009 Concursive Corporation
* http://www.concursive.com
*
* This file is part of ConcourseConnect, an open source social business
* software and community platform.
*
* Concursive ConcourseConnect is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, version 3 of the License.
*
* Under the terms of the GNU Affero General Public License you must release the
* complete source code for any application that uses any part of ConcourseConnect
* (system header files and libraries used by the operating system are excluded).
* These terms must be included in any work that has ConcourseConnect components.
* If you are developing and distributing open source applications under the
* GNU Affero General Public License, then you are free to use ConcourseConnect
* under the GNU Affero General Public License.
*
* If you are deploying a web site in which users interact with any portion of
* ConcourseConnect over a network, the complete source code changes must be made
* available. For example, include a link to the source archive directly from
* your web site.
*
* For OEMs, ISVs, SIs and VARs who distribute ConcourseConnect with their
* products, and do not license and distribute their source code under the GNU
* Affero General Public License, Concursive provides a flexible commercial
* license.
*
* To anyone in doubt, we recommend the commercial license. Our commercial license
* is competitively priced and will eliminate any confusion about how
* ConcourseConnect can be used and distributed.
*
* ConcourseConnect is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with ConcourseConnect. If not, see <http://www.gnu.org/licenses/>.
*
* Attribution Notice: ConcourseConnect is an Original Work of software created
* by Concursive Corporation
*/
package com.concursive.connect.web.portal;
import com.concursive.commons.text.StringUtils;
import com.concursive.connect.web.modules.profile.dao.Project;
import com.concursive.connect.web.modules.profile.utils.ProjectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pluto.driver.url.PortalURL;
import org.apache.pluto.driver.url.PortalURLParameter;
import org.apache.pluto.driver.url.PortalURLParser;
import org.apache.pluto.driver.url.impl.RelativePortalURLImpl;
import javax.portlet.PortletMode;
import javax.portlet.WindowState;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
/**
* A replacement portal url parser which implements simple URLs for the
* project module portlets
*
* @author matt rajkowski
* @created October 24, 2008
*/
public class ProjectPortalURLParserImpl implements PortalURLParser {
private static final Log LOG = LogFactory.getLog(ProjectPortalURLParserImpl.class);
/**
* The singleton parser instance.
*/
private static final PortalURLParser PARSER = new ProjectPortalURLParserImpl();
// Constants used for Encoding/Decoding ------------------------------------
public static final String ALLOWED_PORTAL_PARAMETERS = "plutoAllowedPortalParameters";
private static final String PREFIX = "__";
private static final String DELIM = "_";
private static final String PORTLET_ID = "pd";
private static final String ACTION = "ac";
private static final String RENDER_PARAM = "rp";
private static final String WINDOW_STATE = "ws";
private static final String PORTLET_MODE = "pm";
private static final String VALUE_DELIM = "0x0";
private static final String[][] ENCODINGS = new String[][]{
//new String[]{"_", "0x1"},
new String[]{".", "0x2"},
new String[]{"/", "0x3"},
new String[]{"\r", "0x4"},
new String[]{"\n", "0x5"},
new String[]{"<", "0x6"},
new String[]{">", "0x7"},
new String[]{" ", "0x8"},
new String[]{"#", "0x9"},
};
// Constructor -------------------------------------------------------------
/**
* Private constructor that prevents external instantiation.
*/
private ProjectPortalURLParserImpl() {
// Do nothing.
}
/**
* Returns the singleton parser instance.
*
* @return the singleton parser instance.
*/
public static PortalURLParser getParser() {
return PARSER;
}
// Public Methods ----------------------------------------------------------
/**
* Parse a servlet request to a portal URL.
*
* @param request the servlet request to parse.
* @return the portal URL.
*/
public PortalURL parse(HttpServletRequest request) {
LOG.debug("Parsing URL: " + request.getRequestURI());
StringBuffer url = new StringBuffer();
// Build the URL from various items...
if (request.getParameterMap() != null) {
String action = request.getParameter("portlet-action");
String projectValue = request.getParameter("portlet-pid");
String object = request.getParameter("portlet-object");
String value = request.getParameter("portlet-value");
String params = request.getParameter("portlet-params");
// Append the portlet action
// @note TEST TEST TEST
//url.append("/").append(action);
url.append("/").append(StringUtils.encodeUrl(action));
// Append the targeted profile
Project project = ProjectUtils.loadProject(Integer.parseInt(projectValue));
url.append("/").append(project.getUniqueId());
// Append the object in the profile
// @note TEST TEST TEST
//url.append("/").append(object);
url.append("/").append(StringUtils.encodeUrl(object));
// Append any object value, like object id
if (StringUtils.hasText(value)) {
url.append("/").append(StringUtils.encodeUrl(value));
}
// Append any parameters
if (StringUtils.hasText(params)) {
// @note TEST TEST TEST
//url.append("/").append(params);
url.append("/").append(StringUtils.encodeUrl(params));
}
LOG.debug("reconstructed url: " + url.toString());
}
String servletName = url.toString();
// Construct portal URL using info retrieved from servlet request.
String contextPath = request.getContextPath();
LOG.debug("contextPath: " + contextPath);
PortalURL portalURL = new RelativePortalURLImpl(contextPath, servletName, getParser());
// Action window definition: portalURL.setActionWindow().
String portletAction = request.getParameter("portletAction");
if (portletAction != null && portletAction.startsWith(PREFIX + ACTION)) {
LOG.debug("found action");
portalURL.setActionWindow(decodeControlParameter(portletAction)[0]);
portalURL.setRenderPath(contextPath + ".");
}
// Window state definition: portalURL.setWindowState().
String portletWindowState = null;
int windowStateCount = 0;
while ((portletWindowState = request.getParameter("portletWindowState" + (++windowStateCount))) != null) {
String[] decoded = decodeControlParameter(portletWindowState);
portalURL.setWindowState(decoded[0], new WindowState(decoded[1]));
}
// Portlet mode definition: portalURL.setPortletMode().
String portletMode = request.getParameter("portletMode");
if (portletMode != null) {
String[] decoded = decodeControlParameter(portletMode);
portalURL.setPortletMode(decoded[0], new PortletMode(decoded[1]));
}
// Portal URL parameter: portalURL.addParameter().
Enumeration params = request.getParameterNames();
while (params.hasMoreElements()) {
String parameter = (String) params.nextElement();
if (parameter.startsWith(PREFIX + RENDER_PARAM)) {
String value = request.getParameter(parameter);
LOG.debug("parameter: " + parameter);
portalURL.addParameter(decodeParameter(parameter, value));
}
}
// Return the portal URL.
return portalURL;
}
private static void appendParameter(StringBuffer url, String parameter) {
if (url.length() == 0 || !url.toString().contains("?")) {
url.append("?");
} else {
url.append("&");
}
LOG.debug("appendParameter: " + parameter);
url.append(parameter);
}
/**
* Converts a portal URL to a URL string.
*
* @param portalURL the portal URL to convert.
* @return a URL string representing the portal URL.
*/
public String toString(PortalURL portalURL) {
//servletPath: /context/show/project/module
LOG.debug("servletPath: " + portalURL.getServletPath());
//renderPath: /context.SomePortlet!T3
LOG.debug("renderPath: " + portalURL.getRenderPath());
// Decode the current url
StringBuffer buffer = new StringBuffer();
// Detect if pointing to a new url based on passed parameters
String action = null;
String object = null;
String value = null;
String params = null;
for (Object paramObject : portalURL.getParameters()) {
PortalURLParameter param = (PortalURLParameter) paramObject;
if ("portlet-action".equals(param.getName())) {
action = param.getValues()[0];
} else if ("portlet-object".equals(param.getName())) {
object = param.getValues()[0];
} else if ("portlet-value".equals(param.getName())) {
value = param.getValues()[0];
} else if ("portlet-params".equals(param.getName())) {
params = param.getValues()[0];
}
}
if (action != null || object != null || value != null || params != null) {
// Strip off the context for determining the components of the url
String url = portalURL.getServletPath();
if (portalURL.getRenderPath() != null) {
String ctx = portalURL.getRenderPath().substring(0, portalURL.getRenderPath().indexOf("."));
if (ctx.length() > 1) {
url = url.substring(ctx.length());
buffer.append(ctx);
}
}
LOG.debug("Base url: " + url);
// Split apart the url items
String[] currentURL = url.split("/");
int level = 0;
if (url.startsWith("/")) {
level = 1;
}
// Add the action
if (action != null) {
buffer.append("/").append(action);
} else {
buffer.append("/").append(currentURL[level]);
}
// Add the project uniqueId
buffer.append("/");
buffer.append(currentURL[level + 1]);
// Add the object
if (object != null) {
buffer.append("/").append(object);
} else {
if (currentURL.length > level + 2) {
buffer.append("/").append(currentURL[level + 2]);
}
}
// Add the value
if (value != null) {
buffer.append("/").append(value);
}
// Add the params
if (params != null) {
buffer.append("/").append(params);
}
} else {
// Use the current path
if (!portalURL.getServletPath().startsWith("/")) {
buffer.append("/");
}
buffer.append(portalURL.getServletPath());
}
// Append action and render parameters
for (Iterator it = portalURL.getParameters().iterator();
it.hasNext();) {
PortalURLParameter param = (PortalURLParameter) it.next();
LOG.debug("Checking: " + param.getName());
if ("portlet-action".equals(param.getName())) {
continue;
} else if ("portlet-object".equals(param.getName())) {
continue;
} else if ("portlet-value".equals(param.getName())) {
continue;
} else if ("portlet-params".equals(param.getName())) {
continue;
} else if ("out".equals(param.getName())) {
if (StringUtils.hasText(param.getValues()[0])) {
appendParameter(buffer, param.getName() + "=" + param.getValues()[0]);
}
continue;
} else if ("popup".equals(param.getName())) {
if (StringUtils.hasText(param.getValues()[0])) {
appendParameter(buffer, param.getName() + "=" + param.getValues()[0]);
}
continue;
}
// Encode action params in the query appended at the end of the URL.
if (portalURL.getActionWindow() != null
&& portalURL.getActionWindow().equals(param.getWindowId())) {
LOG.debug("Appending actionWindow parameters");
for (int i = 0; i < param.getValues().length; i++) {
if (StringUtils.hasText(param.getValues()[i])) {
appendParameter(buffer, param.getName() + "=" + param.getValues()[i]);
}
}
} else if (param.getValues() != null && param.getValues().length > 0) {
LOG.debug("Appending parameters... " + param.getWindowId());
// Encode the parameter ONLY if it targets the currently being rendered portlet
if (param.getWindowId().equals(portalURL.getRenderPath())) {
// The Project Portal uses clean URLs, so portlet window targeting is not used
if (!buffer.toString().contains("?" + param.getName() + "=") &&
!buffer.toString().contains("&" + param.getName() + "=")) {
String valueString = encodeMultiValues(param.getValues());
// @note TEST TEST TEST
//appendParameter(buffer, param.getName() + "=" + valueString);
appendParameter(buffer, param.getName() + "=" + StringUtils.encodeUrl(valueString));
} else {
// If param already exists, replace the value
}
}
}
}
// Append the action window definition, if it exists
if (portalURL.getActionWindow() != null) {
appendParameter(buffer, "portletAction=" + PREFIX + ACTION + encodeCharacters(portalURL.getActionWindow()));
}
// Append portlet mode definitions
for (Iterator it = portalURL.getPortletModes().entrySet().iterator();
it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
// If the portlet mode is "view" then no need to show the portlet mode in the url because that is the default
if (!"view".equals(entry.getValue().toString())) {
appendParameter(buffer, "portletMode=" + encodeControlParameter(PORTLET_MODE, entry.getKey().toString(),
entry.getValue().toString()));
}
}
// Append window state definitions
int windowStateCount = 0;
for (Iterator it = portalURL.getWindowStates().entrySet().iterator();
it.hasNext();) {
++windowStateCount;
Map.Entry entry = (Map.Entry) it.next();
// If the portlet window state is "normal" then no need to show it in the url because that is the default
if (!"normal".equals(entry.getValue().toString())) {
appendParameter(buffer, "portletWindowState" + windowStateCount + "=" +
encodeControlParameter(WINDOW_STATE, entry.getKey().toString(),
entry.getValue().toString()));
}
}
// Construct the string representing the portal URL.
return buffer.toString();
}
// Private Encoding/Decoding Methods ---------------------------------------
/**
* Encode a control parameter.
*
* @param type the type of the control parameter, which may be:
* portlet mode, window state, or render parameter.
* @param windowId the portlet window ID.
* @param name the name to encode.
* @return encoded control parameter
*/
private static String encodeControlParameter(String type,
String windowId,
String name) {
StringBuffer buffer = new StringBuffer();
buffer.append(PREFIX).append(type)
.append(encodeCharacters(windowId))
.append(DELIM).append(name);
return buffer.toString();
}
/**
* Encode a string array containing multiple values into a single string.
* This method is used to encode multiple render parameter values.
*
* @param values the string array to encode.
* @return a single string containing all the values.
*/
private String encodeMultiValues(String[] values) {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < values.length; i++) {
buffer.append(values[i] != null ? values[i] : "");
if (i + 1 < values.length) {
buffer.append(VALUE_DELIM);
}
}
return encodeCharacters(buffer.toString());
}
/**
* Encode special characters contained in the string value.
*
* @param string the string value to encode.
* @return the encoded string.
*/
public static String encodeCharacters(String string) {
for (int i = 0; i < ENCODINGS.length; i++) {
string = StringUtils.replace(string,
ENCODINGS[i][0],
ENCODINGS[i][1]);
}
return string;
}
/**
* Decode a control parameter.
*
* @param control the control parameter to decode.
* @return values a pair of decoded values.
*/
private String[] decodeControlParameter(String control) {
String[] valuePair = new String[2];
control = control.substring((PREFIX + PORTLET_ID).length());
int index = control.indexOf(DELIM);
if (index >= 0) {
valuePair[0] = control.substring(0, index);
valuePair[0] = decodeCharacters(valuePair[0]);
if (index + 1 <= control.length()) {
valuePair[1] = control.substring(index + 1);
valuePair[1] = decodeCharacters(valuePair[1]);
} else {
valuePair[1] = "";
}
} else {
valuePair[0] = decodeCharacters(control);
}
return valuePair;
}
/**
* Decode a name-value pair into a portal URL parameter.
*
* @param name the parameter name.
* @param value the parameter value.
* @return the decoded portal URL parameter.
*/
private PortalURLParameter decodeParameter(String name, String value) {
LOG.debug("Decoding parameter: name=" + name + ", value=" + value);
// Defect PLUTO-361
// ADDED: if the length is less than this, there is no parameter...
if (name.length() < (PREFIX + PORTLET_ID).length()) {
return null;
}
// Decode the name into window ID and parameter name.
String noPrefix = name.substring((PREFIX + PORTLET_ID).length());
String windowId = noPrefix.substring(0, noPrefix.indexOf(DELIM));
String paramName = noPrefix.substring(noPrefix.indexOf(DELIM) + 1);
// Decode special characters in window ID and parameter value.
windowId = decodeCharacters(windowId);
if (value != null) {
value = decodeCharacters(value);
}
// Split multiple values into a value array.
String[] paramValues = value.split(VALUE_DELIM);
if (paramValues.length == 1) {
LOG.debug(windowId + " - parameter: " + paramName + "=" + paramValues[0]);
}
// Construct portal URL parameter and return.
return new PortalURLParameter(windowId, paramName, paramValues);
}
/**
* Decode special characters contained in the string value.
*
* @param string the string value to decode.
* @return the decoded string.
*/
private static String decodeCharacters(String string) {
for (int i = 0; i < ENCODINGS.length; i++) {
string = StringUtils.replace(string,
ENCODINGS[i][1],
ENCODINGS[i][0]);
}
return string;
}
public static void addAllParameters(HttpServletRequest request, PortalURL portalURL) {
Enumeration params = request.getParameterNames();
while (params.hasMoreElements()) {
String parameter = (String) params.nextElement();
String value = request.getParameter(parameter);
if (StringUtils.hasText(value)) {
if (!parameter.startsWith(PREFIX + RENDER_PARAM)
&& !parameter.equals("portletWindowState1")
&& !parameter.startsWith("portlet-")
&& !(parameter.equals("command") && "ProjectCenter".equals(value))) {
if (StringUtils.hasText(value)) {
LOG.debug("Made a parameter available to the portlet: " + parameter);
portalURL.addParameter(new PortalURLParameter(portalURL.getRenderPath(), parameter, value));
}
} else {
if ("portlet-command".equals(parameter)) {
LOG.debug("Made a parameter available to the portlet: " + parameter);
portalURL.addParameter(new PortalURLParameter(portalURL.getRenderPath(), parameter, value));
}
}
}
}
}
}