You are viewing a plain text version of this content. The canonical link for it is here.
Posted to pluto-scm@portals.apache.org by ms...@apache.org on 2016/01/18 13:41:28 UTC

[26/35] portals-pluto git commit: Initial integration of bean processor code

http://git-wip-us.apache.org/repos/asf/portals-pluto/blob/2e60a313/pluto-container/src/main/java/org/apache/pluto/container/impl/HttpServletPortletRequestWrapper.java
----------------------------------------------------------------------
diff --git a/pluto-container/src/main/java/org/apache/pluto/container/impl/HttpServletPortletRequestWrapper.java b/pluto-container/src/main/java/org/apache/pluto/container/impl/HttpServletPortletRequestWrapper.java
index e4baac5..74a7fb7 100644
--- a/pluto-container/src/main/java/org/apache/pluto/container/impl/HttpServletPortletRequestWrapper.java
+++ b/pluto-container/src/main/java/org/apache/pluto/container/impl/HttpServletPortletRequestWrapper.java
@@ -1,1465 +1,1462 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF 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.apache.pluto.container.impl;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.Principal;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Map;
-import java.util.NoSuchElementException;
-
-import javax.portlet.ClientDataRequest;
-import javax.portlet.PortletRequest;
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletInputStream;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.http.HttpSession;
-
-import org.apache.pluto.container.PortletInvokerService;
-import org.apache.pluto.container.PortletRequestContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * @author <a href="mailto:ate@douma.nu">Ate Douma</a>
- * @version $Id$
- */
-public class HttpServletPortletRequestWrapper extends HttpServletRequestWrapper
-{
-    private static final Logger logger = LoggerFactory.getLogger(HttpServletPortletRequestWrapper.class);
-    
-    protected static final String INCLUDE_CONTEXT_PATH = "javax.servlet.include.context_path";
-    protected static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
-    protected static final String INCLUDE_QUERY_STRING = "javax.servlet.include.query_string";
-    protected static final String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri";
-    protected static final String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";
-    protected static final String FORWARD_CONTEXT_PATH = "javax.servlet.forward.context_path";
-    protected static final String FORWARD_PATH_INFO = "javax.servlet.forward.path_info";
-    protected static final String FORWARD_QUERY_STRING = "javax.servlet.forward.query_string";
-    protected static final String FORWARD_REQUEST_URI = "javax.servlet.forward.request_uri";
-    protected static final String FORWARD_SERVLET_PATH = "javax.servlet.forward.servlet_path";
-
-    protected static final String[] PATH_ATTRIBUTE_INCLUDE_NAMES = { INCLUDE_CONTEXT_PATH,
-                                                                     INCLUDE_PATH_INFO,
-                                                                     INCLUDE_QUERY_STRING,
-                                                                     INCLUDE_REQUEST_URI,
-                                                                     INCLUDE_SERVLET_PATH };
-
-    protected static final String[] PATH_ATTRIBUTE_FORWARD_NAMES = { FORWARD_CONTEXT_PATH,
-                                                                     FORWARD_PATH_INFO,
-                                                                     FORWARD_QUERY_STRING,
-                                                                     FORWARD_REQUEST_URI,
-                                                                     FORWARD_SERVLET_PATH };
-
-    protected static final String[] PATH_ATTRIBUTE_NAMES = { INCLUDE_CONTEXT_PATH,
-                                                             INCLUDE_PATH_INFO,
-                                                             INCLUDE_QUERY_STRING,
-                                                             INCLUDE_REQUEST_URI,
-                                                             INCLUDE_SERVLET_PATH,
-                                                             FORWARD_CONTEXT_PATH,
-                                                             FORWARD_PATH_INFO,
-                                                             FORWARD_QUERY_STRING,
-                                                             FORWARD_REQUEST_URI,
-                                                             FORWARD_SERVLET_PATH };
-    
-    protected static final HashSet<String> PATH_ATTRIBUTE_NAMES_SET = 
-        new HashSet<String>(Arrays.asList(PATH_ATTRIBUTE_NAMES));
-    
-    /**
-     * PathMethodValues contains the values of a HttpServletRequest PATH methods.
-     */
-    protected static final class PathMethodValues
-    {
-        String contextPath;
-        String servletPath;
-        String pathInfo;
-        String queryString;
-        String requestURI;
-        
-        PathMethodValues(){}
-                
-        PathMethodValues copy(PathMethodValues pmv)
-        {
-            this.contextPath = pmv.contextPath;
-            this.servletPath = pmv.servletPath;
-            this.pathInfo = pmv.pathInfo;
-            this.queryString = pmv.queryString;
-            this.requestURI = pmv.requestURI;
-            return this;
-        }
-    }
-    
-    protected static final String[] DEFAULT_SERVLET_CONTAINER_MANAGED_ATTRIBUTES = { "org.apache.catalina.core.DISPATCHER_TYPE", 
-    																				 "org.apache.catalina.core.DISPATCHER_REQUEST_PATH" };
-    
-    /**
-     * <p>
-     * Some servlet containers like Tomcat (Catalina) use "injected" request attributes within their own Request (dispatch)
-     * wrapper objects to keep track of their current state. Such "injected" attributes are never really "set" or (supposed to be)
-     * visible by the current application request processing logic.
-     * </p><p>
-     * Such special attributes therefore cannot be reliably "managed" or isolated per (portlet) servlet request window as it never
-     * can be known if or when such attributes (value) might change.
-     * </p><p>
-     * And, as these attributes are used internally by the servlet container providing back the wrong (or no) value very easily
-     * can break the expected behavior.
-     * </p><p>
-     * On Tomcat this for instance happens when a forwarded portlet request itself would try an (servlet) include request. Then, its
-     * internal state using "injected" attribute "org.apache.catalina.DISPATCHER_TYPE" is changed.
-     * </p><p>
-     * To support such servlet container internal/injected attribute handling, a static servletContainerManagedAttributes HashSet
-     * is maintained containing the attribute names which value should <em>always</em> be retrieved from the current (injected) parent request.
-     * </p><p>
-     * As default the currently known two Tomcat internal/injected attribute names are used.
-     * </p><p>
-     * For other containers which might use a similar approach these reserved attribute names can be (re)set through the static
-     * method setServletContainerManagedAttributes(String[]), e.g. like with a Springframework based initialization of the container.
-     * </p>
-     */
-    protected static HashSet<String> servletContainerManagedAttributes =
-    	new HashSet<String>(Arrays.asList(DEFAULT_SERVLET_CONTAINER_MANAGED_ATTRIBUTES));
-    
-    /**
-     * DispatchDetection defines how a (nested) RequestDispatcher include/forward call will be detected.
-     * <p>
-     * The dispatch detection is used to optimize building the custom parameters map as returned from the
-     * getParametersMap method as rebuilding the parameters map for each and every access can be quite expensive.
-     * </p>
-     * <p>
-     * A parameter map is constant within the scope of one (level of) request dispatching, but a nested request
-     * dispatch using an additional query string on the dispatch path requires merging of its query string parameters
-     * for the duration of that (nested) dispatch.
-     * </p>
-     * <p>
-     * DispatchDetection defines how such a nested dispatch is detected:
-     * <ul>
-     *   <li>CHECK_STATE: full compare of the current getRequest().getParameterMap() against the initial getParameterMap()</li>
-     *   <li>CHECK_REQUEST_WRAPPER_STACK: check if the webcontainer injected a HttpServletRequestWrapper <em>above</em> this
-     *       request as Tomcat does (which usually will be less time/cpu consuming if many parameters are passed in)</li>
-     *   <li>EVALUATE:  auto detect on first getParameterMap() call if CHECK_REQUEST_WRAPPER_STACK can be used and then switch
-     *       to either CHECK_STATE or CHECK_REQUEST_WRAPPER_STACK DispatchDetection</li>
-     * </ul>
-     * </p>
-     * <p>
-     * By default the CHECK_STATE method is used which in most cases is more optimal except when often many request parameters are used.
-     * </p>
-     */
-    static enum DispatchDetection { CHECK_STATE, CHECK_REQUEST_WRAPPER_STACK, EVALUATE };
-    
-    /**
-     * Current DispatchDetection mode used, defaults to DispatchDetection.CHECK_STATE
-     */
-    static volatile DispatchDetection dispatchDetection = DispatchDetection.CHECK_STATE;
-    
-    /**
-     * Cache for parsed dateHeader values.
-     */
-    protected static final HashMap<String,Long> dateHeaderParseCache = new HashMap<String,Long>();
-    
-    /**
-     * The set of SimpleDateFormat formats to use in getDateHeader().
-     *
-     * Notice that because SimpleDateFormat is not thread-safe, we can't
-     * declare formats[] as a static variable.
-     */
-    protected SimpleDateFormat dateHeaderFormats[] = 
-    {
-        new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
-        new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
-        new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
-    };
-
-    /**
-     * Map of the current, web container provided, PATH_ATTRIBUTE_NAMES attribute values.
-     */
-    protected Map<String,Object> currPathAttributeValues = new HashMap<String,Object>();
-    
-    /**
-     * Map of the first, or first after a (PortletRequestDispatcher initiated nested dispatch, web container provided, PATH_ATTRIBUTE_NAMES attribute values.
-     */
-    protected Map<String,Object> dispPathAttributeValues = new HashMap<String, Object>();
-    
-    /**
-     * Map of the PATH_ATTRIBUTE_NAMES attribute values provided to the invoking servlet as derived for the current (initial or nested) request dispatch.
-     */
-    protected Map<String,Object> pathAttributeValues = new HashMap<String, Object>();
-    
-    /**
-     * Cache of PATH_ATTRIBUTE_NAMES attribute values possibly set using setAttribute.
-     * <p>
-     * This read-through cache protects against concurrent writing to the client request attribute map(s) when using multi-threaded rendering.
-     * </p>
-     */
-    protected Map<String,Object> pathAttributeCache = new HashMap<String, Object>();
-    
-    /**
-     * Current getRequest() PATH method values
-     */
-    protected PathMethodValues currPathMethodValues = new PathMethodValues();
-    
-    /**
-     * The first, or first after a (PortletRequestDispatcher initiated nested dispatch, web container provided, PATH method values.
-     */
-    protected PathMethodValues dispPathMethodValues = new PathMethodValues();
-    
-    /**
-     * PATH method values provided to the invoking servlet as derived for the current (initial or nested) request dispatch
-     */
-    protected PathMethodValues pathMethodValues = new PathMethodValues();
-    
-    /**
-     * The initial, web container provided, PATH method values.
-     * <p>
-     * These values are kept separately from the dispPathMethodValues are the Portlet API retaining those for all further (nested) dispatching.
-     * But this actually only makes sense if the fist dispatch is not done using a NamedDispatcher.
-     * </p>
-     */
-    protected PathMethodValues initPathMethodValues;
-    
-    /**
-     * Flag indicating if the current, web container provided, PATH_ATTRIBUTE_INCLUDE_NAMES attribute values are modified
-     */
-    protected boolean attributeIncludeValuesModified;
-
-    /**
-     * Flag indicating if the current, web container provided, PATH_ATTRIBUTE_FORWARD_NAMES attribute values are modified
-     */
-    protected boolean attributeForwardValuesModified;
-
-    /**
-     * Flag indicating if the current, web container provided, PATH method values are modified
-     */
-    protected boolean methodValuesModified;
-    
-    /**
-     * Flag indicating if the current (possibly nested) request dispatch is based upon a NamedDispatcher.
-     */
-    protected boolean namedDispatch;
-    
-    /**
-     * Flag indicating if the current (possibly nested) request dispatch is <em>intentionally</em> using RequestDispatcher.forward.
-     * <p>Note: it depends on isForwardingPossible() if an actual forward is used or "faked" while performing an include call.</p>
-     */
-    protected boolean forwarded;
-    
-    /**
-     * Flag indicating if the initial level of dispatch is detected.
-     */
-    protected boolean dispatched;
-    
-    /**
-     * Flag indicating if a nested dispatch is detected.
-     */
-    protected boolean nested;
-
-    /**
-     * Current size of the nested HttpServletRequestWrapper stack size, counted from this request up.
-     * <p>
-     * Used together with DispatchDetection mode CHECK_REQUEST_WRAPPER_STACK or EVALUATE to determine if
-     * the current request dispatching "stack" has changed and thus the parameter map needs rebuilding.
-     * </p>
-     * <p>
-     * This type of DispatchDetection only works for web containers like Tomcat which "inject" (and remove) a HttpServletRequestWrapper
-     * of their own on each nested request dispatch call.
-     * </p>
-     */
-    protected int requestWrapperStackSize;
-    
-    /**
-     * Initial parameters map as provided by the web container <em>before</em> the initial request dispatch.
-     * <p>
-     * Used for comparision with the current web container provided parameters map to calculate (possible)
-     * merged parameters when the initial (or a nested) request dispatcher was created using additional query string parameters.
-     * </p>
-     */
-    protected Map<String, String[]> origParameterMap;
-    
-    /**
-     * Cache of current parameters map as provided by the web container.
-     * <p>
-     * Only used with DispatchDetection != CHECK_REQUEST_WRAPPER_STACK for comparision with the current
-     * current web container provided parameters map to detect a possible nested request dispatch
-     * with additional query string parameters (or a return thereof).
-     * </p>
-     */
-    protected Map<String, String[]> currParameterMap;
-    
-    /**
-     * Cache of the derived parameters map to be provided to invoking servlet.
-     * <p>
-     * This parameter map is (re)build on first access to getParametersMap after a (nested) request dispatch or a
-     * return thereof.
-     * </p>
-     */
-    protected Map<String, String[]> parameterMap;
-    
-    protected final PortletRequestContext requestContext;
-    protected final ServletContext servletContext;
-    protected final PortletRequest portletRequest;
-    protected final ClientDataRequest clientDataRequest;
-    protected final String lifecyclePhase;
-    protected final boolean renderPhase;
-    
-    protected HttpSession session;
-    
-    /**
-     * Set the Servlet container managed (projected) attribute names which cannot be isolated per portlet/servlet request window
-     * and therefore need to be retrieved directly from the parent (injected) servlet request object.
-     * @param names array of protected servlet container attribute names
-     */
-    public static void setServletContainerManagedAttributes(String[] names)
-    {
-    	if (names == null)
-    	{
-    		servletContainerManagedAttributes.clear();
-    	}
-    	else
-    	{
-    		servletContainerManagedAttributes = new HashSet<String>(Arrays.asList(names));
-    	}
-    }
-    
-    @SuppressWarnings("unchecked")
-    public HttpServletPortletRequestWrapper(HttpServletRequest request, ServletContext servletContext, HttpSession session, PortletRequest portletRequest, boolean included, boolean namedDispatch)
-    {
-        super(request);
-        this.servletContext = servletContext;
-        this.session = session;
-        this.portletRequest = portletRequest;
-        lifecyclePhase = (String)portletRequest.getAttribute(PortletRequest.LIFECYCLE_PHASE);
-        clientDataRequest = PortletRequest.ACTION_PHASE.equals(lifecyclePhase) || PortletRequest.RESOURCE_PHASE.equals(lifecyclePhase) ? (ClientDataRequest)portletRequest : null;
-        renderPhase = PortletRequest.RENDER_PHASE.equals(lifecyclePhase);
-        requestContext = (PortletRequestContext )portletRequest.getAttribute(PortletInvokerService.REQUEST_CONTEXT);
-        this.forwarded = !included;
-        this.namedDispatch = namedDispatch;
-        origParameterMap = new HashMap<String,String[]>(request.getParameterMap());
-        currParameterMap = origParameterMap;
-        for (String name : PATH_ATTRIBUTE_NAMES)
-        {
-            currPathAttributeValues.put(name,request.getAttribute(name));
-        }
-        currPathMethodValues.contextPath = request.getContextPath();
-        currPathMethodValues.servletPath = request.getServletPath();
-        currPathMethodValues.pathInfo = request.getPathInfo();
-        currPathMethodValues.queryString = request.getQueryString();
-        currPathMethodValues.requestURI = request.getRequestURI();
-        if (dispatchDetection != DispatchDetection.CHECK_STATE)
-        {            
-            HttpServletRequestWrapper currentRequest = this;
-            while ((currentRequest.getRequest()) instanceof HttpServletRequestWrapper)
-            {
-                requestWrapperStackSize++;
-                currentRequest = (HttpServletRequestWrapper)currentRequest.getRequest();
-            }
-        }
-    }
-    
-    /**
-     * Try to parse the given date as a HTTP date. Borrowed and adapted from
-     * Tomcat FastHttpDateFormat
-     */
-    private long parseDateHeader(String value)
-    {
-        Long dateValue = null;
-        try
-        {
-            dateValue = dateHeaderParseCache.get(value);
-        }
-        catch (Exception e)
-        {
-        }
-        if (dateValue == null)
-        {
-            for (int i = 0; i < dateHeaderFormats.length; i++)
-            {
-                try
-                {
-                    Date date = dateHeaderFormats[i].parse(value);
-                    dateValue = new Long(date.getTime());
-                }
-                catch (ParseException e)
-                {
-                }
-            }
-            if (dateValue != null)
-            {
-                synchronized (dateHeaderParseCache)
-                {
-                    if (dateHeaderParseCache.size() > 1000)
-                    {
-                        dateHeaderParseCache.clear();
-                    }
-                    dateHeaderParseCache.put(value, dateValue);
-                }
-            }
-            else
-            {
-                throw new IllegalArgumentException(value);
-            }
-        }
-        return dateValue.longValue();
-    }
-    
-    boolean isForwardingPossible()
-    {
-        return !renderPhase;
-    }
-    
-    /**
-     * Returns the current forwarding state to be cached by the PortletRequestDispatcherImpl
-     * during a nested forward. This state needs to be restored with the restoreFromNestedForward method afterwards.
-     */
-    boolean isForwarded()
-    {
-        return forwarded;
-    }
-    
-    /**
-     * Returns the current namedDispatch state to be cached by the PortletRequestDispatcherImpl
-     * during a nested forward. This state needs to be restored with the restoreFromNestedForward method afterwards.
-     */
-    boolean isNamedDispatch()
-    {
-        return namedDispatch;
-    }
-    
-    /**
-     * Returns a <em>copy</em> of the current dispPathAttributeValues to be cached by the PortletRequestDispatcherImpl
-     * during a nested forward. These need to be restored with the restoreFromNestedForward method afterwards.
-     */
-    Map<String, Object> getPathAttributeValues()
-    {
-        return new HashMap<String, Object>(dispPathAttributeValues);
-    }
-    
-    /**
-     * Returns a <em>copy</em> of the current initPathMethodValues (or null) to be cached by the PortletRequestDispatcherImpl
-     * during a nested forward. These need to be restored with the restoreFromNestedForward method afterwards.
-     */
-    PathMethodValues getInitPathMethodValues()
-    {
-        return initPathMethodValues != null ? new PathMethodValues().copy(initPathMethodValues) : null;
-    }
-    
-    /**
-     * Initiates a nested forward from the PortletRequestDispatcherImpl.
-     */
-    void setNestedForward()
-    {
-        dispatched = false;        
-        forwarded = true;
-        namedDispatch = false;
-    }
-    
-    /**
-     * Restores the previous request path state as cached by the PortletRequestDispatcherImpl after returning from a nested forward.
-     */
-    void restoreFromNestedForward(boolean forwarded, boolean namedDispatch, 
-                                  PathMethodValues pathMethodValues, Map<String, Object> pathAttributeValues)
-    {
-        this.forwarded = forwarded;
-        this.namedDispatch = namedDispatch;
-        dispPathAttributeValues.clear();
-        dispPathAttributeValues.putAll(pathAttributeValues);
-        updateRequestPathState();
-    }
-    
-    /**
-     * Method to check the current web container provided request path state with the cached state to determine if a (nested)
-     * request dispatch has occurred.
-     * <p>
-     * If a (nested) request dispatch has been determined, the derived pathMethodValues and pathAttributeValues are (re)created
-     * to be provided to the invoking servlet as override of the web container "view" of this state.
-     * </p>
-     */
-    protected void updateRequestPathState()
-    {
-        // synchronize current web container path method and attribute values and detect modifications
-        syncDispatcherPathValues();
-        
-        // check and evaluate (significant) modifications to the path state
-        if (checkDispatcherPathValuesChanged())
-        {
-            // dispatch detected
-            
-            if (!dispatched)
-            {
-                //initial dispatch or initial dispatch after a nested forward (from PortletRequestDispatcherImpl) detected
-                initFirstDispatchPathValues();
-            }
-            else
-            {
-                // check if (still) within a nested dispatch or returning back to the initial dispatch
-                checkNestedDispatch();
-            }
-            if (!nested)
-            {
-                // (back to) initial dispatch
-                setupFirstDispatchPathValues();
-            }
-            else // nested
-            {
-                // (still) within a nested dispatch
-                setupNestedDispatchPathValues();
-            }
-        }
-    }
-    
-    private static boolean compareAttributes(Object o1, Object o2)
-    {
-        return (o1 == null && o2 == null) || (o1 != null && o2 != null && o1.equals(o2));
-    }
-    
-    private static String asString(Object o)
-    {
-        return o != null ? o.toString() : null;
-    }
-    
-    /**
-     * Synchronize and compare current web container provided path state with the previously determined path state.
-     */
-    protected void syncDispatcherPathValues()
-    {
-        HttpServletRequest request = (HttpServletRequest)getRequest();
-        Object attrValue;
-        String methodValue;
-
-        attributeIncludeValuesModified = false;
-        attributeForwardValuesModified = false;
-        methodValuesModified = false;
-        
-        for (String name : PATH_ATTRIBUTE_INCLUDE_NAMES)
-        {
-            // first check possible cached path attributes as set through setAttribute
-            attrValue = pathAttributeCache.get(name);
-            if (attrValue == null)
-            {
-                // not cached: get current value from web container
-                attrValue = request.getAttribute(name);
-            }
-            // determine if modified
-            attributeIncludeValuesModified = !attributeIncludeValuesModified ? !compareAttributes(currPathAttributeValues.get(name), attrValue) : true;
-            // save new value for further usage and future modification check
-            currPathAttributeValues.put(name, attrValue);
-        }
-        for (String name : PATH_ATTRIBUTE_FORWARD_NAMES)
-        {
-            // first check possible cached path attributes as set through setAttribute
-            attrValue = pathAttributeCache.get(name);
-            if (attrValue == null)
-            {
-                // not cached: get current value from web container
-                attrValue = request.getAttribute(name);
-            }
-            // determine if modified
-            attributeForwardValuesModified = !attributeForwardValuesModified ? !compareAttributes(currPathAttributeValues.get(name), attrValue) : true;
-            // save new value for further usage and future modification check
-            currPathAttributeValues.put(name, attrValue);
-        }
-        
-        // for all path method values:
-        //   retrieve them from the current web container
-        //   determine if modified
-        //   save them further usage and future modification check
-        
-        methodValue = request.getContextPath();
-        methodValuesModified = methodValuesModified ? true : !compareAttributes(currPathMethodValues.contextPath, methodValue);
-        currPathMethodValues.contextPath = methodValue;
-        
-        methodValue = request.getServletPath();
-        methodValuesModified = methodValuesModified ? true : !compareAttributes(currPathMethodValues.servletPath, methodValue);
-        currPathMethodValues.servletPath = methodValue;
-
-        methodValue = request.getPathInfo();
-        methodValuesModified = methodValuesModified ? true : !compareAttributes(currPathMethodValues.pathInfo, methodValue);
-        currPathMethodValues.pathInfo = methodValue;
-
-        methodValue = request.getQueryString();
-        methodValuesModified = methodValuesModified ? true : !compareAttributes(currPathMethodValues.queryString, methodValue);
-        currPathMethodValues.queryString = methodValue;
-
-        methodValue = request.getRequestURI();
-        methodValuesModified = methodValuesModified ? true : !compareAttributes(currPathMethodValues.requestURI, methodValue);
-        currPathMethodValues.requestURI = methodValue;
-    }
-    
-    /**
-     * Check and evaluate (significant) modifications to the path state.
-     */
-    protected boolean checkDispatcherPathValuesChanged()
-    {
-        // initial "clearing" of current request path method and/or attribute values might be done by the container while
-        // still preparing for the dispatch: ignore and "swallow" those changes
-        if (methodValuesModified && currPathMethodValues.servletPath == null)
-        {
-            methodValuesModified = false;
-        }
-        if (attributeIncludeValuesModified && currPathAttributeValues.get(INCLUDE_SERVLET_PATH) == null)
-        {
-            attributeIncludeValuesModified = false;
-        }
-        if (attributeForwardValuesModified && currPathAttributeValues.get(FORWARD_SERVLET_PATH) == null)
-        {
-            attributeForwardValuesModified = false;
-        }
-        return (attributeIncludeValuesModified || attributeForwardValuesModified || methodValuesModified);
-    }
-    
-    /**
-     * Initialize first dispatch path state
-     */
-    protected void initFirstDispatchPathValues()
-    {
-        dispatched = true;
-        dispPathMethodValues.copy(currPathMethodValues);
-        dispPathAttributeValues.putAll(currPathAttributeValues);
-    }
-    
-    /**
-     * Determine if new dispatch path state represents the first dispatch or the first after a nested forward initiated from
-     * RequestDispatcherImpl, or a nested dispatch through ServletContext.getRequestDispatcher() or an include initiated from
-     * PortletRequestDispatcherImpl.
-     */
-    protected void checkNestedDispatch()
-    {
-        nested = false;
-        for (String name : PATH_ATTRIBUTE_NAMES)
-        {
-            if (!compareAttributes(dispPathAttributeValues.get(name), currPathAttributeValues.get(name)))
-            {
-                nested = true;
-                break;
-            }
-        }
-        nested = nested ? true : !compareAttributes(dispPathMethodValues.contextPath, currPathMethodValues.contextPath);
-        nested = nested ? true : !compareAttributes(dispPathMethodValues.servletPath, currPathMethodValues.servletPath);
-        nested = nested ? true : !compareAttributes(dispPathMethodValues.pathInfo, currPathMethodValues.pathInfo);
-        nested = nested ? true : !compareAttributes(dispPathMethodValues.queryString, currPathMethodValues.queryString);
-        nested = nested ? true : !compareAttributes(dispPathMethodValues.requestURI, currPathMethodValues.requestURI);
-    }
-    
-    /**
-     * Deriving the path state values as should be provided to the invoking servlet for the first dispatch level.
-     * <p>
-     * Note: we also come here after returning back to the initial dispatch from a nested dispatch
-     * </p>
-     * <pre>
-     * If (namedDispatch):
-     *   - All request attribute path values should be "hidden"
-     *   - No path method values can/should be provided either, except for getContextPath()
-     *     Note: this is a use-case not properly recognized in the portlet spec!
-     *     Possibly, we might be *required* to keep the existing path method values as provided by the
-     *     web container or maybe provide (only) a "/" as servletPath?
-     * else:
-     *   - If (!forwarded) || (forwarded && !isForwardingPossible()):
-     *        if (initial path method values determined) // see next If step below
-     *          - use initial path method values
-     *        else
-     *          - use javax.servlet.include.* request attribute values as derived path method values (PLT.19.3.8)
-     *     else: // (forwarded && isForwardingPossible())
-     *       current (forward) path method values are OK
-     *   - If (first dispatched && !namedDispatch)
-     *       The current path method values need to be retained for all further (nested) dispatching (see above and below)
-     *       - save current path method values as initial path values
-     *   - If (!forwarded):
-     *       - current javax.servlet.include.* request attribute values are OK
-     *       - "hide" possible javax.servlet.forward.* request attribute values
-     *   - else if (initial path method values determined) // see previous If step above
-     *       - use initial path method values for javax.servlet.forward.* request attribute values
-     *   - else if (forwarded && isForwardingPossible()):
-     *       - use current path method values for javax.servlet.forward.* request attribute values (PLT.19.4.2)
-     *       - "hide" possible javax.servlet.include.* request attribute values
-     *   - else: // (forwarded && !isForwardingPossible())
-     *       - remap javax.servlet.include.* request attribute values to javax.servlet.forward.* values
-     *       - "hide" javax.servlet.include.* request attribute values
-     * </pre>
-     */
-    protected void setupFirstDispatchPathValues()
-    {
-      // Clear possible previously derived pathAttributeValues from a nested dispatch
-      pathAttributeValues.clear();
-      if (namedDispatch)
-      {
-          // only can/must support request.getContextPath()
-          pathMethodValues.contextPath = dispPathMethodValues.contextPath;
-      }
-      else
-      {
-          if (!forwarded || !isForwardingPossible())
-          {
-              if (initPathMethodValues != null)
-              {
-                  pathMethodValues.copy(initPathMethodValues);
-              }
-              else
-              {
-                  pathMethodValues.contextPath = asString(dispPathAttributeValues.get(INCLUDE_CONTEXT_PATH));
-                  pathMethodValues.servletPath = asString(dispPathAttributeValues.get(INCLUDE_SERVLET_PATH));
-                  pathMethodValues.pathInfo = asString(dispPathAttributeValues.get(INCLUDE_PATH_INFO));
-                  pathMethodValues.queryString = asString(dispPathAttributeValues.get(INCLUDE_QUERY_STRING));
-                  pathMethodValues.requestURI = asString(dispPathAttributeValues.get(INCLUDE_REQUEST_URI));
-              }
-          }
-          else // forwarded && isForwardingPossible()
-          {
-              pathMethodValues.copy(dispPathMethodValues);
-          }
-          
-          // Save *first time* path method values as the portlet spec requires
-          // retaining those for all further (nested) dispatching:
-          // - includes: these values will override the path method values
-          // - forwards: these values will override the forward attribute values
-          // Note: this only make "sense" after a first !namedDispatch dispatch, e.g. .servletPath != null
-          if (initPathMethodValues == null && pathMethodValues.servletPath != null)
-          {
-              initPathMethodValues = new PathMethodValues().copy(pathMethodValues);
-          }
-          
-          if (!forwarded)
-          {
-              pathAttributeValues.put(INCLUDE_CONTEXT_PATH, dispPathAttributeValues.get(INCLUDE_CONTEXT_PATH));
-              pathAttributeValues.put(INCLUDE_SERVLET_PATH, dispPathAttributeValues.get(INCLUDE_SERVLET_PATH));
-              pathAttributeValues.put(INCLUDE_PATH_INFO, dispPathAttributeValues.get(INCLUDE_PATH_INFO));
-              pathAttributeValues.put(INCLUDE_QUERY_STRING, dispPathAttributeValues.get(INCLUDE_QUERY_STRING));
-              pathAttributeValues.put(INCLUDE_REQUEST_URI, dispPathAttributeValues.get(INCLUDE_REQUEST_URI));
-          }
-          else if (initPathMethodValues != null)
-          {
-              pathAttributeValues.put(FORWARD_CONTEXT_PATH, initPathMethodValues.contextPath);
-              pathAttributeValues.put(FORWARD_SERVLET_PATH, initPathMethodValues.servletPath);
-              pathAttributeValues.put(FORWARD_PATH_INFO, initPathMethodValues.pathInfo);
-              pathAttributeValues.put(FORWARD_QUERY_STRING, initPathMethodValues.queryString);
-              pathAttributeValues.put(FORWARD_REQUEST_URI, initPathMethodValues.requestURI);
-          }
-          else if (forwarded && isForwardingPossible())
-          {
-              pathAttributeValues.put(FORWARD_CONTEXT_PATH, dispPathMethodValues.contextPath);
-              pathAttributeValues.put(FORWARD_SERVLET_PATH, dispPathMethodValues.servletPath);
-              pathAttributeValues.put(FORWARD_PATH_INFO, dispPathMethodValues.pathInfo);
-              pathAttributeValues.put(FORWARD_QUERY_STRING, dispPathMethodValues.queryString);
-              pathAttributeValues.put(FORWARD_REQUEST_URI, dispPathMethodValues.requestURI);
-          }
-          else // forwarded && !isForwardingPossible()
-          {
-              pathAttributeValues.put(FORWARD_CONTEXT_PATH, dispPathAttributeValues.get(INCLUDE_CONTEXT_PATH));
-              pathAttributeValues.put(FORWARD_SERVLET_PATH, dispPathAttributeValues.get(INCLUDE_SERVLET_PATH));
-              pathAttributeValues.put(FORWARD_PATH_INFO, dispPathAttributeValues.get(INCLUDE_PATH_INFO));
-              pathAttributeValues.put(FORWARD_QUERY_STRING, dispPathAttributeValues.get(INCLUDE_QUERY_STRING));
-              pathAttributeValues.put(FORWARD_REQUEST_URI, dispPathAttributeValues.get(INCLUDE_REQUEST_URI));
-          }
-      }
-    }
-    
-    /**
-     * Deriving the path state values as should be provided to the invoking servlet for a nested dispatch.
-     * <p>
-     * We are not properly in "control" here anymore but can only assume the nested dispatch is still within
-     * the current portlet application and therefore need to reset to the initial path method values during includes.
-     * </p>
-     * <p>
-     * Furthermore, we assume at least the path INCLUDE attribute values as/if provided by the web container to be correct.
-     * </p>
-     * <p>
-     * However we need to retain the initial dispatch forward path attribute values <em>if</em> no new values are provided.
-     * </p>
-     */
-    protected void setupNestedDispatchPathValues()
-    {
-        if (namedDispatch)
-        {
-            // only can/must support request.getContextPath()
-            pathMethodValues.contextPath = dispPathMethodValues.contextPath;
-        }
-        else
-        {
-            if (!forwarded || !isForwardingPossible())
-            {
-                pathMethodValues.copy(initPathMethodValues);
-            }
-            else // forwarded && isForwardingPossible()
-            {
-                pathMethodValues.copy(dispPathMethodValues);
-            }
-        }
-        
-        // whatever the current attribute include path values: assume them correct (even if null)
-        pathAttributeValues.put(INCLUDE_CONTEXT_PATH, currPathAttributeValues.get(INCLUDE_CONTEXT_PATH));
-        pathAttributeValues.put(INCLUDE_SERVLET_PATH, currPathAttributeValues.get(INCLUDE_SERVLET_PATH));
-        pathAttributeValues.put(INCLUDE_PATH_INFO, currPathAttributeValues.get(INCLUDE_PATH_INFO));
-        pathAttributeValues.put(INCLUDE_QUERY_STRING, currPathAttributeValues.get(INCLUDE_QUERY_STRING));
-        pathAttributeValues.put(INCLUDE_REQUEST_URI, currPathAttributeValues.get(INCLUDE_REQUEST_URI));
-        
-        // However: we need to retain our initial dispatch forward path attribute values *if* no new values are provided
-        // To determine if current forward path attributes are set, only need to check the context path
-        if (attributeForwardValuesModified && currPathAttributeValues.get(FORWARD_CONTEXT_PATH) != null)
-        {
-            pathAttributeValues.put(FORWARD_CONTEXT_PATH, currPathAttributeValues.get(FORWARD_CONTEXT_PATH));
-            pathAttributeValues.put(FORWARD_SERVLET_PATH, currPathAttributeValues.get(FORWARD_SERVLET_PATH));
-            pathAttributeValues.put(FORWARD_PATH_INFO, currPathAttributeValues.get(FORWARD_PATH_INFO));
-            pathAttributeValues.put(FORWARD_QUERY_STRING, currPathAttributeValues.get(FORWARD_QUERY_STRING));
-            pathAttributeValues.put(FORWARD_REQUEST_URI, currPathAttributeValues.get(FORWARD_REQUEST_URI));
-        }
-    }
-    
-    /**
-     * Determine if the web container modified the current HttpServletRequestWrapper stack.
-     * <p>
-     * The current HttpServletRequestWrapper hierachy (stack) size is determined and compared against
-     * the previous size.
-     * </p>
-     * <p>
-     * If the size is different the web container "injected" (or removed) a request wrapper of its own
-     * to manage request dispatcher logic like merging additional dispatcher query string parameters.
-     * </p>
-     * <p>
-     * This DispatchDetection solution only works on web containers like Tomcat.
-     * </p>
-     * @return true if the request wrapper stack (size) changed.
-     */
-    protected boolean isRequestWrapperStackChanged()
-    {
-        HttpServletRequestWrapper currentRequest = this;
-        int currentRequestWrapperStackSize = 0;
-        while ((currentRequest.getRequest()) instanceof HttpServletRequestWrapper)
-        {
-            currentRequestWrapperStackSize++;
-            currentRequest = (HttpServletRequestWrapper)currentRequest.getRequest();
-        }
-        if (currentRequestWrapperStackSize != requestWrapperStackSize)
-        {
-            requestWrapperStackSize = currentRequestWrapperStackSize;
-            return true;
-        }
-        return false;
-    }
-    /**
-     * Returns a derived parameters map for the invoking servlet, merging the web container provided parameter map with the portletRequest parameter map
-     * which might contain additional parameters like public render parameters.
-     * <p>
-     * The derived parameters map is cached for the duration of the current request dispatch, and rebuild for every nested dispatch or return thereof.
-     * </p>
-     * <p>
-     * To determine if a nested dispatch occurred (or a return thereof), the current DispatchDetection mode is used:
-     * <ul>
-     *   <li>CHECK_STATE: full compare of the current getRequest().getParameterMap() against the initial getParameterMap()</li>
-     *   <li>CHECK_REQUEST_WRAPPER_STACK: check if the webcontainer injected a HttpServletRequestWrapper <em>above</em> this
-     *       request as Tomcat does (which usually will be less time/cpu consuming if many parameters are passed in)</li>
-     *   <li>EVALUATE:  auto detect on first getParameterMap() call if CHECK_REQUEST_WRAPPER_STACK can be used and then switch
-     *       to either CHECK_STATE or CHECK_REQUEST_WRAPPER_STACK DispatchDetection</li>
-     * </ul>
-     * </p>
-     */
-    @SuppressWarnings("unchecked")
-    @Override
-    public Map<String, String[]> getParameterMap()
-    {
-        boolean dispatchDetected = false;
-        Map<String, String[]> newParameterMap = null;
-        
-        if (dispatchDetection == DispatchDetection.CHECK_REQUEST_WRAPPER_STACK)
-        {
-            if (isRequestWrapperStackChanged())
-            {
-                dispatchDetected = true;
-            }
-        }
-        else
-        {
-            if (dispatchDetection == DispatchDetection.EVALUATE)
-            {
-                if (isRequestWrapperStackChanged())
-                {
-                    dispatchDetected = true;
-                    
-                    if (logger.isDebugEnabled())
-                    {
-                        logger.debug("DispatchDetection: changing from EVALUATE to CHECK_WEBCONTAINER_REQUEST");
-                    }
-                    dispatchDetection = DispatchDetection.CHECK_REQUEST_WRAPPER_STACK;
-                }
-            }
-            if (!dispatchDetected)
-            {
-                // Use parameters maps comparision to determine if a (nested) dispatch occurred or a return thereof.
-                // Note: if a nested dispatch didn't use additional query string parameters,
-                // no change will (need to) be detected.
-                newParameterMap = getRequest().getParameterMap();
-                if (newParameterMap.size() != currParameterMap.size())
-                {
-                    dispatchDetected = true;
-                }
-                else
-                {
-                    for (Map.Entry<String,String[]> entry : newParameterMap.entrySet())
-                    {
-                        String[] newValues = entry.getValue();
-                        String[] currValues = currParameterMap.get(entry.getKey());
-                        if (currValues == null || newValues.length != currValues.length)
-                        {
-                            // no need to compare the actual parameter values as per the servlet spec additional
-                            // query string parameters always must be prepended so doing a length check is enough.
-                            dispatchDetected = true;
-                            break;
-                        }
-                    }
-                }
-                if (dispatchDetected && dispatchDetection == DispatchDetection.EVALUATE)
-                {
-                    if (logger.isDebugEnabled())
-                    {
-                        logger.debug("DispatchDetection: changing from EVALUATE to CHECK_STATE");
-                    }
-                    dispatchDetection = DispatchDetection.CHECK_STATE;
-                }
-            }
-        }
-
-        if (dispatchDetected || parameterMap == null)
-        {
-            if (newParameterMap == null)
-            {
-                newParameterMap = getRequest().getParameterMap();
-            }
-            
-            if (dispatchDetection != DispatchDetection.CHECK_REQUEST_WRAPPER_STACK)
-            {
-                // Save the current parameters map for future comparision
-                // Note: this *must* be a copy as some web containers like WebSphere use
-                // a "dynamic" parameters map where the content of the current
-                // parameters map itself is modified...
-                currParameterMap = new HashMap<String,String[]>(newParameterMap);
-            }
-            Map<String, String[]> diffParameterMap = new HashMap<String, String[]>();
-            
-            // determine the "diff" between the original parameters map and the current one
-            for (Map.Entry<String,String[]> entry : newParameterMap.entrySet())
-            {
-                String[] values = entry.getValue();
-                String[] original = origParameterMap.get(entry.getKey());
-                String[] diff = null;
-                if ( original == null )
-                {
-                    // a new parameter
-                    diff = values.clone();
-                }
-                else if ( values.length > original.length )
-                {
-                    // we've got some additional query string parameter value(s)
-                    diff = new String[values.length - original.length];
-                    System.arraycopy(values,0,diff,0,values.length-original.length);
-                }
-                if ( diff != null )
-                {
-                    diffParameterMap.put(entry.getKey(), diff);
-                }
-            }
-            // we might actually see an empty diff when using DispatchDetection.CHECK_REQUEST_WRAPPER_STACK
-            // in which case the work above turned out to be not needed after all and we can retain the
-            // current cached parametersMap...
-            if (!diffParameterMap.isEmpty())
-            {
-                // build a new parametersMap by merging the diffParametersMap with the portletRequest.parametersMap
-                newParameterMap = new HashMap<String,String[]>(portletRequest.getParameterMap());
-                for (Map.Entry<String, String[]> entry : diffParameterMap.entrySet())
-                {
-                    String[] diff = entry.getValue();
-                    String[] curr = newParameterMap.get(entry.getKey());
-                    if ( curr == null )
-                    {
-                        newParameterMap.put(entry.getKey(), diff);
-                    }
-                    else
-                    {
-                        // we've got some additional query string parameter value(s)
-                        String[] copy = new String[curr.length+diff.length];
-                        System.arraycopy(diff,0,copy,0,diff.length);
-                        System.arraycopy(curr,0,copy,diff.length,curr.length);
-                        newParameterMap.put(entry.getKey(), copy);
-                    }
-                }
-                parameterMap = Collections.unmodifiableMap(newParameterMap);
-            }
-        }        
-        if (parameterMap == null)
-        {
-            // first time and no web container provided parameters
-            parameterMap = portletRequest.getParameterMap();
-        }
-        return parameterMap;
-    }
-
-    @Override
-    public String getParameter(String name)
-    {
-        // derive from getParametersMap() to ensure the cached parameters map is rebuild
-        // when needed for the current (nested) request dispatch
-        String[] values = this.getParameterMap().get(name);
-        return values != null ? values[0] : null;
-    }
-    
-    @Override
-    public Enumeration<String> getParameterNames()
-    {
-        // derive from getParametersMap() to ensure the cached parameters map is rebuild
-        // when needed for the current (nested) request dispatch
-        return Collections.enumeration(this.getParameterMap().keySet());
-    }
-
-    @Override
-    public String[] getParameterValues(String name)
-    {
-        // derive from getParametersMap() to ensure the cached parameters map is rebuild
-        // when needed for the current (nested) request dispatch
-        return this.getParameterMap().get(name);
-    }
-
-    @Override
-    public String getContextPath()
-    {
-        // synchronize the derived path state values first
-        updateRequestPathState();
-        // return the derived path method value
-        return pathMethodValues.contextPath;
-    }
-
-    @Override
-    public String getPathInfo()
-    {
-        // synchronize the derived path state values first
-        updateRequestPathState();
-        // return the derived path method value
-        return pathMethodValues.pathInfo;
-    }
-
-    @Override
-    public String getPathTranslated()
-    {
-        // synchronize the derived path state values first
-        updateRequestPathState();
-        // base the return value on the derived path method value
-        if (pathMethodValues.pathInfo != null && pathMethodValues.contextPath.equals(portletRequest.getContextPath()))
-        {
-            // can only (and possibly) do this while still within the same context
-            return servletContext.getRealPath(pathMethodValues.pathInfo);
-        }
-        return null;
-    }
-
-    @Override
-    public String getQueryString()
-    {
-        // synchronize the derived path state values first
-        updateRequestPathState();
-        // return the derived path method value
-        return pathMethodValues.queryString;
-    }
-
-    @Override
-    public String getRequestURI()
-    {
-        // synchronize the derived path state values first
-        updateRequestPathState();
-        // return the derived path method value
-        return pathMethodValues.requestURI;
-    }
-
-    @Override
-    public String getServletPath()
-    {
-        // synchronize the derived path state values first
-        updateRequestPathState();
-        // return the derived path method value
-        return pathMethodValues.servletPath;
-    }
-
-    @Override
-    public Object getAttribute(String name)
-    {
-        if (PATH_ATTRIBUTE_NAMES_SET.contains(name))
-        {
-            // synchronize the derived path state values first
-            updateRequestPathState();
-            // return the derived path attribute value
-            return pathAttributeValues.get(name);
-        }
-        // First try to retrieve the attribute from the (possibly buffered/cached/previously set) portletRequest
-        // except for servlet container injected (managed) attributes which cannot reliably be retrieved from the portletRequest
-        Object value = servletContainerManagedAttributes.contains(name) ? null : portletRequest.getAttribute(name);
-        // if null, fall back to retrieve the attribute from the web container itself
-        return value != null ? value : requestContext.getAttribute(name, getRequest());
-    }
-
-    @Override
-    public void setAttribute(String name, Object o)
-    {
-        if (PATH_ATTRIBUTE_NAMES_SET.contains(name))
-        {
-            // path attributes are never set/removed directly to/from the
-            // web container but maintained in a separate cache map to 
-            // protect against concurrent writing to the client request attribute map(s)
-            // when using multi-threaded rendering.
-            pathAttributeCache.put(name, o);
-        }
-        else
-        {
-            portletRequest.setAttribute(name, o);
-        }
-    }
-
-    @Override
-    public void removeAttribute(String name)
-    {
-        if (PATH_ATTRIBUTE_NAMES_SET.contains(name))
-        {
-            // path attributes are never set/removed directly to/from the
-            // web container but maintained in a separate cache map to 
-            // protect against concurrent writing to the client request attribute map(s)
-            // when using multi-threaded rendering.
-            pathAttributeCache.remove(name);
-        }
-        else
-        {
-            portletRequest.removeAttribute(name);
-        }
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public Enumeration<String> getAttributeNames()
-    {
-        HashSet<String> names = new HashSet<String>();
-        Enumeration<String> e;
-        for (e = getRequest().getAttributeNames(); e.hasMoreElements();  )
-        {
-            try
-            {
-                names.add(e.nextElement());
-            }
-            catch(NoSuchElementException nse)
-            {
-                // ignore potential concurrent changes when run in parallel mode
-            }
-        }
-        for (e = portletRequest.getAttributeNames(); e.hasMoreElements();  )
-        {
-            try
-            {
-                names.add(e.nextElement());
-            }
-            catch(NoSuchElementException nse)
-            {
-                // ignore potential concurrent changes when run in parallel mode
-            }
-        }
-        // now synchronize the derived path state values before overriding with (or possibly removing from)
-        // the actual names set to enumerate
-        updateRequestPathState();
-        for (String name : PATH_ATTRIBUTE_NAMES)
-        {
-            if (pathAttributeValues.get(name) != null)
-            {
-                // ensure the derived path attribute name is present in the set
-                names.add(name);
-            }
-            else
-            {
-                // remove a possibly web container provided path attribute name
-                // if it currently should not be present based on our derived path state
-                names.remove(name);
-            }
-        }
-        return Collections.enumeration(names);
-    }
-
-    @Override
-    public RequestDispatcher getRequestDispatcher(String path)
-    {
-        if (path != null)
-        {
-            // first determine if the web container does know how to dispatch to this path
-            RequestDispatcher dispatcher = super.getRequestDispatcher(path);
-            if (dispatcher != null)
-            {
-                // we have a RequestDispatcher
-                if (!dispatched)
-                {
-                    // unlikely, but for sanity sake making sure our internal initial state is created
-                    updateRequestPathState();
-                }
-                
-                if (forwarded && isForwardingPossible())
-                {
-                    // The webcontainer already will have set the initial request forward path attributes
-                    // and a subsequent forward (or include) won't need any further special handling
-                    // therefore we can simply let the webcontainer handle this itself
-                    return dispatcher;
-                }
-                else
-                {
-                    // !forwarded || !isForwardingPossible() can and needs to be handled by PortletRequestDispatcherImpl
-                    // when the dispatch is going to be done using forward because that will require special overriding
-                    // handling because of portlet spec requirements.
-                    return new PortletRequestDispatcherImpl(dispatcher, false);
-                }
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public long getDateHeader(String name)
-    {
-        String value = portletRequest.getProperty(name);
-        if (value == null)
-        { 
-            return (-1L);
-        }
-        // Attempt to convert the date header in a variety of formats
-        return parseDateHeader(value);
-    }
-
-    @Override
-    public String getAuthType()
-    {
-        return portletRequest.getAuthType();
-    }
-
-    @Override
-    public Cookie[] getCookies()
-    {
-        return portletRequest.getCookies();
-    }
-
-    @Override
-    public String getHeader(String name)
-    {
-        return portletRequest.getProperty(name);
-    }
-
-    @Override
-    public Enumeration<String> getHeaderNames()
-    {
-        return portletRequest.getPropertyNames();
-    }
-
-    @Override
-    public Enumeration<String> getHeaders(String name)
-    {
-        return portletRequest.getProperties(name);
-    }
-
-    @Override
-    public int getIntHeader(String name)
-    {
-        String property = portletRequest.getProperty(name);
-        if (property == null)
-        {
-            return -1;
-        }
-        return Integer.parseInt(property);
-    }
-
-    @Override
-    public String getMethod()
-    {
-        return renderPhase ? "GET" : super.getMethod();
-    }
-    
-    @Override
-    public HttpSession getSession()
-    {
-        return session !=  null ? session : super.getSession();
-    }
-
-    @Override
-    public HttpSession getSession(boolean create)
-    {
-        return session != null ? session : super.getSession(create);
-    }
-
-    @Override
-    public String getRemoteUser()
-    {
-        return portletRequest.getRemoteUser();
-    }
-
-    @Override
-    public String getRequestedSessionId()
-    {
-        return portletRequest.getRequestedSessionId();
-    }
-
-    @Override
-    public StringBuffer getRequestURL()
-    {
-        return null;
-    }
-
-    @Override
-    public Principal getUserPrincipal()
-    {
-        return portletRequest.getUserPrincipal();
-    }
-
-    @Override
-    public boolean isRequestedSessionIdValid()
-    {
-        return portletRequest.isRequestedSessionIdValid();
-    }
-
-    @Override
-    public boolean isUserInRole(String role)
-    {
-        return portletRequest.isUserInRole(role);
-    }
-
-    @Override
-    public String getCharacterEncoding()
-    {
-        return clientDataRequest != null ? clientDataRequest.getCharacterEncoding() : null;
-    }
-
-    @Override
-    public void setCharacterEncoding(String enc) throws UnsupportedEncodingException
-    {
-        if (clientDataRequest != null)
-        {
-            clientDataRequest.setCharacterEncoding(enc);
-        }
-    }    
-    
-    @Override
-    public int getContentLength()
-    {
-        return clientDataRequest != null ? clientDataRequest.getContentLength() : 0;
-    }
-
-    @Override
-    public String getContentType()
-    {
-        return clientDataRequest != null ? clientDataRequest.getContentType() : null;
-    }
-
-    @Override
-    public ServletInputStream getInputStream() throws IOException
-    {
-        return clientDataRequest != null ? (ServletInputStream)clientDataRequest.getPortletInputStream() : null;
-    }
-
-    @Override
-    public String getLocalAddr()
-    {
-        return null;
-    }
-
-    @Override
-    public Locale getLocale()
-    {
-        return portletRequest.getLocale();
-    }
-
-    @Override
-    public Enumeration<Locale> getLocales()
-    {
-        return portletRequest.getLocales();
-    }
-
-    @Override
-    public String getLocalName()
-    {
-        return null;
-    }
-
-    @Override
-    public int getLocalPort()
-    {
-        return 0;
-    }
-
-    @Override
-    public String getProtocol()
-    {
-        return "HTTP/1.1";
-    }
-
-    @Override
-    public BufferedReader getReader() throws IOException
-    {
-        return clientDataRequest != null ? clientDataRequest.getReader() : null;
-    }
-
-    @Override
-    public String getRealPath(String path)
-    {
-        return null;
-    }
-
-    @Override
-    public String getRemoteAddr()
-    {
-        return null;
-    }
-
-    @Override
-    public String getRemoteHost()
-    {
-        return null;
-    }
-
-    @Override
-    public int getRemotePort()
-    {
-        return 0;
-    }
-
-    @Override
-    public String getScheme()
-    {
-        return portletRequest.getScheme();
-    }
-
-    @Override
-    public String getServerName()
-    {
-        return portletRequest.getServerName();
-    }
-
-    @Override
-    public int getServerPort()
-    {
-        return portletRequest.getServerPort();
-    }
-
-    @Override
-    public boolean isSecure()
-    {
-        return portletRequest.isSecure();
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.pluto.container.impl;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import javax.portlet.ClientDataRequest;
+import javax.portlet.PortletRequest;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpSession;
+
+import org.apache.pluto.container.PortletInvokerService;
+import org.apache.pluto.container.PortletRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author <a href="mailto:ate@douma.nu">Ate Douma</a>
+ * @version $Id$
+ */
+public class HttpServletPortletRequestWrapper extends HttpServletRequestWrapper
+{
+    private static final Logger logger = LoggerFactory.getLogger(HttpServletPortletRequestWrapper.class);
+    
+    protected static final String INCLUDE_CONTEXT_PATH = "javax.servlet.include.context_path";
+    protected static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
+    protected static final String INCLUDE_QUERY_STRING = "javax.servlet.include.query_string";
+    protected static final String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri";
+    protected static final String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";
+    protected static final String FORWARD_CONTEXT_PATH = "javax.servlet.forward.context_path";
+    protected static final String FORWARD_PATH_INFO = "javax.servlet.forward.path_info";
+    protected static final String FORWARD_QUERY_STRING = "javax.servlet.forward.query_string";
+    protected static final String FORWARD_REQUEST_URI = "javax.servlet.forward.request_uri";
+    protected static final String FORWARD_SERVLET_PATH = "javax.servlet.forward.servlet_path";
+
+    protected static final String[] PATH_ATTRIBUTE_INCLUDE_NAMES = { INCLUDE_CONTEXT_PATH,
+                                                                     INCLUDE_PATH_INFO,
+                                                                     INCLUDE_QUERY_STRING,
+                                                                     INCLUDE_REQUEST_URI,
+                                                                     INCLUDE_SERVLET_PATH };
+
+    protected static final String[] PATH_ATTRIBUTE_FORWARD_NAMES = { FORWARD_CONTEXT_PATH,
+                                                                     FORWARD_PATH_INFO,
+                                                                     FORWARD_QUERY_STRING,
+                                                                     FORWARD_REQUEST_URI,
+                                                                     FORWARD_SERVLET_PATH };
+
+    protected static final String[] PATH_ATTRIBUTE_NAMES = { INCLUDE_CONTEXT_PATH,
+                                                             INCLUDE_PATH_INFO,
+                                                             INCLUDE_QUERY_STRING,
+                                                             INCLUDE_REQUEST_URI,
+                                                             INCLUDE_SERVLET_PATH,
+                                                             FORWARD_CONTEXT_PATH,
+                                                             FORWARD_PATH_INFO,
+                                                             FORWARD_QUERY_STRING,
+                                                             FORWARD_REQUEST_URI,
+                                                             FORWARD_SERVLET_PATH };
+    
+    protected static final HashSet<String> PATH_ATTRIBUTE_NAMES_SET = 
+        new HashSet<String>(Arrays.asList(PATH_ATTRIBUTE_NAMES));
+    
+    /**
+     * PathMethodValues contains the values of a HttpServletRequest PATH methods.
+     */
+    protected static final class PathMethodValues
+    {
+        String contextPath;
+        String servletPath;
+        String pathInfo;
+        String queryString;
+        String requestURI;
+        
+        PathMethodValues(){}
+                
+        PathMethodValues copy(PathMethodValues pmv)
+        {
+            this.contextPath = pmv.contextPath;
+            this.servletPath = pmv.servletPath;
+            this.pathInfo = pmv.pathInfo;
+            this.queryString = pmv.queryString;
+            this.requestURI = pmv.requestURI;
+            return this;
+        }
+    }
+    
+    protected static final String[] DEFAULT_SERVLET_CONTAINER_MANAGED_ATTRIBUTES = { "org.apache.catalina.core.DISPATCHER_TYPE", 
+    																				 "org.apache.catalina.core.DISPATCHER_REQUEST_PATH" };
+    
+    /**
+     * <p>
+     * Some servlet containers like Tomcat (Catalina) use "injected" request attributes within their own Request (dispatch)
+     * wrapper objects to keep track of their current state. Such "injected" attributes are never really "set" or (supposed to be)
+     * visible by the current application request processing logic.
+     * </p><p>
+     * Such special attributes therefore cannot be reliably "managed" or isolated per (portlet) servlet request window as it never
+     * can be known if or when such attributes (value) might change.
+     * </p><p>
+     * And, as these attributes are used internally by the servlet container providing back the wrong (or no) value very easily
+     * can break the expected behavior.
+     * </p><p>
+     * On Tomcat this for instance happens when a forwarded portlet request itself would try an (servlet) include request. Then, its
+     * internal state using "injected" attribute "org.apache.catalina.DISPATCHER_TYPE" is changed.
+     * </p><p>
+     * To support such servlet container internal/injected attribute handling, a static servletContainerManagedAttributes HashSet
+     * is maintained containing the attribute names which value should <em>always</em> be retrieved from the current (injected) parent request.
+     * </p><p>
+     * As default the currently known two Tomcat internal/injected attribute names are used.
+     * </p><p>
+     * For other containers which might use a similar approach these reserved attribute names can be (re)set through the static
+     * method setServletContainerManagedAttributes(String[]), e.g. like with a Springframework based initialization of the container.
+     * </p>
+     */
+    protected static HashSet<String> servletContainerManagedAttributes =
+    	new HashSet<String>(Arrays.asList(DEFAULT_SERVLET_CONTAINER_MANAGED_ATTRIBUTES));
+    
+    /**
+     * DispatchDetection defines how a (nested) RequestDispatcher include/forward call will be detected.
+     * <p>
+     * The dispatch detection is used to optimize building the custom parameters map as returned from the
+     * getParametersMap method as rebuilding the parameters map for each and every access can be quite expensive.
+     * </p>
+     * <p>
+     * A parameter map is constant within the scope of one (level of) request dispatching, but a nested request
+     * dispatch using an additional query string on the dispatch path requires merging of its query string parameters
+     * for the duration of that (nested) dispatch.
+     * </p>
+     * <p>
+     * DispatchDetection defines how such a nested dispatch is detected:
+     * <ul>
+     *   <li>CHECK_STATE: full compare of the current getRequest().getParameterMap() against the initial getParameterMap()</li>
+     *   <li>CHECK_REQUEST_WRAPPER_STACK: check if the webcontainer injected a HttpServletRequestWrapper <em>above</em> this
+     *       request as Tomcat does (which usually will be less time/cpu consuming if many parameters are passed in)</li>
+     *   <li>EVALUATE:  auto detect on first getParameterMap() call if CHECK_REQUEST_WRAPPER_STACK can be used and then switch
+     *       to either CHECK_STATE or CHECK_REQUEST_WRAPPER_STACK DispatchDetection</li>
+     * </ul>
+     * </p>
+     * <p>
+     * By default the CHECK_STATE method is used which in most cases is more optimal except when often many request parameters are used.
+     * </p>
+     */
+    static enum DispatchDetection { CHECK_STATE, CHECK_REQUEST_WRAPPER_STACK, EVALUATE };
+    
+    /**
+     * Current DispatchDetection mode used, defaults to DispatchDetection.CHECK_STATE
+     */
+    static volatile DispatchDetection dispatchDetection = DispatchDetection.CHECK_STATE;
+    
+    /**
+     * Cache for parsed dateHeader values.
+     */
+    protected static final HashMap<String,Long> dateHeaderParseCache = new HashMap<String,Long>();
+    
+    /**
+     * The set of SimpleDateFormat formats to use in getDateHeader().
+     *
+     * Notice that because SimpleDateFormat is not thread-safe, we can't
+     * declare formats[] as a static variable.
+     */
+    protected SimpleDateFormat dateHeaderFormats[] = 
+    {
+        new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
+        new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
+        new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
+    };
+
+    /**
+     * Map of the current, web container provided, PATH_ATTRIBUTE_NAMES attribute values.
+     */
+    protected Map<String,Object> currPathAttributeValues = new HashMap<String,Object>();
+    
+    /**
+     * Map of the first, or first after a (PortletRequestDispatcher initiated nested dispatch, web container provided, PATH_ATTRIBUTE_NAMES attribute values.
+     */
+    protected Map<String,Object> dispPathAttributeValues = new HashMap<String, Object>();
+    
+    /**
+     * Map of the PATH_ATTRIBUTE_NAMES attribute values provided to the invoking servlet as derived for the current (initial or nested) request dispatch.
+     */
+    protected Map<String,Object> pathAttributeValues = new HashMap<String, Object>();
+    
+    /**
+     * Cache of PATH_ATTRIBUTE_NAMES attribute values possibly set using setAttribute.
+     * <p>
+     * This read-through cache protects against concurrent writing to the client request attribute map(s) when using multi-threaded rendering.
+     * </p>
+     */
+    protected Map<String,Object> pathAttributeCache = new HashMap<String, Object>();
+    
+    /**
+     * Current getRequest() PATH method values
+     */
+    protected PathMethodValues currPathMethodValues = new PathMethodValues();
+    
+    /**
+     * The first, or first after a (PortletRequestDispatcher initiated nested dispatch, web container provided, PATH method values.
+     */
+    protected PathMethodValues dispPathMethodValues = new PathMethodValues();
+    
+    /**
+     * PATH method values provided to the invoking servlet as derived for the current (initial or nested) request dispatch
+     */
+    protected PathMethodValues pathMethodValues = new PathMethodValues();
+    
+    /**
+     * The initial, web container provided, PATH method values.
+     * <p>
+     * These values are kept separately from the dispPathMethodValues are the Portlet API retaining those for all further (nested) dispatching.
+     * But this actually only makes sense if the fist dispatch is not done using a NamedDispatcher.
+     * </p>
+     */
+    protected PathMethodValues initPathMethodValues;
+    
+    /**
+     * Flag indicating if the current, web container provided, PATH_ATTRIBUTE_INCLUDE_NAMES attribute values are modified
+     */
+    protected boolean attributeIncludeValuesModified;
+
+    /**
+     * Flag indicating if the current, web container provided, PATH_ATTRIBUTE_FORWARD_NAMES attribute values are modified
+     */
+    protected boolean attributeForwardValuesModified;
+
+    /**
+     * Flag indicating if the current, web container provided, PATH method values are modified
+     */
+    protected boolean methodValuesModified;
+    
+    /**
+     * Flag indicating if the current (possibly nested) request dispatch is based upon a NamedDispatcher.
+     */
+    protected boolean namedDispatch;
+    
+    /**
+     * Flag indicating if the current (possibly nested) request dispatch is <em>intentionally</em> using RequestDispatcher.forward.
+     * <p>Note: it depends on isForwardingPossible() if an actual forward is used or "faked" while performing an include call.</p>
+     */
+    protected boolean forwarded;
+    
+    /**
+     * Flag indicating if the initial level of dispatch is detected.
+     */
+    protected boolean dispatched;
+    
+    /**
+     * Flag indicating if a nested dispatch is detected.
+     */
+    protected boolean nested;
+
+    /**
+     * Current size of the nested HttpServletRequestWrapper stack size, counted from this request up.
+     * <p>
+     * Used together with DispatchDetection mode CHECK_REQUEST_WRAPPER_STACK or EVALUATE to determine if
+     * the current request dispatching "stack" has changed and thus the parameter map needs rebuilding.
+     * </p>
+     * <p>
+     * This type of DispatchDetection only works for web containers like Tomcat which "inject" (and remove) a HttpServletRequestWrapper
+     * of their own on each nested request dispatch call.
+     * </p>
+     */
+    protected int requestWrapperStackSize;
+    
+    /**
+     * Initial parameters map as provided by the web container <em>before</em> the initial request dispatch.
+     * <p>
+     * Used for comparision with the current web container provided parameters map to calculate (possible)
+     * merged parameters when the initial (or a nested) request dispatcher was created using additional query string parameters.
+     * </p>
+     */
+    protected Map<String, String[]> origParameterMap;
+    
+    /**
+     * Cache of current parameters map as provided by the web container.
+     * <p>
+     * Only used with DispatchDetection != CHECK_REQUEST_WRAPPER_STACK for comparision with the current
+     * current web container provided parameters map to detect a possible nested request dispatch
+     * with additional query string parameters (or a return thereof).
+     * </p>
+     */
+    protected Map<String, String[]> currParameterMap;
+    
+    /**
+     * Cache of the derived parameters map to be provided to invoking servlet.
+     * <p>
+     * This parameter map is (re)build on first access to getParametersMap after a (nested) request dispatch or a
+     * return thereof.
+     * </p>
+     */
+    protected Map<String, String[]> parameterMap;
+    
+    protected final PortletRequestContext requestContext;
+    protected final ServletContext servletContext;
+    protected final PortletRequest portletRequest;
+    protected final ClientDataRequest clientDataRequest;
+    protected final String lifecyclePhase;
+    protected final boolean renderPhase;
+    
+    protected HttpSession session;
+    
+    /**
+     * Set the Servlet container managed (projected) attribute names which cannot be isolated per portlet/servlet request window
+     * and therefore need to be retrieved directly from the parent (injected) servlet request object.
+     * @param names array of protected servlet container attribute names
+     */
+    public static void setServletContainerManagedAttributes(String[] names)
+    {
+    	if (names == null)
+    	{
+    		servletContainerManagedAttributes.clear();
+    	}
+    	else
+    	{
+    		servletContainerManagedAttributes = new HashSet<String>(Arrays.asList(names));
+    	}
+    }
+    
+    public HttpServletPortletRequestWrapper(HttpServletRequest request, ServletContext servletContext, HttpSession session, PortletRequest portletRequest, boolean included, boolean namedDispatch)
+    {
+        super(request);
+        this.servletContext = servletContext;
+        this.session = session;
+        this.portletRequest = portletRequest;
+        lifecyclePhase = (String)portletRequest.getAttribute(PortletRequest.LIFECYCLE_PHASE);
+        clientDataRequest = PortletRequest.ACTION_PHASE.equals(lifecyclePhase) || PortletRequest.RESOURCE_PHASE.equals(lifecyclePhase) ? (ClientDataRequest)portletRequest : null;
+        renderPhase = PortletRequest.RENDER_PHASE.equals(lifecyclePhase);
+        requestContext = (PortletRequestContext )portletRequest.getAttribute(PortletInvokerService.REQUEST_CONTEXT);
+        this.forwarded = !included;
+        this.namedDispatch = namedDispatch;
+        origParameterMap = new HashMap<String,String[]>(request.getParameterMap());
+        currParameterMap = origParameterMap;
+        for (String name : PATH_ATTRIBUTE_NAMES)
+        {
+            currPathAttributeValues.put(name,request.getAttribute(name));
+        }
+        currPathMethodValues.contextPath = request.getContextPath();
+        currPathMethodValues.servletPath = request.getServletPath();
+        currPathMethodValues.pathInfo = request.getPathInfo();
+        currPathMethodValues.queryString = request.getQueryString();
+        currPathMethodValues.requestURI = request.getRequestURI();
+        if (dispatchDetection != DispatchDetection.CHECK_STATE)
+        {            
+            HttpServletRequestWrapper currentRequest = this;
+            while ((currentRequest.getRequest()) instanceof HttpServletRequestWrapper)
+            {
+                requestWrapperStackSize++;
+                currentRequest = (HttpServletRequestWrapper)currentRequest.getRequest();
+            }
+        }
+    }
+    
+    /**
+     * Try to parse the given date as a HTTP date. Borrowed and adapted from
+     * Tomcat FastHttpDateFormat
+     */
+    private long parseDateHeader(String value)
+    {
+        Long dateValue = null;
+        try
+        {
+            dateValue = dateHeaderParseCache.get(value);
+        }
+        catch (Exception e)
+        {
+        }
+        if (dateValue == null)
+        {
+            for (int i = 0; i < dateHeaderFormats.length; i++)
+            {
+                try
+                {
+                    Date date = dateHeaderFormats[i].parse(value);
+                    dateValue = new Long(date.getTime());
+                }
+                catch (ParseException e)
+                {
+                }
+            }
+            if (dateValue != null)
+            {
+                synchronized (dateHeaderParseCache)
+                {
+                    if (dateHeaderParseCache.size() > 1000)
+                    {
+                        dateHeaderParseCache.clear();
+                    }
+                    dateHeaderParseCache.put(value, dateValue);
+                }
+            }
+            else
+            {
+                throw new IllegalArgumentException(value);
+            }
+        }
+        return dateValue.longValue();
+    }
+    
+    boolean isForwardingPossible()
+    {
+        return !renderPhase;
+    }
+    
+    /**
+     * Returns the current forwarding state to be cached by the PortletRequestDispatcherImpl
+     * during a nested forward. This state needs to be restored with the restoreFromNestedForward method afterwards.
+     */
+    boolean isForwarded()
+    {
+        return forwarded;
+    }
+    
+    /**
+     * Returns the current namedDispatch state to be cached by the PortletRequestDispatcherImpl
+     * during a nested forward. This state needs to be restored with the restoreFromNestedForward method afterwards.
+     */
+    boolean isNamedDispatch()
+    {
+        return namedDispatch;
+    }
+    
+    /**
+     * Returns a <em>copy</em> of the current dispPathAttributeValues to be cached by the PortletRequestDispatcherImpl
+     * during a nested forward. These need to be restored with the restoreFromNestedForward method afterwards.
+     */
+    Map<String, Object> getPathAttributeValues()
+    {
+        return new HashMap<String, Object>(dispPathAttributeValues);
+    }
+    
+    /**
+     * Returns a <em>copy</em> of the current initPathMethodValues (or null) to be cached by the PortletRequestDispatcherImpl
+     * during a nested forward. These need to be restored with the restoreFromNestedForward method afterwards.
+     */
+    PathMethodValues getInitPathMethodValues()
+    {
+        return initPathMethodValues != null ? new PathMethodValues().copy(initPathMethodValues) : null;
+    }
+    
+    /**
+     * Initiates a nested forward from the PortletRequestDispatcherImpl.
+     */
+    void setNestedForward()
+    {
+        dispatched = false;        
+        forwarded = true;
+        namedDispatch = false;
+    }
+    
+    /**
+     * Restores the previous request path state as cached by the PortletRequestDispatcherImpl after returning from a nested forward.
+     */
+    void restoreFromNestedForward(boolean forwarded, boolean namedDispatch, 
+                                  PathMethodValues pathMethodValues, Map<String, Object> pathAttributeValues)
+    {
+        this.forwarded = forwarded;
+        this.namedDispatch = namedDispatch;
+        dispPathAttributeValues.clear();
+        dispPathAttributeValues.putAll(pathAttributeValues);
+        updateRequestPathState();
+    }
+    
+    /**
+     * Method to check the current web container provided request path state with the cached state to determine if a (nested)
+     * request dispatch has occurred.
+     * <p>
+     * If a (nested) request dispatch has been determined, the derived pathMethodValues and pathAttributeValues are (re)created
+     * to be provided to the invoking servlet as override of the web container "view" of this state.
+     * </p>
+     */
+    protected void updateRequestPathState()
+    {
+        // synchronize current web container path method and attribute values and detect modifications
+        syncDispatcherPathValues();
+        
+        // check and evaluate (significant) modifications to the path state
+        if (checkDispatcherPathValuesChanged())
+        {
+            // dispatch detected
+            
+            if (!dispatched)
+            {
+                //initial dispatch or initial dispatch after a nested forward (from PortletRequestDispatcherImpl) detected
+                initFirstDispatchPathValues();
+            }
+            else
+            {
+                // check if (still) within a nested dispatch or returning back to the initial dispatch
+                checkNestedDispatch();
+            }
+            if (!nested)
+            {
+                // (back to) initial dispatch
+                setupFirstDispatchPathValues();
+            }
+            else // nested
+            {
+                // (still) within a nested dispatch
+                setupNestedDispatchPathValues();
+            }
+        }
+    }
+    
+    private static boolean compareAttributes(Object o1, Object o2)
+    {
+        return (o1 == null && o2 == null) || (o1 != null && o2 != null && o1.equals(o2));
+    }
+    
+    private static String asString(Object o)
+    {
+        return o != null ? o.toString() : null;
+    }
+    
+    /**
+     * Synchronize and compare current web container provided path state with the previously determined path state.
+     */
+    protected void syncDispatcherPathValues()
+    {
+        HttpServletRequest request = (HttpServletRequest)getRequest();
+        Object attrValue;
+        String methodValue;
+
+        attributeIncludeValuesModified = false;
+        attributeForwardValuesModified = false;
+        methodValuesModified = false;
+        
+        for (String name : PATH_ATTRIBUTE_INCLUDE_NAMES)
+        {
+            // first check possible cached path attributes as set through setAttribute
+            attrValue = pathAttributeCache.get(name);
+            if (attrValue == null)
+            {
+                // not cached: get current value from web container
+                attrValue = request.getAttribute(name);
+            }
+            // determine if modified
+            attributeIncludeValuesModified = !attributeIncludeValuesModified ? !compareAttributes(currPathAttributeValues.get(name), attrValue) : true;
+            // save new value for further usage and future modification check
+            currPathAttributeValues.put(name, attrValue);
+        }
+        for (String name : PATH_ATTRIBUTE_FORWARD_NAMES)
+        {
+            // first check possible cached path attributes as set through setAttribute
+            attrValue = pathAttributeCache.get(name);
+            if (attrValue == null)
+            {
+                // not cached: get current value from web container
+                attrValue = request.getAttribute(name);
+            }
+            // determine if modified
+            attributeForwardValuesModified = !attributeForwardValuesModified ? !compareAttributes(currPathAttributeValues.get(name), attrValue) : true;
+            // save new value for further usage and future modification check
+            currPathAttributeValues.put(name, attrValue);
+        }
+        
+        // for all path method values:
+        //   retrieve them from the current web container
+        //   determine if modified
+        //   save them further usage and future modification check
+        
+        methodValue = request.getContextPath();
+        methodValuesModified = methodValuesModified ? true : !compareAttributes(currPathMethodValues.contextPath, methodValue);
+        currPathMethodValues.contextPath = methodValue;
+        
+        methodValue = request.getServletPath();
+        methodValuesModified = methodValuesModified ? true : !compareAttributes(currPathMethodValues.servletPath, methodValue);
+        currPathMethodValues.servletPath = methodValue;
+
+        methodValue = request.getPathInfo();
+        methodValuesModified = me

<TRUNCATED>