blob: 0881739397029c0e6e151daac57186f0f115e877 [file] [log] [blame]
/*
* 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.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 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);
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 : getRequest().getAttribute(name);
}
@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();
}
}