Package org.jasig.portal.url

Source Code of org.jasig.portal.url.UrlSyntaxProviderImpl

/**
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig 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:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.jasig.portal.url;

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.jasig.portal.IUserPreferencesManager;
import org.jasig.portal.layout.IUserLayout;
import org.jasig.portal.layout.IUserLayoutManager;
import org.jasig.portal.portlet.PortletUtils;
import org.jasig.portal.portlet.om.IPortletEntity;
import org.jasig.portal.portlet.om.IPortletWindow;
import org.jasig.portal.portlet.om.IPortletWindowId;
import org.jasig.portal.portlet.registry.IPortletWindowRegistry;
import org.jasig.portal.portlet.rendering.IPortletRenderer;
import org.jasig.portal.user.IUserInstance;
import org.jasig.portal.user.IUserInstanceManager;
import org.jasig.portal.utils.Tuple;
import org.jasig.portal.utils.web.PortalWebUtils;
import org.jasig.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;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;

/**
* {@link IPortalUrlProvider} and {@link IUrlSyntaxProvider} implementation
* that uses a consistent human readable URL format.
*
* @author Eric Dalquist
* @version $Revision$
*/
@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.jasig.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 " + name + " as it must have a value. Value: " + 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 " + name + " as it is only valid for " + validUrlTypes + " requests and this is a " + portalRequestInfo.getUrlType() + " request. Value: " + 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 " + name + " is for the targeted portlet but no portlet is targeted by the request. The parameter will be ignored. Value: " + 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 #getPortletRequestInfo(IPortalRequestInfo, Map, 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;
        if (targeted) {
            prefixedPortletWindowId = "";
            suffixedPortletWindowId = "";
        }
        else {
            final String portletWindowIdStr = portletWindowId.toString();
            prefixedPortletWindowId = SEPARATOR + portletWindowIdStr;
            suffixedPortletWindowId = portletWindowIdStr + SEPARATOR;
            url.addParameter(PARAM_ADDITIONAL_PORTLET, portletWindowIdStr);

            //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());
            }
        }

        switch (urlType) {
            case RESOURCE: {
                final String cacheability = portletUrlBuilder.getCacheability();
                if(cacheability != null) {
                    url.addParameter(PARAM_CACHEABILITY + prefixedPortletWindowId, cacheability);
                }
               
                final String resourceId = portletUrlBuilder.getResourceId();
                if (!targeted && resourceId != null) {
                    url.addParameter(PARAM_RESOURCE_ID + prefixedPortletWindowId, resourceId);
                }
               
                break;
            }
            default: {
                //Add requested portlet mode
                final PortletMode portletMode = portletUrlBuilder.getPortletMode();
                if (portletMode != null) {
                    url.addParameter(PARAM_PORTLET_MODE + prefixedPortletWindowId, portletMode.toString());
                }
                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());
                }
               
                //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());
                }
               
                break;
            }
        }
       
        if (portletUrlBuilder.getCopyCurrentRenderParameters()) {
            url.addParameter(PARAM_COPY_PARAMETERS + suffixedPortletWindowId);
        }
           
        final Map<String, String[]> parameters = portletUrlBuilder.getParameters();
        if (!parameters.isEmpty()) {
            url.addParametersArray(PORTLET_PARAM_PREFIX + suffixedPortletWindowId, parameters);
        }
    }

    /**
     * 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);

        /*
         * At this point they must be the same
         */
        return !canonicalTuple.equals(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;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            ContentTuple other = (ContentTuple) obj;
            if (folder == null) {
                if (other.folder != null)
                    return false;
            } else if (!folder.equals(other.folder))
                return false;
            if (portlet == null) {
                if (other.portlet != null)
                    return false;
            } else if (!portlet.equals(other.portlet))
                return false;
            return true;
        }

    }

}
TOP

Related Classes of org.jasig.portal.url.UrlSyntaxProviderImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.