/**
* Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright ownership. Apereo
* licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the License at the
* following location:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apereo.portal.url;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.portlet.PortletMode;
import javax.portlet.WindowState;
import javax.servlet.http.HttpServletRequest;
import javax.xml.xpath.XPathExpression;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apereo.portal.IUserPreferencesManager;
import org.apereo.portal.layout.IUserLayout;
import org.apereo.portal.layout.IUserLayoutManager;
import org.apereo.portal.portlet.PortletUtils;
import org.apereo.portal.portlet.om.IPortletEntity;
import org.apereo.portal.portlet.om.IPortletWindow;
import org.apereo.portal.portlet.om.IPortletWindowId;
import org.apereo.portal.portlet.registry.IPortletWindowRegistry;
import org.apereo.portal.portlet.rendering.IPortletRenderer;
import org.apereo.portal.user.IUserInstance;
import org.apereo.portal.user.IUserInstanceManager;
import org.apereo.portal.utils.Tuple;
import org.apereo.portal.utils.web.PortalWebUtils;
import org.apereo.portal.xml.xpath.XPathOperations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UrlPathHelper;
/**
* {@link IPortalUrlProvider} and {@link IUrlSyntaxProvider} implementation that uses a consistent
* human readable URL format.
*
*/
@Component("portalUrlProvider")
public class UrlSyntaxProviderImpl implements IUrlSyntaxProvider {
static final String SEPARATOR = "_";
static final String PORTAL_PARAM_PREFIX = "u" + SEPARATOR;
static final String PORTLET_CONTROL_PREFIX = "pC";
static final String PORTLET_PARAM_PREFIX = "pP" + SEPARATOR;
static final String PORTLET_PUBLIC_RENDER_PARAM_PREFIX = "pG" + SEPARATOR;
static final String PARAM_TARGET_PORTLET = PORTLET_CONTROL_PREFIX + "t";
static final String PARAM_ADDITIONAL_PORTLET = PORTLET_CONTROL_PREFIX + "a";
static final String PARAM_DELEGATE_PARENT = PORTLET_CONTROL_PREFIX + "d";
static final String PARAM_RESOURCE_ID = PORTLET_CONTROL_PREFIX + "r";
static final String PARAM_CACHEABILITY = PORTLET_CONTROL_PREFIX + "c";
static final String PARAM_WINDOW_STATE = PORTLET_CONTROL_PREFIX + "s";
static final String PARAM_PORTLET_MODE = PORTLET_CONTROL_PREFIX + "m";
static final String PARAM_COPY_PARAMETERS = PORTLET_CONTROL_PREFIX + "p";
static final Set<String> LEGACY_URL_PATHS =
ImmutableSet.of(
"/render.userLayoutRootNode.uP",
"/tag.idempotent.render.userLayoutRootNode.uP");
static final String LEGACY_PARAM_PORTLET_FNAME = "uP_fname";
static final String LEGACY_PARAM_PORTLET_REQUEST_TYPE = "pltc_type";
static final String LEGACY_PARAM_PORTLET_STATE = "pltc_state";
static final String LEGACY_PARAM_PORTLET_MODE = "pltc_mode";
static final String LEGACY_PARAM_PORTLET_PARAM_PREFX = "pltp_";
static final String LEGACY_PARAM_LAYOUT_ROOT = "root";
static final String LEGACY_PARAM_LAYOUT_ROOT_VALUE = "uP_root";
static final String LEGACY_PARAM_LAYOUT_STRUCT_PARAM = "uP_sparam";
static final String LEGACY_PARAM_LAYOUT_TAB_ID = "activeTab";
static final String SLASH = "/";
static final String PORTLET_PATH_PREFIX = "p";
static final String FOLDER_PATH_PREFIX = "f";
static final String REQUEST_TYPE_SUFFIX = ".uP";
private static final Pattern SLASH_PATTERN = Pattern.compile(SLASH);
private static final String PORTAL_CANONICAL_URL =
UrlSyntaxProviderImpl.class.getName() + ".PORTAL_CANONICAL_URL";
private static final String PORTAL_REQUEST_INFO_ATTR =
UrlSyntaxProviderImpl.class.getName() + ".PORTAL_REQUEST_INFO";
private static final String PORTAL_REQUEST_PARSING_IN_PROGRESS_ATTR =
UrlSyntaxProviderImpl.class.getName() + ".PORTAL_REQUEST_PARSING_IN_PROGRESS";
/**
* Utility enum used for parsing parameters that can appear multiple times on one URL and may or
* may not be suffixed with the portlet's window id
*/
private enum SuffixedPortletParameter {
RESOURCE_ID(UrlSyntaxProviderImpl.PARAM_RESOURCE_ID, UrlType.RESOURCE) {
@Override
public void updateRequestInfo(
HttpServletRequest request,
IPortletWindowRegistry portletWindowRegistry,
PortletRequestInfoImpl portletRequestInfo,
List<String> values,
Map<IPortletWindowId, IPortletWindowId> delegateIdMappings) {
portletRequestInfo.setResourceId(values.get(0));
}
},
CACHEABILITY(UrlSyntaxProviderImpl.PARAM_CACHEABILITY, UrlType.RESOURCE) {
@Override
public void updateRequestInfo(
HttpServletRequest request,
IPortletWindowRegistry portletWindowRegistry,
PortletRequestInfoImpl portletRequestInfo,
List<String> values,
Map<IPortletWindowId, IPortletWindowId> delegateIdMappings) {
portletRequestInfo.setCacheability(values.get(0));
}
},
DELEGATE_PARENT(
UrlSyntaxProviderImpl.PARAM_DELEGATE_PARENT,
UrlType.RENDER,
UrlType.ACTION,
UrlType.RESOURCE) {
@Override
public void updateRequestInfo(
HttpServletRequest request,
IPortletWindowRegistry portletWindowRegistry,
PortletRequestInfoImpl portletRequestInfo,
List<String> values,
Map<IPortletWindowId, IPortletWindowId> delegateIdMappings) {
try {
final IPortletWindowId delegateParentWindowId =
portletWindowRegistry.getPortletWindowId(request, values.get(0));
portletRequestInfo.setDelegateParentWindowId(delegateParentWindowId);
final IPortletWindowId delegateWindowId =
portletRequestInfo.getPortletWindowId();
delegateIdMappings.put(delegateParentWindowId, delegateWindowId);
} catch (IllegalArgumentException e) {
this.logger.warn(
"Failed to parse delegate portlet window ID '"
+ values.get(0)
+ "', the delegation window parameter will be ignored",
e);
}
}
},
WINDOW_STATE(UrlSyntaxProviderImpl.PARAM_WINDOW_STATE, UrlType.RENDER, UrlType.ACTION) {
@Override
public void updateRequestInfo(
HttpServletRequest request,
IPortletWindowRegistry portletWindowRegistry,
PortletRequestInfoImpl portletRequestInfo,
List<String> values,
Map<IPortletWindowId, IPortletWindowId> delegateIdMappings) {
portletRequestInfo.setWindowState(PortletUtils.getWindowState(values.get(0)));
}
},
PORTLET_MODE(UrlSyntaxProviderImpl.PARAM_PORTLET_MODE, UrlType.RENDER, UrlType.ACTION) {
@Override
public void updateRequestInfo(
HttpServletRequest request,
IPortletWindowRegistry portletWindowRegistry,
PortletRequestInfoImpl portletRequestInfo,
List<String> values,
Map<IPortletWindowId, IPortletWindowId> delegateIdMappings) {
portletRequestInfo.setPortletMode(PortletUtils.getPortletMode(values.get(0)));
}
},
COPY_PARAMETERS(UrlSyntaxProviderImpl.PARAM_COPY_PARAMETERS, UrlType.RENDER) {
@Override
public void updateRequestInfo(
HttpServletRequest request,
IPortletWindowRegistry portletWindowRegistry,
PortletRequestInfoImpl portletRequestInfo,
List<String> values,
Map<IPortletWindowId, IPortletWindowId> delegateIdMappings) {
final Map<String, List<String>> portletParameters =
portletRequestInfo.getPortletParameters();
final IPortletWindowId portletRequestInfoWindowId =
portletRequestInfo.getPortletWindowId();
final IPortletWindow portletWindow =
portletWindowRegistry.getPortletWindow(request, portletRequestInfoWindowId);
final Map<String, String[]> renderParameters = portletWindow.getRenderParameters();
ParameterMap.putAllList(portletParameters, renderParameters);
}
};
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
private final String parameterPrefix;
private final Set<UrlType> validUrlTypes;
private SuffixedPortletParameter(
String parameterPrefix, UrlType validUrlType, UrlType... validUrlTypes) {
this.parameterPrefix = parameterPrefix;
this.validUrlTypes = Sets.immutableEnumSet(validUrlType, validUrlTypes);
}
/** @return The {@link UrlType}s this parameter is valid on */
public Set<UrlType> getValidUrlTypes() {
return this.validUrlTypes;
}
/** @return The parameter prefix */
public String getParameterPrefix() {
return this.parameterPrefix;
}
/** Update the portlet request info based on the values for the parameter */
public abstract void updateRequestInfo(
HttpServletRequest request,
IPortletWindowRegistry portletWindowRegistry,
PortletRequestInfoImpl portletRequestInfo,
List<String> values,
Map<IPortletWindowId, IPortletWindowId> delegateIdMappings);
}
/**
* Enum used in getPortalRequestInfo to keep track of the parser state when reading the URL
* string. IMPORTANT, if you add a new parse step the SWITCH block in getPortalRequestInfo MUST
* be updated
*/
private enum ParseStep {
FOLDER,
PORTLET,
STATE,
TYPE,
COMPLETE;
}
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
/** WindowStates that are communicated as part of the path */
private static final Set<WindowState> PATH_WINDOW_STATES =
new LinkedHashSet<WindowState>(
Arrays.asList(
WindowState.MAXIMIZED,
IPortletRenderer.DETACHED,
IPortletRenderer.EXCLUSIVE));
private final UrlPathHelper urlPathHelper = new UrlPathHelper();
private Set<UrlState> statelessUrlStates = EnumSet.of(UrlState.DETACHED, UrlState.EXCLUSIVE);
private String defaultEncoding = "UTF-8";
private IPortletWindowRegistry portletWindowRegistry;
private IPortalRequestUtils portalRequestUtils;
private IUrlNodeSyntaxHelperRegistry urlNodeSyntaxHelperRegistry;
private IPortalUrlProvider portalUrlProvider;
private IUserInstanceManager userInstanceManager;
private XPathOperations xpathOperations;
@Autowired
public void setUserInstanceManager(IUserInstanceManager userInstanceManager) {
this.userInstanceManager = userInstanceManager;
}
@Autowired
public void setXpathOperations(XPathOperations xpathOperations) {
this.xpathOperations = xpathOperations;
}
@Autowired
public void setPortalUrlProvider(IPortalUrlProvider portalUrlProvider) {
this.portalUrlProvider = portalUrlProvider;
}
/** @param defaultEncoding the defaultEncoding to set */
public void setDefaultEncoding(String defaultEncoding) {
this.defaultEncoding = defaultEncoding;
}
/** @param portletWindowRegistry the portletWindowRegistry to set */
@Autowired
public void setPortletWindowRegistry(IPortletWindowRegistry portletWindowRegistry) {
this.portletWindowRegistry = portletWindowRegistry;
}
/** @param portalRequestUtils the portalRequestUtils to set */
@Autowired
public void setPortalRequestUtils(IPortalRequestUtils portalRequestUtils) {
this.portalRequestUtils = portalRequestUtils;
}
@Autowired
public void setUrlNodeSyntaxHelperRegistry(
IUrlNodeSyntaxHelperRegistry urlNodeSyntaxHelperRegistry) {
this.urlNodeSyntaxHelperRegistry = urlNodeSyntaxHelperRegistry;
}
/* (non-Javadoc)
* @see org.apereo.portal.url.IPortalUrlProvider#getPortalRequestInfo(javax.servlet.http.HttpServletRequest)
*/
@Override
public IPortalRequestInfo getPortalRequestInfo(HttpServletRequest request) {
request = this.portalRequestUtils.getOriginalPortalRequest(request);
final IPortalRequestInfo cachedPortalRequestInfo =
(IPortalRequestInfo) request.getAttribute(PORTAL_REQUEST_INFO_ATTR);
if (cachedPortalRequestInfo != null) {
if (logger.isDebugEnabled()) {
logger.debug("short-circuit: found portalRequestInfo within request attributes");
}
return cachedPortalRequestInfo;
}
synchronized (PortalWebUtils.getRequestAttributeMutex(request)) {
// set a flag to say this request is currently being parsed
final Boolean inProgressAttr =
(Boolean) request.getAttribute(PORTAL_REQUEST_PARSING_IN_PROGRESS_ATTR);
if (inProgressAttr != null && inProgressAttr) {
if (logger.isDebugEnabled()) {
logger.warn("Portal request info parsing already in progress, returning null");
}
return null;
}
request.setAttribute(PORTAL_REQUEST_PARSING_IN_PROGRESS_ATTR, Boolean.TRUE);
}
try {
// Clone the parameter map so data can be removed from it as it is parsed to help determine
// what to do with non-namespaced parameters
final Map<String, String[]> parameterMap = new ParameterMap(request.getParameterMap());
final String requestPath = this.urlPathHelper.getPathWithinApplication(request);
if (LEGACY_URL_PATHS.contains(requestPath)) {
return parseLegacyPortalUrl(request, parameterMap);
}
final IUrlNodeSyntaxHelper urlNodeSyntaxHelper =
this.urlNodeSyntaxHelperRegistry.getCurrentUrlNodeSyntaxHelper(request);
final PortalRequestInfoImpl portalRequestInfo = new PortalRequestInfoImpl();
IPortletWindowId targetedPortletWindowId = null;
PortletRequestInfoImpl targetedPortletRequestInfo = null;
final String[] requestPathParts = SLASH_PATTERN.split(requestPath);
UrlState requestedUrlState = null;
ParseStep parseStep = ParseStep.FOLDER;
for (int pathPartIndex = 0; pathPartIndex < requestPathParts.length; pathPartIndex++) {
String pathPart = requestPathParts[pathPartIndex];
logger.trace("In parseStep {} considering pathPart [{}].", parseStep, pathPart);
if (StringUtils.isEmpty(pathPart)) {
continue;
}
switch (parseStep) {
case FOLDER:
{
parseStep = ParseStep.PORTLET;
if (FOLDER_PATH_PREFIX.equals(pathPart)) {
logger.trace(
"Skipping adding {} to the folders deque "
+ "because it is simply the folder path prefix.",
pathPart);
pathPartIndex++;
final LinkedList<String> folders = new LinkedList<String>();
for (; pathPartIndex < requestPathParts.length; pathPartIndex++) {
pathPart = requestPathParts[pathPartIndex];
if (PORTLET_PATH_PREFIX.equals(pathPart)) {
logger.trace(
"Found the portlet part of the path "
+ "demarked by portlet path prefix [{}]; "
+ "stepping back one path part to finish folder processing",
pathPart);
pathPartIndex--;
break;
} else {
if (pathPart.endsWith(REQUEST_TYPE_SUFFIX)) {
logger.trace(
"Found the end of the folder path with pathPart [{}];"
+ " stepping back one, checking for state, "
+ "and finishing folder parsing",
pathPart);
pathPartIndex--;
pathPart = requestPathParts[pathPartIndex];
// If a state was added to the folder list remove it and step back one so
// other code can handle it
if (UrlState.valueOfIngoreCase(pathPart, null)
!= null) {
logger.trace(
"A state was added to the end of folder list {};"
+ " removing it.",
folders);
folders.removeLast();
pathPartIndex--;
}
break;
}
}
logger.trace("Adding pathPart [{}] to folders.", pathPart);
folders.add(pathPart);
}
logger.trace("Folders is [{}]", folders);
if (folders.size() > 0) {
final String targetedLayoutNodeId =
urlNodeSyntaxHelper.getLayoutNodeForFolderNames(
request, folders);
portalRequestInfo.setTargetedLayoutNodeId(targetedLayoutNodeId);
}
break;
}
}
case PORTLET:
{
parseStep = ParseStep.STATE;
final String targetedLayoutNodeId =
portalRequestInfo.getTargetedLayoutNodeId();
if (PORTLET_PATH_PREFIX.equals(pathPart)) {
if (++pathPartIndex < requestPathParts.length) {
pathPart = requestPathParts[pathPartIndex];
targetedPortletWindowId =
urlNodeSyntaxHelper.getPortletForFolderName(
request, targetedLayoutNodeId, pathPart);
}
break;
}
//See if a portlet was targeted by parameter
final String[] targetedPortletIds =
parameterMap.remove(PARAM_TARGET_PORTLET);
if (targetedPortletIds != null && targetedPortletIds.length > 0) {
final String targetedPortletString = targetedPortletIds[0];
targetedPortletWindowId =
urlNodeSyntaxHelper.getPortletForFolderName(
request,
targetedLayoutNodeId,
targetedPortletString);
}
}
case STATE:
{
parseStep = ParseStep.TYPE;
//States other than the default only make sense if a portlet is being targeted
if (targetedPortletWindowId == null) {
break;
}
requestedUrlState = UrlState.valueOfIngoreCase(pathPart, null);
//Set the URL state
if (requestedUrlState != null) {
portalRequestInfo.setUrlState(requestedUrlState);
//If the request is stateless
if (statelessUrlStates.contains(requestedUrlState)) {
final IPortletWindow statelessPortletWindow =
this.portletWindowRegistry
.getOrCreateStatelessPortletWindow(
request, targetedPortletWindowId);
targetedPortletWindowId =
statelessPortletWindow.getPortletWindowId();
}
//Create the portlet request info
targetedPortletRequestInfo =
portalRequestInfo.getPortletRequestInfo(
targetedPortletWindowId);
portalRequestInfo.setTargetedPortletWindowId(
targetedPortletWindowId);
//Set window state based on URL State first then look for the window state parameter
switch (requestedUrlState) {
case MAX:
{
targetedPortletRequestInfo.setWindowState(
WindowState.MAXIMIZED);
}
break;
case DETACHED:
{
targetedPortletRequestInfo.setWindowState(
IPortletRenderer.DETACHED);
}
break;
case EXCLUSIVE:
{
targetedPortletRequestInfo.setWindowState(
IPortletRenderer.EXCLUSIVE);
}
break;
}
break;
}
}
case TYPE:
{
parseStep = ParseStep.COMPLETE;
if (pathPartIndex == requestPathParts.length - 1
&& pathPart.endsWith(REQUEST_TYPE_SUFFIX)
&& pathPart.length() > REQUEST_TYPE_SUFFIX.length()) {
final String urlTypePart =
pathPart.substring(
0,
pathPart.length() - REQUEST_TYPE_SUFFIX.length());
final UrlType urlType;
//Handle inline resourceIds, look for a . in the request type string and use the suffix as the urlType
final int lastPeriod = urlTypePart.lastIndexOf('.');
if (lastPeriod >= 0 && lastPeriod < urlTypePart.length()) {
final String urlTypePartSuffix =
urlTypePart.substring(lastPeriod + 1);
urlType = UrlType.valueOfIngoreCase(urlTypePartSuffix, null);
if (urlType == UrlType.RESOURCE
&& targetedPortletRequestInfo != null) {
final String resourceId =
urlTypePart.substring(0, lastPeriod);
targetedPortletRequestInfo.setResourceId(resourceId);
}
} else {
urlType = UrlType.valueOfIngoreCase(urlTypePart, null);
}
if (urlType != null) {
portalRequestInfo.setUrlType(urlType);
break;
}
}
}
}
}
//If a targeted portlet window ID is found but no targeted portlet request info has been retrieved yet, set it up
if (targetedPortletWindowId != null && targetedPortletRequestInfo == null) {
targetedPortletRequestInfo =
portalRequestInfo.getPortletRequestInfo(targetedPortletWindowId);
portalRequestInfo.setTargetedPortletWindowId(targetedPortletWindowId);
}
//Get the set of portlet window ids that also have parameters on the url
final String[] additionalPortletIdArray = parameterMap.remove(PARAM_ADDITIONAL_PORTLET);
final Set<String> additionalPortletIds =
Sets.newHashSet(
additionalPortletIdArray != null
? additionalPortletIdArray
: new String[0]);
//Used if there is delegation to capture form-submit and other non-prefixed parameters
//Map of parent id to delegate id
final Map<IPortletWindowId, IPortletWindowId> delegateIdMappings =
new LinkedHashMap<IPortletWindowId, IPortletWindowId>(0);
//Parse all remaining parameters from the request
final Set<Entry<String, String[]>> parameterEntrySet = parameterMap.entrySet();
for (final Iterator<Entry<String, String[]>> parameterEntryItr =
parameterEntrySet.iterator();
parameterEntryItr.hasNext();
) {
final Entry<String, String[]> parameterEntry = parameterEntryItr.next();
final String name = parameterEntry.getKey();
final List<String> values = Arrays.asList(parameterEntry.getValue());
/* NOTE: continues are being used to allow fall-through behavior like a switch statement would provide */
//Portal Parameters, just need to remove the prefix
if (name.startsWith(PORTAL_PARAM_PREFIX)) {
final Map<String, List<String>> portalParameters =
portalRequestInfo.getPortalParameters();
portalParameters.put(
this.safeSubstringAfter(PORTAL_PARAM_PREFIX, name), values);
parameterEntryItr.remove();
continue;
}
//Generic portlet parameters, have to remove the prefix and see if there was a portlet windowId between the prefix and parameter name
if (name.startsWith(PORTLET_PARAM_PREFIX)) {
final Tuple<String, IPortletWindowId> portletParameterParts =
this.parsePortletParameterName(request, name, additionalPortletIds);
final IPortletWindowId portletWindowId = portletParameterParts.second;
final String paramName = portletParameterParts.first;
//Get the portlet parameter map to add the parameter to
final Map<String, List<String>> portletParameters;
if (portletWindowId == null) {
if (targetedPortletRequestInfo == null) {
this.logger.warn(
"Parameter "
+ name
+ " is for the targeted portlet but no portlet is targeted by the request. The parameter will be ignored. Value: "
+ values);
parameterEntryItr.remove();
break;
}
portletParameters = targetedPortletRequestInfo.getPortletParameters();
} else {
final PortletRequestInfoImpl portletRequestInfoImpl =
portalRequestInfo.getPortletRequestInfo(portletWindowId);
portletParameters = portletRequestInfoImpl.getPortletParameters();
}
portletParameters.put(paramName, values);
parameterEntryItr.remove();
continue;
}
// Portlet control parameters are either used directly or as a prefix to a windowId. Use the
// SuffixedPortletParameter to simplify their parsing
for (final SuffixedPortletParameter suffixedPortletParameter :
SuffixedPortletParameter.values()) {
final String parameterPrefix = suffixedPortletParameter.getParameterPrefix();
//Skip to the next parameter prefix if the current doesn't match
if (!name.startsWith(parameterPrefix)) {
continue;
}
//All of these parameters require at least one value
if (values.isEmpty()) {
this.logger.warn(
"Ignoring parameter {} as it must have a value. Value: {}",
name,
values);
break;
}
//Verify the parameter is being used on the correct type of URL
final Set<UrlType> validUrlTypes = suffixedPortletParameter.getValidUrlTypes();
if (!validUrlTypes.contains(portalRequestInfo.getUrlType())) {
this.logger.warn(
"Ignoring parameter {} as it is only valid for {} requests and this is a "
+ "{} request. Value: {}",
name,
validUrlTypes,
portalRequestInfo.getUrlType(),
values);
break;
}
//Determine the portlet window and request info the parameter targets
final IPortletWindowId portletWindowId =
this.parsePortletWindowIdSuffix(
request, parameterPrefix, additionalPortletIds, name);
final PortletRequestInfoImpl portletRequestInfo =
getTargetedPortletRequestInfo(
portalRequestInfo, targetedPortletRequestInfo, portletWindowId);
if (portletRequestInfo == null) {
this.logger.warn(
"Parameter {} is for the targeted portlet but no portlet is targeted"
+ " by the request. The parameter will be ignored. Value: {}",
name,
values);
break;
}
parameterEntryItr.remove();
//Use the enum helper to store the parameter values on the request info
suffixedPortletParameter.updateRequestInfo(
request,
portletWindowRegistry,
portletRequestInfo,
values,
delegateIdMappings);
break;
}
}
//Any non-namespaced parameters still need processing?
if (!parameterMap.isEmpty()) {
//If the parameter was not ignored by a previous parser add it to whatever was targeted (portlet or portal)
final Map<String, List<String>> parameters;
if (!delegateIdMappings.isEmpty()) {
//Resolve the last portlet window in the chain of delegation
PortletRequestInfoImpl delegatePortletRequestInfo = null;
for (final IPortletWindowId delegatePortletWindowId :
delegateIdMappings.values()) {
if (!delegateIdMappings.containsKey(delegatePortletWindowId)) {
delegatePortletRequestInfo =
portalRequestInfo.getPortletRequestInfo(
delegatePortletWindowId);
break;
}
}
if (delegatePortletRequestInfo != null) {
parameters = delegatePortletRequestInfo.getPortletParameters();
} else {
this.logger.warn(
"No root delegate portlet could be resolved, non-namespaced parameters"
+ " will be sent to the targeted portlet. THIS SHOULD NEVER HAPPEN. Delegate"
+ " parent/child mapping: {}",
delegateIdMappings);
if (targetedPortletRequestInfo != null) {
parameters = targetedPortletRequestInfo.getPortletParameters();
} else {
parameters = portalRequestInfo.getPortalParameters();
}
}
} else if (targetedPortletRequestInfo != null) {
parameters = targetedPortletRequestInfo.getPortletParameters();
} else {
parameters = portalRequestInfo.getPortalParameters();
}
ParameterMap.putAllList(parameters, parameterMap);
}
//If a portlet is targeted but no layout node is targeted must be maximized
if (targetedPortletRequestInfo != null
&& portalRequestInfo.getTargetedLayoutNodeId() == null
&& (requestedUrlState == null || requestedUrlState == UrlState.NORMAL)) {
portalRequestInfo.setUrlState(UrlState.MAX);
targetedPortletRequestInfo.setWindowState(WindowState.MAXIMIZED);
}
//Make the request info object read-only, once parsed the request info should be static
portalRequestInfo.makeReadOnly();
request.setAttribute(PORTAL_REQUEST_INFO_ATTR, portalRequestInfo);
logger.debug("Finished building requestInfo: {}", portalRequestInfo);
return portalRequestInfo;
} finally {
request.removeAttribute(PORTAL_REQUEST_PARSING_IN_PROGRESS_ATTR);
}
}
protected IPortalRequestInfo parseLegacyPortalUrl(
HttpServletRequest request, Map<String, String[]> parameterMap) {
final PortalRequestInfoImpl portalRequestInfo = new PortalRequestInfoImpl();
final String[] fname = parameterMap.remove(LEGACY_PARAM_PORTLET_FNAME);
if (fname != null && fname.length > 0) {
final IPortletWindow portletWindow =
this.portletWindowRegistry.getOrCreateDefaultPortletWindowByFname(
request, fname[0]);
if (portletWindow != null) {
logger.debug("Legacy fname parameter {} resolved to {}", fname[0], portletWindow);
final IPortletWindowId portletWindowId = portletWindow.getPortletWindowId();
portalRequestInfo.setTargetedPortletWindowId(portletWindowId);
final PortletRequestInfoImpl portletRequestInfo =
portalRequestInfo.getPortletRequestInfo(portletWindowId);
//Check the portlet request type
final String[] type = parameterMap.remove(LEGACY_PARAM_PORTLET_REQUEST_TYPE);
if (type != null && type.length > 0 && "ACTION".equals(type[0])) {
portalRequestInfo.setUrlType(UrlType.ACTION);
}
//Set the window state
final String[] state = parameterMap.remove(LEGACY_PARAM_PORTLET_STATE);
if (state != null && state.length > 0) {
final WindowState windowState = PortletUtils.getWindowState(state[0]);
//If this isn't an action request only allow PATH communicated WindowStates as none of the other options make sense
if (portalRequestInfo.getUrlType() == UrlType.ACTION
|| PATH_WINDOW_STATES.contains(windowState)) {
portletRequestInfo.setWindowState(windowState);
}
}
//If no window state was set assume MAXIMIZED
if (portletRequestInfo.getWindowState() == null) {
portletRequestInfo.setWindowState(WindowState.MAXIMIZED);
}
//Set the portlet mode
final String[] mode = parameterMap.remove(LEGACY_PARAM_PORTLET_MODE);
if (mode != null && mode.length > 0) {
final PortletMode portletMode = PortletUtils.getPortletMode(mode[0]);
portletRequestInfo.setPortletMode(portletMode);
}
//Set the parameters
final Map<String, List<String>> portletParameters =
portletRequestInfo.getPortletParameters();
for (final Map.Entry<String, String[]> parameterEntry : parameterMap.entrySet()) {
final String prefixedName = parameterEntry.getKey();
//If the parameter starts with the portlet param prefix
if (prefixedName.startsWith(LEGACY_PARAM_PORTLET_PARAM_PREFX)) {
final String name =
prefixedName.substring(LEGACY_PARAM_PORTLET_PARAM_PREFX.length());
portletParameters.put(name, Arrays.asList(parameterEntry.getValue()));
}
}
//Set the url state based on the window state
final UrlState urlState =
this.determineUrlState(portletWindow, portletRequestInfo.getWindowState());
portalRequestInfo.setUrlState(urlState);
} else {
logger.debug(
"Could not find portlet for legacy fname fname parameter {}", fname[0]);
}
}
//Check root=uP_root
final String[] root = parameterMap.remove(LEGACY_PARAM_LAYOUT_ROOT);
if (root != null && root.length > 0) {
if (LEGACY_PARAM_LAYOUT_ROOT_VALUE.equals(root[0])) {
//Check uP_sparam=activeTab
final String[] structParam = parameterMap.remove(LEGACY_PARAM_LAYOUT_STRUCT_PARAM);
if (structParam != null && structParam.length > 0) {
if (LEGACY_PARAM_LAYOUT_TAB_ID.equals(structParam[0])) {
//Get the active tab id
final String[] activeTabId =
parameterMap.remove(LEGACY_PARAM_LAYOUT_TAB_ID);
if (activeTabId != null && activeTabId.length > 0) {
//Get the user's layout and do xpath for tab at index=activeTabId[0]
final IUserInstance userInstance =
this.userInstanceManager.getUserInstance(request);
final IUserPreferencesManager preferencesManager =
userInstance.getPreferencesManager();
final IUserLayoutManager userLayoutManager =
preferencesManager.getUserLayoutManager();
final IUserLayout userLayout = userLayoutManager.getUserLayout();
final String nodeId =
this.xpathOperations.doWithExpression(
"/layout/folder/folder[@type='regular' and @hidden='false'][position() = $activeTabId]/@ID",
Collections.singletonMap("activeTabId", activeTabId[0]),
new Function<XPathExpression, String>() {
@Override
public String apply(
XPathExpression xPathExpression) {
return userLayout.findNodeId(xPathExpression);
}
});
//Found nodeId for activeTabId
if (nodeId != null) {
logger.debug(
"Found layout node {} for legacy activeTabId parameter {}",
nodeId,
activeTabId[0]);
portalRequestInfo.setTargetedLayoutNodeId(nodeId);
} else {
logger.debug(
"No layoout node found for legacy activeTabId parameter {}",
activeTabId[0]);
}
}
}
}
}
}
return portalRequestInfo;
}
/**
* If the targetedPortletWindowId is not null {@link
* IPortalRequestInfo#getPortletRequestInfo(IPortletWindowId)} is called and that value is
* returned. If targetedPortletWindowId is null targetedPortletRequestInfo is returned.
*/
protected PortletRequestInfoImpl getTargetedPortletRequestInfo(
final PortalRequestInfoImpl portalRequestInfo,
final PortletRequestInfoImpl targetedPortletRequestInfo,
final IPortletWindowId targetedPortletWindowId) {
if (targetedPortletWindowId == null) {
return targetedPortletRequestInfo;
}
return portalRequestInfo.getPortletRequestInfo(targetedPortletWindowId);
}
/**
* Parse the parameter name and the optional portlet window id from a fully qualified query
* parameter.
*/
protected Tuple<String, IPortletWindowId> parsePortletParameterName(
HttpServletRequest request, String name, Set<String> additionalPortletIds) {
//Look for a 2nd separator which might indicate a portlet window id
for (final String additionalPortletId : additionalPortletIds) {
final int windowIdIdx = name.indexOf(additionalPortletId);
if (windowIdIdx == -1) {
continue;
}
final String paramName =
name.substring(
PORTLET_PARAM_PREFIX.length()
+ additionalPortletId.length()
+ SEPARATOR.length());
final IPortletWindowId portletWindowId =
this.portletWindowRegistry.getPortletWindowId(request, additionalPortletId);
return new Tuple<String, IPortletWindowId>(paramName, portletWindowId);
}
final String paramName = this.safeSubstringAfter(PORTLET_PARAM_PREFIX, name);
return new Tuple<String, IPortletWindowId>(paramName, null);
}
/**
* Determines if the parameter name contains a {@link IPortletWindowId} after the prefix. The id
* must also be contained in the Set of additionalPortletIds. If no id is found in the parameter
* name null is returned.
*/
protected IPortletWindowId parsePortletWindowIdSuffix(
HttpServletRequest request,
final String prefix,
final Set<String> additionalPortletIds,
final String name) {
//See if the parameter name has an additional separator
final int windowIdStartIdx = name.indexOf(SEPARATOR, prefix.length());
if (windowIdStartIdx < (prefix.length() + SEPARATOR.length()) - 1) {
return null;
}
//Extract the windowId string and see if it was listed as an additional windowId
final String portletWindowIdStr = name.substring(windowIdStartIdx + SEPARATOR.length());
if (additionalPortletIds.contains(portletWindowIdStr)) {
try {
return this.portletWindowRegistry.getPortletWindowId(request, portletWindowIdStr);
} catch (IllegalArgumentException e) {
this.logger.warn(
"Failed to parse portlet window id: "
+ portletWindowIdStr
+ " null will be returned",
e);
}
}
return null;
}
protected String safeSubstringAfter(String prefix, String fullName) {
if (prefix.length() >= fullName.length()) {
return "";
}
return fullName.substring(prefix.length());
}
@Override
public String getCanonicalUrl(HttpServletRequest request) {
boolean isRedirectionToDefaultUrl = false;
request = this.portalRequestUtils.getOriginalPortalRequest(request);
final String cachedCanonicalUrl = (String) request.getAttribute(PORTAL_CANONICAL_URL);
if (cachedCanonicalUrl != null) {
if (logger.isDebugEnabled()) {
logger.debug("short-circuit: found canonicalUrl within request attributes");
}
return cachedCanonicalUrl;
}
final IPortalRequestInfo portalRequestInfo = this.getPortalRequestInfo(request);
final UrlType urlType = portalRequestInfo.getUrlType();
final IPortletWindowId targetedPortletWindowId =
portalRequestInfo.getTargetedPortletWindowId();
final String targetedLayoutNodeId = portalRequestInfo.getTargetedLayoutNodeId();
//Create a portal url builder with the appropriate target
final IPortalUrlBuilder portalUrlBuilder;
if (targetedPortletWindowId != null) {
portalUrlBuilder =
this.portalUrlProvider.getPortalUrlBuilderByPortletWindow(
request, targetedPortletWindowId, urlType);
} else if (targetedLayoutNodeId != null) {
portalUrlBuilder =
this.portalUrlProvider.getPortalUrlBuilderByLayoutNode(
request, targetedLayoutNodeId, urlType);
} else {
portalUrlBuilder = this.portalUrlProvider.getDefaultUrl(request);
isRedirectionToDefaultUrl = request.getPathInfo() != null;
}
//Copy over portal parameters
Map<String, List<String>> portalParameters = portalRequestInfo.getPortalParameters();
if (isRedirectionToDefaultUrl) { //add in redirect parameter so we know that we were redirected
Map<String, List<String>> portalParamsWithRedirect =
new HashMap<String, List<String>>(portalParameters);
portalParamsWithRedirect.put("redirectToDefault", Collections.singletonList("true"));
portalUrlBuilder.setParameters(portalParamsWithRedirect);
} else {
portalUrlBuilder.setParameters(portalParameters);
}
//Copy data for each portlet
for (final IPortletRequestInfo portletRequestInfo :
portalRequestInfo.getPortletRequestInfoMap().values()) {
final IPortletWindowId portletWindowId = portletRequestInfo.getPortletWindowId();
final IPortletUrlBuilder portletUrlBuilder =
portalUrlBuilder.getPortletUrlBuilder(portletWindowId);
//Parameters
final Map<String, List<String>> portletParameters =
portletRequestInfo.getPortletParameters();
portletUrlBuilder.setParameters(portletParameters);
switch (urlType) {
case RESOURCE:
{
//cacheability and resourceId for resource requests
portletUrlBuilder.setCacheability(portletRequestInfo.getCacheability());
portletUrlBuilder.setResourceId(portletRequestInfo.getResourceId());
}
case RENDER:
case ACTION:
{
//state & mode for all requests
portletUrlBuilder.setWindowState(portletRequestInfo.getWindowState());
portletUrlBuilder.setPortletMode(portletRequestInfo.getPortletMode());
break;
}
}
}
return portalUrlBuilder.getUrlString();
}
@Override
public String generateUrl(
HttpServletRequest request, IPortalActionUrlBuilder portalActionUrlBuilder) {
final String redirectLocation = portalActionUrlBuilder.getRedirectLocation();
//If no redirect location just generate the portal url
if (redirectLocation == null) {
return this.generateUrl(request, (IPortalUrlBuilder) portalActionUrlBuilder);
}
final String renderUrlParamName = portalActionUrlBuilder.getRenderUrlParamName();
//If no render param name just return the redirect url
if (renderUrlParamName == null) {
return redirectLocation;
}
//Need to stick the generated portal url onto the redirect url
final StringBuilder redirectLocationBuilder = new StringBuilder(redirectLocation);
final int queryParamStartIndex = redirectLocationBuilder.indexOf("?");
//Already has parameters, add the new one correctly
if (queryParamStartIndex > -1) {
redirectLocationBuilder.append('&');
}
//No parameters, add parm seperator
else {
redirectLocationBuilder.append('?');
}
//Generate the portal url
final String portalRenderUrl =
this.generateUrl(request, (IPortalUrlBuilder) portalActionUrlBuilder);
//Encode the render param name and the render url
final String encoding = this.getEncoding(request);
final String encodedRenderUrlParamName;
final String encodedPortalRenderUrl;
try {
encodedRenderUrlParamName = URLEncoder.encode(renderUrlParamName, encoding);
encodedPortalRenderUrl = URLEncoder.encode(portalRenderUrl, encoding);
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Encoding '" + encoding + "' is not supported.", e);
}
return redirectLocationBuilder
.append(encodedRenderUrlParamName)
.append("=")
.append(encodedPortalRenderUrl)
.toString();
}
@Override
public String generateUrl(HttpServletRequest request, IPortalUrlBuilder portalUrlBuilder) {
Validate.notNull(request, "HttpServletRequest was null");
Validate.notNull(portalUrlBuilder, "IPortalPortletUrl was null");
//Convert the callback request to the portal request
request = this.portalRequestUtils.getOriginalPortalRequest(request);
final IUrlNodeSyntaxHelper urlNodeSyntaxHelper =
this.urlNodeSyntaxHelperRegistry.getCurrentUrlNodeSyntaxHelper(request);
//Get the encoding and create a new URL string builder
final String encoding = this.getEncoding(request);
//Add the portal's context path
final String contextPath = this.getCleanedContextPath(request);
final UrlStringBuilder url =
new UrlStringBuilder(encoding, contextPath.length() > 0 ? contextPath : null);
final Map<IPortletWindowId, IPortletUrlBuilder> portletUrlBuilders =
portalUrlBuilder.getPortletUrlBuilders();
//Build folder path based on targeted portlet or targeted folder
final IPortletWindowId targetedPortletWindowId =
portalUrlBuilder.getTargetPortletWindowId();
final UrlType urlType = portalUrlBuilder.getUrlType();
final UrlState urlState;
final String resourceId;
if (targetedPortletWindowId != null) {
final IPortletWindow portletWindow =
this.portletWindowRegistry.getPortletWindow(request, targetedPortletWindowId);
final IPortletEntity portletEntity = portletWindow.getPortletEntity();
//Add folder information if available: /f/tabId
final String channelSubscribeId = portletEntity.getLayoutNodeId();
final List<String> folderNames =
urlNodeSyntaxHelper.getFolderNamesForLayoutNode(request, channelSubscribeId);
if (!folderNames.isEmpty()) {
url.addPath(FOLDER_PATH_PREFIX);
for (final String folderName : folderNames) {
url.addPath(folderName);
}
}
final IPortletUrlBuilder targetedPortletUrlBuilder =
portletUrlBuilders.get(targetedPortletWindowId);
//Determine the resourceId for resource requests
if (urlType == UrlType.RESOURCE && targetedPortletUrlBuilder != null) {
resourceId = targetedPortletUrlBuilder.getResourceId();
} else {
resourceId = null;
}
//Resource requests will never have a requested window state
urlState = this.determineUrlState(portletWindow, targetedPortletUrlBuilder);
final String targetedPortletString =
urlNodeSyntaxHelper.getFolderNameForPortlet(request, targetedPortletWindowId);
//If a non-normal render url or an action/resource url stick the portlet info in the path
if ((urlType == UrlType.RENDER && urlState != UrlState.NORMAL)
|| urlType == UrlType.ACTION
|| urlType == UrlType.RESOURCE) {
url.addPath(PORTLET_PATH_PREFIX);
url.addPath(targetedPortletString);
}
//For normal render requests (generally multiple portlets on a page) add the targeted portlet as a parameter
else {
url.addParameter(PARAM_TARGET_PORTLET, targetedPortletString);
}
} else {
final String targetFolderId = portalUrlBuilder.getTargetFolderId();
final List<String> folderNames =
urlNodeSyntaxHelper.getFolderNamesForLayoutNode(request, targetFolderId);
if (folderNames != null && !folderNames.isEmpty()) {
url.addPath(FOLDER_PATH_PREFIX);
for (final String folderName : folderNames) {
url.addPath(folderName);
}
}
urlState = UrlState.NORMAL;
resourceId = null;
}
//Add the state of the URL
url.addPath(urlState.toLowercaseString());
//File part specifying the type of URL, resource URLs include the resourceId
if (urlType == UrlType.RESOURCE && resourceId != null) {
url.addPath(resourceId + "." + urlType.toLowercaseString() + REQUEST_TYPE_SUFFIX);
} else {
url.addPath(urlType.toLowercaseString() + REQUEST_TYPE_SUFFIX);
}
//Add all portal parameters
final Map<String, String[]> portalParameters = portalUrlBuilder.getParameters();
url.addParametersArray(PORTAL_PARAM_PREFIX, portalParameters);
//Is this URL stateless
final boolean statelessUrl = statelessUrlStates.contains(urlState);
//Add parameters for every portlet URL
for (final IPortletUrlBuilder portletUrlBuilder : portletUrlBuilders.values()) {
this.addPortletUrlData(
request,
url,
urlType,
portletUrlBuilder,
targetedPortletWindowId,
statelessUrl);
}
if (logger.isDebugEnabled()) {
logger.debug("Generated '" + url + "' from '" + portalUrlBuilder);
}
return url.toString();
}
/** Add the provided portlet url builder data to the url string builder */
protected void addPortletUrlData(
final HttpServletRequest request,
final UrlStringBuilder url,
final UrlType urlType,
final IPortletUrlBuilder portletUrlBuilder,
final IPortletWindowId targetedPortletWindowId,
final boolean statelessUrl) {
final IPortletWindowId portletWindowId = portletUrlBuilder.getPortletWindowId();
final boolean targeted = portletWindowId.equals(targetedPortletWindowId);
IPortletWindow portletWindow = null;
//The targeted portlet doesn't need namespaced parameters
final String prefixedPortletWindowId;
final String suffixedPortletWindowId;
// Track whether or not we are adding parameters to the URL for non-targeted or delegate portlets.
boolean addedNonTargetedPortletParam = false;
if (targeted) {
prefixedPortletWindowId = "";
suffixedPortletWindowId = "";
} else {
final String portletWindowIdStr = portletWindowId.toString();
prefixedPortletWindowId = SEPARATOR + portletWindowIdStr;
suffixedPortletWindowId = portletWindowIdStr + SEPARATOR;
//targeted portlets can never be delegates (it is always the top most parent that is targeted)
portletWindow = this.portletWindowRegistry.getPortletWindow(request, portletWindowId);
final IPortletWindowId delegationParentId = portletWindow.getDelegationParentId();
if (delegationParentId != null) {
url.addParameter(
PARAM_DELEGATE_PARENT + prefixedPortletWindowId,
delegationParentId.getStringId());
addedNonTargetedPortletParam = true;
}
}
switch (urlType) {
case RESOURCE:
{
final String cacheability = portletUrlBuilder.getCacheability();
if (cacheability != null) {
url.addParameter(
PARAM_CACHEABILITY + prefixedPortletWindowId, cacheability);
addedNonTargetedPortletParam =
!targeted ? true : addedNonTargetedPortletParam;
}
final String resourceId = portletUrlBuilder.getResourceId();
if (!targeted && resourceId != null) {
url.addParameter(PARAM_RESOURCE_ID + prefixedPortletWindowId, resourceId);
// We know we are !targeted, but kept the assignement consistent with the other similar
// assignments for clarity.
addedNonTargetedPortletParam =
!targeted ? true : addedNonTargetedPortletParam;
}
break;
}
default:
{
//Add requested portlet mode
final PortletMode portletMode = portletUrlBuilder.getPortletMode();
if (portletMode != null) {
url.addParameter(
PARAM_PORTLET_MODE + prefixedPortletWindowId,
portletMode.toString());
addedNonTargetedPortletParam =
!targeted ? true : addedNonTargetedPortletParam;
} else if (targeted && statelessUrl) {
portletWindow =
portletWindow != null
? portletWindow
: this.portletWindowRegistry.getPortletWindow(
request, portletWindowId);
final PortletMode currentPortletMode = portletWindow.getPortletMode();
url.addParameter(
PARAM_PORTLET_MODE + prefixedPortletWindowId,
currentPortletMode.toString());
// We know we are targeted, but kept the assignement consistent with the other similar
// assignments for clarity. Will always be a nop.
addedNonTargetedPortletParam =
!targeted ? true : addedNonTargetedPortletParam;
}
//Add requested window state if it isn't included on the path
final WindowState windowState = portletUrlBuilder.getWindowState();
if (windowState != null
&& (!targeted || !PATH_WINDOW_STATES.contains(windowState))) {
url.addParameter(
PARAM_WINDOW_STATE + prefixedPortletWindowId,
windowState.toString());
addedNonTargetedPortletParam =
!targeted ? true : addedNonTargetedPortletParam;
}
break;
}
}
if (portletUrlBuilder.getCopyCurrentRenderParameters()) {
url.addParameter(PARAM_COPY_PARAMETERS + suffixedPortletWindowId);
addedNonTargetedPortletParam = !targeted ? true : addedNonTargetedPortletParam;
}
final Map<String, String[]> parameters = portletUrlBuilder.getParameters();
if (!parameters.isEmpty()) {
url.addParametersArray(PORTLET_PARAM_PREFIX + suffixedPortletWindowId, parameters);
addedNonTargetedPortletParam = !targeted ? true : addedNonTargetedPortletParam;
}
// UP-4566 If we have added a portlet parameter for a non-targeted (or delegate) portlet add in the
// additional-portlet parameter to aid in URL parsing later on. Among other things the practical impact
// of this is when we do a search which sends an event to all porlets the user has access to which adds a
// bunch of portletUrlBuilders to the request, we don't add a bunch of unnecessary &PCa parameters to the
// URL since there are no parameters actually being passed to the searched portlets.
if (addedNonTargetedPortletParam) {
url.addParameter(PARAM_ADDITIONAL_PORTLET, portletWindowId.toString());
}
}
/** Determine the {@link UrlState} to use for the targeted portlet window */
protected UrlState determineUrlState(
final IPortletWindow portletWindow,
final IPortletUrlBuilder targetedPortletUrlBuilder) {
final WindowState requestedWindowState;
if (targetedPortletUrlBuilder == null) {
requestedWindowState = null;
} else {
requestedWindowState = targetedPortletUrlBuilder.getWindowState();
}
return determineUrlState(portletWindow, requestedWindowState);
}
/** Determine the {@link UrlState} to use for the targeted portlet window */
protected UrlState determineUrlState(
final IPortletWindow portletWindow, final WindowState requestedWindowState) {
//Determine the UrlState based on the WindowState of the targeted portlet
final WindowState currentWindowState = portletWindow.getWindowState();
final WindowState urlWindowState =
requestedWindowState != null ? requestedWindowState : currentWindowState;
if (WindowState.MAXIMIZED.equals(urlWindowState)) {
return UrlState.MAX;
}
if (IPortletRenderer.DETACHED.equals(urlWindowState)) {
return UrlState.DETACHED;
}
if (IPortletRenderer.EXCLUSIVE.equals(urlWindowState)) {
return UrlState.EXCLUSIVE;
}
if (!WindowState.NORMAL.equals(urlWindowState)
&& !WindowState.MINIMIZED.equals(urlWindowState)) {
this.logger.warn(
"Unknown WindowState '"
+ urlWindowState
+ "' specified for portlet window "
+ portletWindow
+ ", defaulting to UrlState.NORMAL");
}
return UrlState.NORMAL;
}
/**
* Tries to determine the encoded from the request, if not available falls back to configured
* default.
*
* @param request The current request.
* @return The encoding to use.
*/
protected String getEncoding(HttpServletRequest request) {
final String encoding = request.getCharacterEncoding();
if (encoding != null) {
return encoding;
}
return this.defaultEncoding;
}
protected String getCleanedContextPath(HttpServletRequest request) {
String contextPath = request.getContextPath();
if (contextPath.length() == 0) {
return "";
}
//Make sure the context path doesn't start with a /
if (contextPath.charAt(0) == '/') {
contextPath = contextPath.substring(1);
}
//Make sure the URL ends with a /
if (contextPath.charAt(contextPath.length() - 1) == '/') {
contextPath = contextPath.substring(0, contextPath.length() - 1);
}
return contextPath;
}
@Override
public boolean doesRequestPathReferToSpecificAndDifferentContentVsCanonicalPath(
final String requestPath, final String canonicalPath) {
// Assertions.
if (requestPath == null) {
String msg = "Argument 'path1' cannot be null";
throw new IllegalArgumentException(msg);
}
if (canonicalPath == null) {
String msg = "Argument 'path2' cannot be null";
throw new IllegalArgumentException(msg);
}
/*
* If either is legacy, we can't make the determination.
* (Actually I wonder if this task would be possible; too much to
* attempt right now.)
*/
if (LEGACY_URL_PATHS.contains(requestPath) || LEGACY_URL_PATHS.contains(canonicalPath)) {
return false;
}
final ContentTuple requestTuple = ContentTuple.parse(requestPath);
/*
* The requestPath must refer to something specific
*/
if (requestTuple.getFolder() == null && requestTuple.getPortlet() == null) {
return false;
}
final ContentTuple canonicalTuple = ContentTuple.parse(canonicalPath);
/*
* Return true if they are approximately/functionally equivalent.
*/
return !canonicalTuple.equalsIgnoringNullFolderDifferences(requestTuple);
}
private static final class ContentTuple {
private static final Pattern FOLDER_PARSING_PATTERN =
Pattern.compile(".*/f/([a-zA-Z0-9_]+)[\\./]?.*");
private static final Pattern PORTLET_PARSING_PATTERN =
Pattern.compile(".*/p/([a-zA-Z0-9_]+)[\\./]?.*");
private final String folder;
private final String portlet;
public static ContentTuple parse(String path) {
String folder = null; // default
Matcher fMatcher = FOLDER_PARSING_PATTERN.matcher(path);
if (fMatcher.matches()) {
folder = fMatcher.group(1);
}
String portlet = null; // default
Matcher pMatcher = PORTLET_PARSING_PATTERN.matcher(path);
if (pMatcher.matches()) {
portlet = pMatcher.group(1);
}
return new ContentTuple(folder, portlet);
}
public String getFolder() {
return folder;
}
public String getPortlet() {
return portlet;
}
private ContentTuple(String folder, String portlet) {
this.folder = folder;
this.portlet = portlet;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((folder == null) ? 0 : folder.hashCode());
result = prime * result + ((portlet == null) ? 0 : portlet.hashCode());
return result;
}
/**
* Compares two tuples for equality ignoring null folder differences. A path such as
* /uPortal/p/uportal-links/max which doesn't specify a folder should be considered equal to
* a canonical path such as /uPortal/f/welcome/p/uportal-links.u32l1n12/max/render.uP which
* is targeting the same portlet but happens to specify the folder the portlet is on.
*
* @param obj <code>ContentTuple</code> to compare to
* @return true if the <code>ContentTuple's</code> are functionally equal ignoring one
* folder being null
*/
public boolean equalsIgnoringNullFolderDifferences(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ContentTuple other = (ContentTuple) obj;
// If the portlets are not the same, return false.
if (portlet == null) {
if (other.portlet != null) {
return false;
}
} else if (!portlet.equals(other.portlet)) {
return false;
}
// If either or both of the folders are null, return true. Otherwise if the folders are not the same,
// return false.
if (folder == null || other.folder == null) {
return true;
}
return folder.equals(other.folder);
}
}
}