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 at...@apache.org on 2009/05/04 16:39:50 UTC

svn commit: r771319 [1/2] - in /portals/pluto/trunk: pluto-container-api/src/main/java/org/apache/pluto/container/ pluto-container-api/src/main/java/org/apache/pluto/container/om/portlet/ pluto-container-driver-api/src/main/java/org/apache/pluto/contai...

Author: ate
Date: Mon May  4 14:39:49 2009
New Revision: 771319

URL: http://svn.apache.org/viewvc?rev=771319&view=rev
Log:
PLUTO-557: Fixing the RequestDispatcher implementation to be more web container generic and pluggable through a Service
See: https://issues.apache.org/jira/browse/PLUTO-557

- A new RequestDispatcherService to allow for a pluggable and extendable Servlet RequestDispatcher implementation
  The HttpPortletServletRequestWrapper class functionality is split into fine-grained protected method and uses protected or package scope member fields to actually make it easy to extend
- all HttpPortletServletRequestWrapper functionality is now heavily documented both inline and as javadoc
- DispatchDetection (a static enum) for *only* getParameterMap is configurable:
  - CHECK_STATE (default): always compare current and previous parameterMap to detect changes
  - CHECK_REQUEST_WRAPPER_STACK: always use the request wrapper stack (size) to see if the dispatch level has changed
  - EVALUATE: upon the first getParameterMap call check if the request wrapper stack (size) is changed and switch to CHECK_REQUEST_WRAPPER_STACK, otherwise to CHECK_STATE
  - the default CHECK_STATE can be overridden as constructor parameter on the (default) RequestDispatcherServiceImpl
  - for detecting a dispatch on access to path method or attribute values, this solution can *not* be used
- PortletApplicationDefininition.getServletMappingURLPattern and RequestDispatcherPathInfo/-Provider no longer are needed and removed again (see also: PLUTO-535)

Added:
    portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/RequestDispatcherService.java   (with props)
    portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/RequestDispatcherServiceImpl.java   (with props)
Removed:
    portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/RequestDispatcherPathInfo.java
    portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/RequestDispatcherPathInfoProvider.java
    portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/RequestDispatcherPathInfoImpl.java
    portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/RequestDispatcherPathInfoProviderImpl.java
Modified:
    portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/ContainerServices.java
    portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/om/portlet/PortletApplicationDefinition.java
    portals/pluto/trunk/pluto-container-driver-api/src/main/java/org/apache/pluto/container/driver/OptionalContainerServices.java
    portals/pluto/trunk/pluto-container-driver-api/src/main/java/org/apache/pluto/container/driver/PlutoServices.java
    portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/HttpServletPortletRequestWrapper.java
    portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/PortletAppDescriptorServiceImpl.java
    portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/PortletContextImpl.java
    portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/PortletRequestDispatcherImpl.java
    portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/om/portlet/impl/PortletAppType.java
    portals/pluto/trunk/pluto-portal-driver/src/main/java/org/apache/pluto/driver/container/DefaultOptionalContainerServices.java
    portals/pluto/trunk/pluto-portal-driver/src/main/java/org/apache/pluto/driver/container/DriverPortletContextImpl.java
    portals/pluto/trunk/pluto-portal-driver/src/main/java/org/apache/pluto/driver/container/PortalDriverServicesImpl.java
    portals/pluto/trunk/pluto-portal-driver/src/main/java/org/apache/pluto/driver/container/PortletContextManager.java

Modified: portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/ContainerServices.java
URL: http://svn.apache.org/viewvc/portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/ContainerServices.java?rev=771319&r1=771318&r2=771319&view=diff
==============================================================================
--- portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/ContainerServices.java (original)
+++ portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/ContainerServices.java Mon May  4 14:39:49 2009
@@ -92,4 +92,10 @@
      * @return a CCPPProfileServiceImplementation
      */
     CCPPProfileService getCCPPProfileService();
+    
+    /**
+     * Returns the RequestDispatcher service implementation
+     * @return a RequestDispatcherService implementation
+     */
+    RequestDispatcherService getRequestDispatcherService();
 }

Added: portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/RequestDispatcherService.java
URL: http://svn.apache.org/viewvc/portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/RequestDispatcherService.java?rev=771319&view=auto
==============================================================================
--- portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/RequestDispatcherService.java (added)
+++ portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/RequestDispatcherService.java Mon May  4 14:39:49 2009
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletRequestDispatcher;
+import javax.portlet.PortletResponse;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.http.HttpSession;
+
+import org.apache.pluto.container.om.portlet.PortletApplicationDefinition;
+
+/**
+ * Service to retrieve a RequestDispatcher and corresponding HttpServletRequest and HttpServletResponse wrappers
+ * for usage by the PortletContext and to support custom Servlet Request dispatching.
+ * 
+ * @version $Id$
+ */
+public interface RequestDispatcherService
+{
+    PortletRequestDispatcher getRequestDispatcher(ServletContext servletContext, PortletApplicationDefinition app,
+                                                  String path);
+
+    PortletRequestDispatcher getNamedDispatcher(ServletContext servletContext, PortletApplicationDefinition app,
+                                                String name);
+
+    HttpServletRequestWrapper getRequestWrapper(ServletContext servletContext, HttpServletRequest servletRequest,
+                                                PortletRequest portletRequest, HttpSession session, boolean included,
+                                                boolean named);
+
+    HttpServletResponseWrapper getResponseWraper(ServletContext servletContext, HttpServletResponse servletResponse,
+                                                 PortletRequest portletRequest, PortletResponse portletResponse,
+                                                 boolean included);
+}

Propchange: portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/RequestDispatcherService.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/RequestDispatcherService.java
------------------------------------------------------------------------------
    svn:keywords = Id

Propchange: portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/RequestDispatcherService.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/om/portlet/PortletApplicationDefinition.java
URL: http://svn.apache.org/viewvc/portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/om/portlet/PortletApplicationDefinition.java?rev=771319&r1=771318&r2=771319&view=diff
==============================================================================
--- portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/om/portlet/PortletApplicationDefinition.java (original)
+++ portals/pluto/trunk/pluto-container-api/src/main/java/org/apache/pluto/container/om/portlet/PortletApplicationDefinition.java Mon May  4 14:39:49 2009
@@ -19,7 +19,6 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Set;
 
 import javax.xml.namespace.QName;
 
@@ -86,9 +85,6 @@
 	List<? extends ContainerRuntimeOption> getContainerRuntimeOptions();
 	ContainerRuntimeOption addContainerRuntimeOption(String name);
 
-    Set<String> getServletMappingURLPatterns();
-    void addServletMappingURLPattern(String servletMappingURLPattern);
-    
     Map<Locale, String> getLocaleEncodingMappings();
     void addLocaleEncodingMapping(Locale locale, String encoding);
 }
\ No newline at end of file

Modified: portals/pluto/trunk/pluto-container-driver-api/src/main/java/org/apache/pluto/container/driver/OptionalContainerServices.java
URL: http://svn.apache.org/viewvc/portals/pluto/trunk/pluto-container-driver-api/src/main/java/org/apache/pluto/container/driver/OptionalContainerServices.java?rev=771319&r1=771318&r2=771319&view=diff
==============================================================================
--- portals/pluto/trunk/pluto-container-driver-api/src/main/java/org/apache/pluto/container/driver/OptionalContainerServices.java (original)
+++ portals/pluto/trunk/pluto-container-driver-api/src/main/java/org/apache/pluto/container/driver/OptionalContainerServices.java Mon May  4 14:39:49 2009
@@ -21,6 +21,7 @@
 import org.apache.pluto.container.PortletEnvironmentService;
 import org.apache.pluto.container.PortletInvokerService;
 import org.apache.pluto.container.PortletPreferencesService;
+import org.apache.pluto.container.RequestDispatcherService;
 import org.apache.pluto.container.UserInfoService;
 
 /**
@@ -80,4 +81,9 @@
      * @return a CCPPProfileServiceImplementation
      */
     CCPPProfileService getCCPPProfileService();
+
+    /**
+     * Returns the RequestDispatcherService
+     */
+    RequestDispatcherService getRequestDispatcherService();
 }

Modified: portals/pluto/trunk/pluto-container-driver-api/src/main/java/org/apache/pluto/container/driver/PlutoServices.java
URL: http://svn.apache.org/viewvc/portals/pluto/trunk/pluto-container-driver-api/src/main/java/org/apache/pluto/container/driver/PlutoServices.java?rev=771319&r1=771318&r2=771319&view=diff
==============================================================================
--- portals/pluto/trunk/pluto-container-driver-api/src/main/java/org/apache/pluto/container/driver/PlutoServices.java (original)
+++ portals/pluto/trunk/pluto-container-driver-api/src/main/java/org/apache/pluto/container/driver/PlutoServices.java Mon May  4 14:39:49 2009
@@ -27,6 +27,7 @@
 import org.apache.pluto.container.PortletPreferencesService;
 import org.apache.pluto.container.PortletRequestContextService;
 import org.apache.pluto.container.PortletURLListenerService;
+import org.apache.pluto.container.RequestDispatcherService;
 import org.apache.pluto.container.UserInfoService;
 
 
@@ -116,4 +117,9 @@
     {
         return driverServices.getPortletURLListenerService();
     }
+    
+    public RequestDispatcherService getRequestDispatcherService()
+    {
+        return driverServices.getRequestDispatcherService();
+    }
 }

Modified: portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/HttpServletPortletRequestWrapper.java
URL: http://svn.apache.org/viewvc/portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/HttpServletPortletRequestWrapper.java?rev=771319&r1=771318&r2=771319&view=diff
==============================================================================
--- portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/HttpServletPortletRequestWrapper.java (original)
+++ portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/HttpServletPortletRequestWrapper.java Mon May  4 14:39:49 2009
@@ -37,17 +37,13 @@
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletInputStream;
-import javax.servlet.ServletRequest;
 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.apache.pluto.container.RequestDispatcherPathInfo;
-import org.apache.pluto.container.RequestDispatcherPathInfoProvider;
-import org.apache.pluto.container.om.portlet.PortletApplicationDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * @author <a href="mailto:ate@douma.nu">Ate Douma</a>
@@ -55,28 +51,100 @@
  */
 public class HttpServletPortletRequestWrapper extends HttpServletRequestWrapper
 {
-    private static final String INCLUDE_CONTEXT_PATH = "javax.servlet.include.context_path";
-    private static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
-    private static final String INCLUDE_QUERY_STRING = "javax.servlet.include.query_string";
-    private static final String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri";
-    private static final String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";
-    private static final String FORWARD_CONTEXT_PATH = "javax.servlet.forward.context_path";
-    private static final String FORWARD_PATH_INFO = "javax.servlet.forward.path_info";
-    private static final String FORWARD_QUERY_STRING = "javax.servlet.forward.query_string";
-    private static final String FORWARD_REQUEST_URI = "javax.servlet.forward.request_uri";
-    private static final String FORWARD_SERVLET_PATH = "javax.servlet.forward.servlet_path";
-    
-    private static final HashSet<String> PATHINFO_ATTRIBUTE_NAMES = 
-        new HashSet<String>(Arrays.asList(new String[] { 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 }));
+    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;
+        }
+    }
+    
+    /**
+     * 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.
@@ -95,36 +163,171 @@
         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();
     
-    private boolean included;
-    private RequestDispatcherPathInfo methodPathInfo;
+    /**
+     * 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 fro 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;
 
-    private Map<String, String[]> parameterMap;
-    private Map<String, String[]> origParameterMap;
+    /**
+     * 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;
     
-    private ServletRequest initialRequest;
-    private ServletRequest currentRequest;
+    protected final ServletContext servletContext;
+    protected final PortletRequest portletRequest;
+    protected final ClientDataRequest clientDataRequest;
+    protected final String lifecyclePhase;
+    protected final boolean renderPhase;
     
-    private final ServletContext servletContext;
-    private final PortletRequest portletRequest;
-    private final ClientDataRequest clientDataRequest;
-    private final String lifecyclePhase;
-    private final boolean renderPhase;
-    private HttpSession session;
-    private Map<String,Object> pathInfoAttributes = new HashMap<String,Object>();
+    protected HttpSession session;
     
     @SuppressWarnings("unchecked")
-    public HttpServletPortletRequestWrapper(HttpServletRequest request, ServletContext servletContext, HttpSession session, PortletRequest portletRequest, RequestDispatcherPathInfo pathInfo, boolean included)
+    public HttpServletPortletRequestWrapper(HttpServletRequest request, ServletContext servletContext, HttpSession session, PortletRequest portletRequest, boolean included, boolean namedDispatch)
     {
         super(request);
-        origParameterMap = request.getParameterMap();
         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);
-        setNewPathInfo(pathInfo, included);
+        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();
+            }
+        }
     }
     
     /**
@@ -173,87 +376,523 @@
         return dateValue.longValue();
     }
     
-    public boolean isForwardingPossible()
+    boolean isForwardingPossible()
     {
         return !renderPhase;
     }
-
-    public boolean isIncluded()
+    
+    /**
+     * 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 included;
+        return namedDispatch;
     }
     
-    public void setNewPathInfo(RequestDispatcherPathInfo methodPathInfo, boolean included)
+    /**
+     * 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()
     {
-        // Only set/override forward attributes if:
-        // - first time
-        // - current request is a forward and a NamedDispatcher (meaning: no forward attributes)
-        boolean setForwardAttributes = this.methodPathInfo == null || (!this.included && this.methodPathInfo.isNamedRequestDispatcher());
-        this.initialRequest = getRequest();
-        this.methodPathInfo = methodPathInfo;
-        if (!methodPathInfo.isNamedRequestDispatcher())
+        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())
         {
-            if (included)
+            // 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)
             {
-                pathInfoAttributes.put(INCLUDE_CONTEXT_PATH, getContextPath());
-                pathInfoAttributes.put(INCLUDE_PATH_INFO, methodPathInfo.getPathInfo());
-                pathInfoAttributes.put(INCLUDE_QUERY_STRING, methodPathInfo.getQueryString());
-                pathInfoAttributes.put(INCLUDE_REQUEST_URI, methodPathInfo.getRequestURI());
-                pathInfoAttributes.put(INCLUDE_SERVLET_PATH, methodPathInfo.getServletPath());
+                // (back to) initial dispatch
+                setupFirstDispatchPathValues();
             }
-            else if (setForwardAttributes)
+            else // nested
             {
-                pathInfoAttributes.put(FORWARD_CONTEXT_PATH, getContextPath());
-                pathInfoAttributes.put(FORWARD_PATH_INFO, methodPathInfo.getPathInfo());
-                pathInfoAttributes.put(FORWARD_QUERY_STRING, methodPathInfo.getQueryString());
-                pathInfoAttributes.put(FORWARD_REQUEST_URI, methodPathInfo.getRequestURI());
-                pathInfoAttributes.put(FORWARD_SERVLET_PATH, methodPathInfo.getServletPath());
+                // (still) within a nested dispatch
+                setupNestedDispatchPathValues();
             }
         }
-        this.included = included;
     }
     
-    public void restorePathInfo(ServletRequest initialRequest, RequestDispatcherPathInfo methodPathInfo, Map<String, Object> pathInfoAttributes, boolean included)
+    private static boolean compareAttributes(Object o1, Object o2)
     {
-        this.initialRequest = initialRequest;
-        this.methodPathInfo = methodPathInfo;
-        this.pathInfoAttributes = new HashMap<String,Object>(pathInfoAttributes);
-        this.included = included;
+        return (o1 == null && o2 == null) || (o1 != null && o2 != null && o1.equals(o2));
     }
     
-    public ServletRequest getInitialRequest()
+    private static String asString(Object o)
     {
-        return initialRequest;
+        return o != null ? o.toString() : null;
     }
     
-    public RequestDispatcherPathInfo getMethodPathInfo()
+    /**
+     * Synchronize and compare current web container provided path state with the previously determined path state.
+     */
+    protected void syncDispatcherPathValues()
     {
-        return methodPathInfo;
+        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;
     }
     
-    public Map<String,Object> getPathInfoAttributes()
+    /**
+     * Check and evaluate (significant) modifications to the path state.
+     */
+    protected boolean checkDispatcherPathValuesChanged()
     {
-        return new HashMap<String,Object>(pathInfoAttributes);
+        // 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);
     }
     
-    @Override
-    public String getParameter(String name)
+    /**
+     * Initialize first dispatch path state
+     */
+    protected void initFirstDispatchPathValues()
     {
-        String[] values = this.getParameterMap().get(name);
-        return values != null ? values[0] : null;
+        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>
+     * As we are not in "control" here anymore, assume path method values and path INCLUDE attribute values
+     * as 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()
+    {
+        // Use the web container provided path values instead of the initial dispatch path values
+        // whatever the current path method values: assume them correct
+        pathMethodValues.copy(currPathMethodValues);
+        
+        // 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()
     {
-        if (currentRequest == null || getRequest() != currentRequest)
+        boolean dispatchDetected = false;
+        Map<String, String[]> newParameterMap = null;
+        
+        if (dispatchDetection == DispatchDetection.CHECK_REQUEST_WRAPPER_STACK)
         {
-            currentRequest = getRequest();
-            Map<String, String[]> currParameterMap = currentRequest.getParameterMap();
+            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[]>();
             
-            for (Map.Entry<String,String[]> entry : currParameterMap.entrySet())
+            // 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());
@@ -274,17 +913,20 @@
                     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())
             {
-                currParameterMap.clear();
-                currParameterMap.putAll(portletRequest.getParameterMap());
+                // 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 = currParameterMap.get(entry.getKey());
+                    String[] curr = newParameterMap.get(entry.getKey());
                     if ( curr == null )
                     {
-                        currParameterMap.put(entry.getKey(), diff.clone());
+                        newParameterMap.put(entry.getKey(), diff);
                     }
                     else
                     {
@@ -292,145 +934,296 @@
                         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);
-                        currParameterMap.put(entry.getKey(), copy);
+                        newParameterMap.put(entry.getKey(), copy);
                     }
                 }
-                parameterMap = Collections.unmodifiableMap(currParameterMap);
+                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 getAuthType()
+    public String getContextPath()
     {
-        return portletRequest.getAuthType();
+        // synchronize the derived path state values first
+        updateRequestPathState();
+        // return the derived path method value
+        return pathMethodValues.contextPath;
     }
 
     @Override
-    public Cookie[] getCookies()
+    public String getPathInfo()
     {
-        return portletRequest.getCookies();
+        // synchronize the derived path state values first
+        updateRequestPathState();
+        // return the derived path method value
+        return pathMethodValues.pathInfo;
     }
 
     @Override
-    public long getDateHeader(String name)
+    public String getPathTranslated()
     {
-        String value = portletRequest.getProperty(name);
-        if (value == null)
-        { 
-            return (-1L);
+        // 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);
         }
-        // Attempt to convert the date header in a variety of formats
-        return parseDateHeader(value);
+        return null;
     }
 
     @Override
-    public String getHeader(String name)
+    public String getQueryString()
     {
-        return portletRequest.getProperty(name);
+        // synchronize the derived path state values first
+        updateRequestPathState();
+        // return the derived path method value
+        return pathMethodValues.queryString;
     }
 
     @Override
-    public Enumeration<String> getHeaderNames()
+    public String getRequestURI()
     {
-        return portletRequest.getPropertyNames();
+        // synchronize the derived path state values first
+        updateRequestPathState();
+        // return the derived path method value
+        return pathMethodValues.requestURI;
     }
 
     @Override
-    public Enumeration<String> getHeaders(String name)
+    public String getServletPath()
     {
-        return portletRequest.getProperties(name);
+        // synchronize the derived path state values first
+        updateRequestPathState();
+        // return the derived path method value
+        return pathMethodValues.servletPath;
     }
 
     @Override
-    public int getIntHeader(String name)
+    public Object getAttribute(String name)
     {
-        String property = portletRequest.getProperty(name);
-        if (property == null)
+        if (PATH_ATTRIBUTE_NAMES_SET.contains(name))
         {
-            return -1;
+            // synchronize the derived path state values first
+            updateRequestPathState();
+            // return the derived path attribute value
+            return pathAttributeValues.get(name);
         }
-        return Integer.parseInt(property);
+        // always first try to retrieve the attribute from the web container itself
+        Object value = getRequest().getAttribute(name);
+        return value != null ? value : portletRequest.getAttribute(name);
     }
 
     @Override
-    public String getMethod()
+    public void setAttribute(String name, Object o)
     {
-        return renderPhase ? "GET" : super.getMethod();
+        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 String getContextPath()
+    public void removeAttribute(String name)
     {
-        return portletRequest.getContextPath();
+        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 String getPathInfo()
+    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)
     {
-        return methodPathInfo.getPathInfo();
+        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 String getPathTranslated()
+    public long getDateHeader(String name)
     {
-        return getPathInfo() != null ? servletContext.getRealPath(getPathInfo()) : null;
+        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 getQueryString()
+    public String getAuthType()
     {
-        return methodPathInfo.getQueryString();
+        return portletRequest.getAuthType();
     }
 
     @Override
-    public String getRemoteUser()
+    public Cookie[] getCookies()
     {
-        return portletRequest.getRemoteUser();
+        return portletRequest.getCookies();
     }
 
     @Override
-    public String getRequestedSessionId()
+    public String getHeader(String name)
     {
-        return portletRequest.getRequestedSessionId();
+        return portletRequest.getProperty(name);
     }
 
     @Override
-    public String getRequestURI()
+    public Enumeration<String> getHeaderNames()
     {
-        return methodPathInfo.getRequestURI();
+        return portletRequest.getPropertyNames();
     }
 
     @Override
-    public StringBuffer getRequestURL()
+    public Enumeration<String> getHeaders(String name)
     {
-        return null;
+        return portletRequest.getProperties(name);
     }
 
     @Override
-    public String getServletPath()
+    public int getIntHeader(String name)
     {
-        return methodPathInfo.getServletPath();
+        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();
@@ -443,80 +1236,39 @@
     }
 
     @Override
-    public Principal getUserPrincipal()
+    public String getRemoteUser()
     {
-        return portletRequest.getUserPrincipal();
+        return portletRequest.getRemoteUser();
     }
 
     @Override
-    public boolean isRequestedSessionIdValid()
+    public String getRequestedSessionId()
     {
-        return portletRequest.isRequestedSessionIdValid();
+        return portletRequest.getRequestedSessionId();
     }
 
     @Override
-    public boolean isUserInRole(String role)
+    public StringBuffer getRequestURL()
     {
-        return portletRequest.isUserInRole(role);
+        return null;
     }
 
     @Override
-    public Object getAttribute(String name)
+    public Principal getUserPrincipal()
     {
-        boolean initialPathInfo = !included || getRequest() == initialRequest;
-        if (initialPathInfo && PATHINFO_ATTRIBUTE_NAMES.contains(name))
-        {
-            return methodPathInfo.isNamedRequestDispatcher() ? null : pathInfoAttributes.get(name);
-        }
-        
-        /*
-         * First try to access the attribute from the *parent* request.
-         * Webcontainers typically "inject" a custom servletrequest in between the
-         * dispatched request (this) and its original "parent", to ensure proper
-         * management of requestdispatcher state.
-         * Webcontainer specific attributes therefore *must* be accessed from the
-         * *current* parent request to ensure proper behavior
-         * Without doing this, for instance Tomcat fails to properly match
-         * servlet filters properly, if at all...
-         * This should not really interfere with normal PortletRequest attribute
-         * management as the PortletRequestContext implementation is assumed to
-         * encode newly set attributes anyway or store them otherwise, hence going
-         * through the current parent request *first* should not yield those
-         * attributes.
-         */
-        Object value = getRequest().getAttribute(name);
-        return value != null ? value : portletRequest.getAttribute(name);
+        return portletRequest.getUserPrincipal();
     }
 
-    @SuppressWarnings("unchecked")
     @Override
-    public Enumeration<String> getAttributeNames()
+    public boolean isRequestedSessionIdValid()
     {
-        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
-            }
-        }
-        return Collections.enumeration(names);
+        return portletRequest.isRequestedSessionIdValid();
+    }
+
+    @Override
+    public boolean isUserInRole(String role)
+    {
+        return portletRequest.isUserInRole(role);
     }
 
     @Override
@@ -526,6 +1278,15 @@
     }
 
     @Override
+    public void setCharacterEncoding(String enc) throws UnsupportedEncodingException
+    {
+        if (clientDataRequest != null)
+        {
+            clientDataRequest.setCharacterEncoding(enc);
+        }
+    }    
+    
+    @Override
     public int getContentLength()
     {
         return clientDataRequest != null ? clientDataRequest.getContentLength() : 0;
@@ -610,52 +1371,6 @@
     }
 
     @Override
-    public RequestDispatcher getRequestDispatcher(String path)
-    {
-        if (path != null)
-        {
-            RequestDispatcher dispatcher = super.getRequestDispatcher(path);
-            if (dispatcher != null)
-            {
-                if (!included && !isForwardingPossible())
-                {
-                    // Special case: current "forward" dispatch is actually done using an "include" (e.g. during RENDER phase)
-                    // Thus we need to intercept a possible additional "forward" again
-                    // See also: PLT.19.4, last paragraph
-                    if (!path.startsWith("/"))
-                    {
-                        String servletPath = getServletPath();
-                        if (servletPath != null)
-                        {                        
-                            path = (servletPath.equals("/") ? "/" : servletPath) + path;
-                        }
-                        else
-                        {
-                            // Initial servlet dispatch was done using a NamedRequestDispatcher:
-                            // Impossible to determine the needed servletPath prefix
-                            // to build a correct RequestDispatcherPathInfo.
-                            // Only possible "solution" now is to assume "/"
-                            // However, chances are the webcontainerr then also will have been "lost" 
-                            // and returned a null value for the requestDispatcher above in which case
-                            // we'll never end up here.
-                            path = "/" + path;
-                        }
-                    }
-                    PortletRequestContext requestContext = (PortletRequestContext)portletRequest.getAttribute(PortletInvokerService.REQUEST_CONTEXT);
-                    PortletApplicationDefinition app = requestContext.getPortletWindow().getPortletEntity().getPortletDefinition().getApplication();
-                    RequestDispatcherPathInfoProvider provider = RequestDispatcherPathInfoProviderImpl.getProvider(requestContext.getPortletConfig().getPortletContext(), app);
-                    return new PortletRequestDispatcherImpl(dispatcher, provider.getPathInfo(getContextPath(),path));
-                }
-                else
-                {
-                    return dispatcher;
-                }
-            }
-        }
-        return null;
-    }
-
-    @Override
     public String getScheme()
     {
         return portletRequest.getScheme();
@@ -678,39 +1393,4 @@
     {
         return portletRequest.isSecure();
     }
-
-    @Override
-    public void removeAttribute(String name)
-    {
-        if (PATHINFO_ATTRIBUTE_NAMES.contains(name))
-        {
-            pathInfoAttributes.remove(name);
-        }
-        else
-        {
-            portletRequest.removeAttribute(name);
-        }
-    }
-
-    @Override
-    public void setAttribute(String name, Object o)
-    {
-        if (PATHINFO_ATTRIBUTE_NAMES.contains(name))
-        {
-            pathInfoAttributes.put(name, o);
-        }
-        else
-        {
-            portletRequest.setAttribute(name, o);
-        }
-    }
-
-    @Override
-    public void setCharacterEncoding(String enc) throws UnsupportedEncodingException
-    {
-        if (clientDataRequest != null)
-        {
-            clientDataRequest.setCharacterEncoding(enc);
-        }
-    }    
 }

Modified: portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/PortletAppDescriptorServiceImpl.java
URL: http://svn.apache.org/viewvc/portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/PortletAppDescriptorServiceImpl.java?rev=771319&r1=771318&r2=771319&view=diff
==============================================================================
--- portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/PortletAppDescriptorServiceImpl.java (original)
+++ portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/PortletAppDescriptorServiceImpl.java Mon May  4 14:39:49 2009
@@ -222,27 +222,6 @@
         NodeList children;
         Element element;
 
-        // retrieve servlet-mapping url patterns
-        nodes = (NodeList)xpath.evaluate("/"+prefix+"web-app/"+prefix+"servlet-mapping", document, XPathConstants.NODESET);
-        if (nodes != null)
-        {
-            String urlPattern;
-            
-            for (int i = 0, nsize = nodes.getLength(); i < nsize; i++)
-            {
-                element = (Element)nodes.item(i);
-                children = element.getElementsByTagName("url-pattern");
-                if (children != null && children.getLength() != 0)
-                {
-                    urlPattern = children.item(0).getTextContent().trim();
-                    if (urlPattern.length() > 0)
-                    {
-                        pa.getServletMappingURLPatterns().add(urlPattern);
-                    }
-                }
-            }
-        }
-        
         // retrieve locale-encoding-mapping
         nodes = (NodeList)xpath.evaluate("/"+prefix+"web-app/"+prefix+"locale-encoding-mapping-list/"+prefix+"locale-encoding-mapping", document, XPathConstants.NODESET);
         if (nodes != null)

Modified: portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/PortletContextImpl.java
URL: http://svn.apache.org/viewvc/portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/PortletContextImpl.java?rev=771319&r1=771318&r2=771319&view=diff
==============================================================================
--- portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/PortletContextImpl.java (original)
+++ portals/pluto/trunk/pluto-container/src/main/java/org/apache/pluto/container/impl/PortletContextImpl.java Mon May  4 14:39:49 2009
@@ -26,15 +26,10 @@
 
 import javax.portlet.PortletContext;
 import javax.portlet.PortletRequestDispatcher;
-import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletContext;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.apache.pluto.container.ContainerInfo;
-import org.apache.pluto.container.RequestDispatcherPathInfoProvider;
-import org.apache.pluto.container.impl.PortletRequestDispatcherImpl;
-import org.apache.pluto.container.impl.RequestDispatcherPathInfoProviderImpl;
+import org.apache.pluto.container.RequestDispatcherService;
 import org.apache.pluto.container.om.portlet.PortletApplicationDefinition;
 
 /**
@@ -44,18 +39,13 @@
  */
 public class PortletContextImpl implements PortletContext
 {
-	/**
-	 *  Logger.
-	 */
-    private static final Logger LOG = LoggerFactory.getLogger(PortletContextImpl.class);
-    
-    
     // Private Member Variables ------------------------------------------------
     
     protected ServletContext servletContext;
     protected PortletApplicationDefinition portletApp;
     protected ContainerInfo containerInfo;
     protected List<String> supportedContainerRuntimeOptions;
+    protected RequestDispatcherService rdService;
 
     // Constructor -------------------------------------------------------------
     
@@ -67,12 +57,14 @@
     public PortletContextImpl(ServletContext servletContext,
                               PortletApplicationDefinition portletApp, 
                               ContainerInfo containerInfo, 
-                              List<String> supportedContainerRuntimeOptions)
+                              List<String> supportedContainerRuntimeOptions,
+                              RequestDispatcherService rdService)
     {
         this.servletContext = servletContext;
         this.portletApp = portletApp;
         this.containerInfo = containerInfo;
         this.supportedContainerRuntimeOptions = supportedContainerRuntimeOptions;
+        this.rdService = rdService;
     }
     
     // PortletContext Impl -----------------------------------------------------
@@ -87,58 +79,12 @@
     
     public PortletRequestDispatcher getRequestDispatcher(String path)
     {
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("PortletRequestDispatcher requested: " + path);
-        }
-        
-        // Check if the path name is valid. A valid path name must not be null
-        //   and must start with a slash '/' as defined by the portlet spec.
-        if (path == null || !path.startsWith("/")) {
-        	if (LOG.isInfoEnabled()) {
-        		LOG.info("Failed to retrieve PortletRequestDispatcher: "
-        				+ "path name must begin with a slash '/'.");
-        	}
-        	return null;
-        }
-        
-        // Construct PortletRequestDispatcher.
-        PortletRequestDispatcher portletRequestDispatcher = null;
-        try {
-            RequestDispatcher servletRequestDispatcher = servletContext.getRequestDispatcher(path);
-            if (servletRequestDispatcher != null) {
-
-                RequestDispatcherPathInfoProvider provider = RequestDispatcherPathInfoProviderImpl.getProvider(this, portletApp);
-            	portletRequestDispatcher = new PortletRequestDispatcherImpl(servletRequestDispatcher, provider.getPathInfo(portletApp.getContextPath(), path));
-            } else {
-            	if (LOG.isInfoEnabled()) {
-            		LOG.info("No matching request dispatcher found for: " + path);
-            	}
-            }
-        } catch (Exception ex) {
-            // We need to catch exception because of a Tomcat 4.x bug.
-            //   Tomcat throws an exception instead of return null if the path
-        	//   was not found.
-        	if (LOG.isInfoEnabled()) {
-        		LOG.info("Failed to retrieve PortletRequestDispatcher: "
-        				+ ex.getMessage());
-        	}
-        	portletRequestDispatcher = null;
-        }
-        return portletRequestDispatcher;
+        return rdService.getRequestDispatcher(servletContext, portletApp, path);
     }
     
-    public PortletRequestDispatcher getNamedDispatcher(String name) {
-        RequestDispatcher dispatcher = servletContext.getNamedDispatcher(name);
-        if (dispatcher != null)
-        {
-            RequestDispatcherPathInfoProvider provider = RequestDispatcherPathInfoProviderImpl.getProvider(this, portletApp);
-            return new PortletRequestDispatcherImpl(dispatcher, provider.getNamedRequestDispatcherPathInfo());
-        }
-    	if (LOG.isInfoEnabled()) {
-    		LOG.info("No matching request dispatcher found for name: "
-    				+ name);
-    	}
-        return null;
+    public PortletRequestDispatcher getNamedDispatcher(String name)
+    {
+        return rdService.getNamedDispatcher(servletContext, portletApp, name);
     }
 
     public InputStream getResourceAsStream(String path) {