/*
 * Copyright (c) 2008, 2020 Oracle and/or its affiliates. All rights reserved.
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed 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.glassfish.grizzly.servlet;

import java.nio.charset.Charset;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import org.glassfish.grizzly.http.server.util.Enumerator;
import org.glassfish.grizzly.http.server.util.Globals;
import org.glassfish.grizzly.http.server.util.ParameterMap;
import org.glassfish.grizzly.http.util.Constants;
import org.glassfish.grizzly.http.util.DataChunk;
import org.glassfish.grizzly.http.util.Parameters;
import org.glassfish.grizzly.utils.Charsets;

import jakarta.servlet.AsyncContext;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;

Wrapper around a jakarta.servlet.http.HttpServletRequest that transforms an application request object (which might be the original one passed to a servlet.
Author:Bongjae Chang
/** * Wrapper around a <code>jakarta.servlet.http.HttpServletRequest</code> that transforms an application request object * (which might be the original one passed to a servlet. * * @author Bongjae Chang */
public class DispatchedHttpServletRequest extends HttpServletRequestWrapper { // ------------------------------------------------------- Static Variables
The set of attribute names that are special for request dispatchers
/** * The set of attribute names that are special for request dispatchers */
private static final HashSet<String> specials = new HashSet<>(15); static { specials.add(RequestDispatcher.INCLUDE_REQUEST_URI); specials.add(RequestDispatcher.INCLUDE_CONTEXT_PATH); specials.add(RequestDispatcher.INCLUDE_SERVLET_PATH); specials.add(RequestDispatcher.INCLUDE_PATH_INFO); specials.add(RequestDispatcher.INCLUDE_QUERY_STRING); specials.add(RequestDispatcher.FORWARD_REQUEST_URI); specials.add(RequestDispatcher.FORWARD_CONTEXT_PATH); specials.add(RequestDispatcher.FORWARD_SERVLET_PATH); specials.add(RequestDispatcher.FORWARD_PATH_INFO); specials.add(RequestDispatcher.FORWARD_QUERY_STRING); specials.add(AsyncContext.ASYNC_REQUEST_URI); specials.add(AsyncContext.ASYNC_CONTEXT_PATH); specials.add(AsyncContext.ASYNC_SERVLET_PATH); specials.add(AsyncContext.ASYNC_PATH_INFO); specials.add(AsyncContext.ASYNC_QUERY_STRING); } // ----------------------------------------------------------- Constructors
Construct a new wrapped request around the specified servlet request.
Params:
  • request – the servlet request being wrapped
  • dispatcherType – the dispatcher type
/** * Construct a new wrapped request around the specified servlet request. * * @param request the servlet request being wrapped * @param dispatcherType the dispatcher type */
public DispatchedHttpServletRequest(HttpServletRequest request, WebappContext context, boolean crossContext, DispatcherType dispatcherType) { super(request); this.context = context; this.crossContext = crossContext; this.dispatcherType = dispatcherType; setRequest(request); } // ----------------------------------------------------- Instance Variables
The context for this request.
/** * The context for this request. */
protected WebappContext context = null;
The context path for this request.
/** * The context path for this request. */
protected String contextPath = null;
If this request is cross context, since this changes session access behavior.
/** * If this request is cross context, since this changes session access behavior. */
protected boolean crossContext = false;
The dispatcher type.
/** * The dispatcher type. */
protected final DispatcherType dispatcherType;
Hash map used in the getParametersMap method.
/** * Hash map used in the getParametersMap method. */
private final ParameterMap parameterMap = new ParameterMap(); private final Parameters mergedParameters = new Parameters();
Have the parameters for this request already been parsed?
/** * Have the parameters for this request already been parsed? */
private boolean parsedParams = false;
The path information for this request.
/** * The path information for this request. */
protected String pathInfo = null;
The query parameters for the current request.
/** * The query parameters for the current request. */
private String queryParamString = null;
The query string for this request.
/** * The query string for this request. */
protected String queryString = null;
The current request dispatcher path.
/** * The current request dispatcher path. */
protected Object requestDispatcherPath = null;
The request URI for this request.
/** * The request URI for this request. */
protected String requestURI = null;
The servlet path for this request.
/** * The servlet path for this request. */
protected String servletPath = null;
Special attributes.
/** * Special attributes. */
private HashMap<String, Object> specialAttributes = null; // ------------------------------------------------- ServletRequest Methods
Override the getAttribute() method of the wrapped request.
Params:
  • name – Name of the attribute to retrieve
/** * Override the <code>getAttribute()</code> method of the wrapped request. * * @param name Name of the attribute to retrieve */
@Override public Object getAttribute(String name) { if (name.equals(Globals.DISPATCHER_REQUEST_PATH_ATTR)) { if (requestDispatcherPath != null) { return requestDispatcherPath.toString(); } else { return null; } } if (!isSpecial(name)) { return getRequest().getAttribute(name); } else { Object value = null; if (specialAttributes != null) { value = specialAttributes.get(name); } if (value == null && name.startsWith("jakarta.servlet.forward")) { /* * If it's a forward special attribute, and null, delegate to the wrapped request. This will allow access to the forward * special attributes from a request that was first forwarded and then included, or forwarded multiple times in a row. * Notice that forward special attributes are set only on the wrapper that was created for the initial forward (i.e., * the top-most wrapper for a request that was forwarded multiple times in a row, and never included, will not contain * any specialAttributes!). This is different from an include, where the special include attributes are set on every * include wrapper. */ value = getRequest().getAttribute(name); } return value; } }
Override the getAttributeNames() method of the wrapped request.
/** * Override the <code>getAttributeNames()</code> method of the wrapped request. */
@Override public Enumeration<String> getAttributeNames() { return new AttributeNamesEnumerator(); }
Override the removeAttribute() method of the wrapped request.
Params:
  • name – Name of the attribute to remove
/** * Override the <code>removeAttribute()</code> method of the wrapped request. * * @param name Name of the attribute to remove */
@Override public void removeAttribute(String name) { if (isSpecial(name)) { if (specialAttributes != null) { specialAttributes.remove(name); } } else { getRequest().removeAttribute(name); } }
Override the setAttribute() method of the wrapped request.
Params:
  • name – Name of the attribute to set
  • value – Value of the attribute to set
/** * Override the <code>setAttribute()</code> method of the wrapped request. * * @param name Name of the attribute to set * @param value Value of the attribute to set */
@Override public void setAttribute(String name, Object value) { if (name.equals(Globals.DISPATCHER_REQUEST_PATH_ATTR)) { requestDispatcherPath = value; return; } if (isSpecial(name)) { if (specialAttributes != null) { specialAttributes.put(name, value); } } else { getRequest().setAttribute(name, value); } }
Return a RequestDispatcher that wraps the resource at the specified path, which may be interpreted as relative to the current request path.
Params:
  • path – Path of the resource to be wrapped
/** * Return a RequestDispatcher that wraps the resource at the specified path, which may be interpreted as relative to the * current request path. * * @param path Path of the resource to be wrapped */
@Override public RequestDispatcher getRequestDispatcher(String path) { if (context == null) { return null; } // If the path is already context-relative, just pass it through if (path == null) { return null; } else if (path.startsWith("/")) { return context.getRequestDispatcher(path); } // Convert a request-relative path to a context-relative one String servletPath = (String) getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); if (servletPath == null) { servletPath = getServletPath(); } // Add the path info, if there is any String pathInfo = getPathInfo(); String requestPath = null; if (pathInfo == null) { requestPath = servletPath; } else { requestPath = servletPath + pathInfo; } int pos = requestPath.lastIndexOf('/'); String relative = null; if (pos >= 0) { relative = requestPath.substring(0, pos + 1) + path; } else { relative = requestPath + path; } return context.getRequestDispatcher(relative); } @Override public DispatcherType getDispatcherType() { return dispatcherType; } // --------------------------------------------- HttpServletRequest Methods
Override the getContextPath() method of the wrapped request.
/** * Override the <code>getContextPath()</code> method of the wrapped request. */
@Override public String getContextPath() { return this.contextPath; }
Override the getParameter() method of the wrapped request.
Params:
  • name – Name of the requested parameter
/** * Override the <code>getParameter()</code> method of the wrapped request. * * @param name Name of the requested parameter */
@Override public String getParameter(String name) { if (!parsedParams) { parseParameters(); } if (System.getSecurityManager() != null) { return AccessController.doPrivileged(new GetParameterPrivilegedAction(name)); } else { return mergedParameters.getParameter(name); } }
Override the getParameterMap() method of the wrapped request.
/** * Override the <code>getParameterMap()</code> method of the wrapped request. */
@Override public Map<String, String[]> getParameterMap() { if (!parsedParams) { parseParameters(); } if (System.getSecurityManager() != null) { return AccessController.doPrivileged(new GetParameterMapPrivilegedAction()); } else { return getParameterMapInternal(); } }
Override the getParameterNames() method of the wrapped request.
/** * Override the <code>getParameterNames()</code> method of the wrapped request. */
@Override public Enumeration<String> getParameterNames() { if (!parsedParams) { parseParameters(); } if (System.getSecurityManager() != null) { return new Enumerator<>(AccessController.doPrivileged(new GetParameterNamesPrivilegedAction())); } else { return new Enumerator<>(mergedParameters.getParameterNames()); } }
Override the getParameterValues() method of the wrapped request.
Params:
  • name – Name of the requested parameter
/** * Override the <code>getParameterValues()</code> method of the wrapped request. * * @param name Name of the requested parameter */
@Override public String[] getParameterValues(String name) { if (!parsedParams) { parseParameters(); } String[] ret; /* * Clone the returned array only if there is a security manager in place, so that performance won't suffer in the * nonsecure case */ if (System.getSecurityManager() != null) { ret = AccessController.doPrivileged(new GetParameterValuePrivilegedAction(name)); if (ret != null) { ret = ret.clone(); } } else { ret = mergedParameters.getParameterValues(name); } return ret; } private ParameterMap getParameterMapInternal() { if (parameterMap.isLocked()) { return parameterMap; } for (final String name : mergedParameters.getParameterNames()) { final String[] values = mergedParameters.getParameterValues(name); parameterMap.put(name, values); } parameterMap.setLocked(true); return parameterMap; }
Parses the parameters of this request.

If parameters are present in both the query string and the request content, they are merged.
/** * Parses the parameters of this request. * <p/> * If parameters are present in both the query string and the request content, they are merged. */
@SuppressWarnings("unchecked") private void parseParameters() { if (parsedParams) { return; } final String enc = getCharacterEncoding(); Charset charset; if (enc != null) { try { charset = Charsets.lookupCharset(enc); } catch (Exception e) { charset = Constants.DEFAULT_HTTP_CHARSET; } } else { charset = Constants.DEFAULT_HTTP_CHARSET; } mergedParameters.setEncoding(charset); mergedParameters.setQueryStringEncoding(charset); DataChunk queryDC = DataChunk.newInstance(); queryDC.setString(queryParamString); mergedParameters.setQuery(queryDC); mergedParameters.handleQueryParameters(); Map<String, String[]> paramMap = getRequest().getParameterMap(); for (final Map.Entry<String, String[]> entry : paramMap.entrySet()) { mergedParameters.addParameterValues(entry.getKey(), entry.getValue()); } parsedParams = true; }
Override the getPathInfo() method of the wrapped request.
/** * Override the <code>getPathInfo()</code> method of the wrapped request. */
@Override public String getPathInfo() { return this.pathInfo; }
Override the getQueryString() method of the wrapped request.
/** * Override the <code>getQueryString()</code> method of the wrapped request. */
@Override public String getQueryString() { return this.queryString; }
Override the getRequestURI() method of the wrapped request.
/** * Override the <code>getRequestURI()</code> method of the wrapped request. */
@Override public String getRequestURI() { return this.requestURI; }
Override the getRequestURL() method of the wrapped request.
/** * Override the <code>getRequestURL()</code> method of the wrapped request. */
@Override public StringBuffer getRequestURL() { StringBuffer url = new StringBuffer(); String scheme = getScheme(); int port = getServerPort(); if (port < 0) { port = 80; // Work around java.net.URL bug } url.append(scheme); url.append("://"); url.append(getServerName()); if (scheme.equals("http") && port != 80 || scheme.equals("https") && port != 443) { url.append(':'); url.append(port); } url.append(getRequestURI()); return url; }
Override the getServletPath() method of the wrapped request.
/** * Override the <code>getServletPath()</code> method of the wrapped request. */
@Override public String getServletPath() { return this.servletPath; } // -------------------------------------------------------- Package Methods
Perform a shallow copy of the specified Map, and return the result.
Params:
  • orig – Origin Map to be copied
/** * Perform a shallow copy of the specified Map, and return the result. * * @param orig Origin Map to be copied */
void copyMap(Map<String, String[]> orig, Map<String, String[]> dest) { if (orig == null) { return; } // noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (orig) { for (Map.Entry<String, String[]> entry : orig.entrySet()) { dest.put(entry.getKey(), entry.getValue()); } } }
Set the context path for this request.
Params:
  • contextPath – The new context path
/** * Set the context path for this request. * * @param contextPath The new context path */
void setContextPath(String contextPath) { this.contextPath = contextPath; }
Set the path information for this request.
Params:
  • pathInfo – The new path info
/** * Set the path information for this request. * * @param pathInfo The new path info */
void setPathInfo(String pathInfo) { this.pathInfo = pathInfo; }
Set the query string for this request.
Params:
  • queryString – The new query string
/** * Set the query string for this request. * * @param queryString The new query string */
void setQueryString(String queryString) { this.queryString = queryString; }
Set the request that we are wrapping.
Params:
  • request – The new wrapped request
/** * Set the request that we are wrapping. * * @param request The new wrapped request */
void setRequest(HttpServletRequest request) { super.setRequest(request); // Initialize the attributes for this request requestDispatcherPath = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR); // Initialize the path elements for this request contextPath = request.getContextPath(); pathInfo = request.getPathInfo(); queryString = request.getQueryString(); requestURI = request.getRequestURI(); servletPath = request.getServletPath(); }
Set the request URI for this request.
Params:
  • requestURI – The new request URI
/** * Set the request URI for this request. * * @param requestURI The new request URI */
void setRequestURI(String requestURI) { this.requestURI = requestURI; }
Set the servlet path for this request.
Params:
  • servletPath – The new servlet path
/** * Set the servlet path for this request. * * @param servletPath The new servlet path */
void setServletPath(String servletPath) { this.servletPath = servletPath; }
Save query parameters for this request.
Params:
  • queryString – The query string containing parameters for this request
/** * Save query parameters for this request. * * @param queryString The query string containing parameters for this request */
void setQueryParams(String queryString) { this.queryParamString = queryString; } // ------------------------------------------------------ Protected Methods
Is this attribute name one of the special ones that is added only for included servlets?
Params:
  • name – Attribute name to be tested
/** * Is this attribute name one of the special ones that is added only for included servlets? * * @param name Attribute name to be tested */
protected boolean isSpecial(String name) { return specials.contains(name); }
Initializes the special attributes of this request wrapper.
Params:
  • requestUri – The request URI
  • contextPath – The context path
  • servletPath – The servlet path
  • pathInfo – The path info
  • queryString – The query string
/** * Initializes the special attributes of this request wrapper. * * @param requestUri The request URI * @param contextPath The context path * @param servletPath The servlet path * @param pathInfo The path info * @param queryString The query string */
void initSpecialAttributes(String requestUri, String contextPath, String servletPath, String pathInfo, String queryString) { specialAttributes = new HashMap<>(5); switch (dispatcherType) { case INCLUDE: specialAttributes.put(RequestDispatcher.INCLUDE_REQUEST_URI, requestUri); specialAttributes.put(RequestDispatcher.INCLUDE_CONTEXT_PATH, contextPath); specialAttributes.put(RequestDispatcher.INCLUDE_SERVLET_PATH, servletPath); specialAttributes.put(RequestDispatcher.INCLUDE_PATH_INFO, pathInfo); specialAttributes.put(RequestDispatcher.INCLUDE_QUERY_STRING, queryString); break; case FORWARD: case ERROR: specialAttributes.put(RequestDispatcher.FORWARD_REQUEST_URI, requestUri); specialAttributes.put(RequestDispatcher.FORWARD_CONTEXT_PATH, contextPath); specialAttributes.put(RequestDispatcher.FORWARD_SERVLET_PATH, servletPath); specialAttributes.put(RequestDispatcher.FORWARD_PATH_INFO, pathInfo); specialAttributes.put(RequestDispatcher.FORWARD_QUERY_STRING, queryString); break; case ASYNC: specialAttributes.put(AsyncContext.ASYNC_REQUEST_URI, requestUri); specialAttributes.put(AsyncContext.ASYNC_CONTEXT_PATH, contextPath); specialAttributes.put(AsyncContext.ASYNC_SERVLET_PATH, servletPath); specialAttributes.put(AsyncContext.ASYNC_PATH_INFO, pathInfo); specialAttributes.put(AsyncContext.ASYNC_QUERY_STRING, queryString); break; default: // REQUEST break; } }
Merge the two sets of parameter values into a single String array.
Params:
  • values1 – First set of values
  • values2 – Second set of values
/** * Merge the two sets of parameter values into a single String array. * * @param values1 First set of values * @param values2 Second set of values */
protected String[] mergeValues(Object values1, Object values2) { ArrayList<String> results = new ArrayList<>(); if (values1 == null) { ; } else if (values1 instanceof String) { results.add((String) values1); } else if (values1 instanceof String[]) { String values[] = (String[]) values1; Collections.addAll(results, values); } else { results.add(values1.toString()); } if (values2 == null) { ; } else if (values2 instanceof String) { results.add((String) values2); } else if (values2 instanceof String[]) { String values[] = (String[]) values2; Collections.addAll(results, values); } else { results.add(values2.toString()); } String values[] = new String[results.size()]; return results.toArray(values); } public void recycle() { // parameterMap.setLocked( false ); // parameterMap.clear(); } // ----------------------------------- AttributeNamesEnumerator Inner Class
Utility class used to expose the special attributes as being available as request attributes.
/** * Utility class used to expose the special attributes as being available as request attributes. */
private final class AttributeNamesEnumerator implements Enumeration<String> { Enumeration<String> parentEnumeration = null; String next = null; private Iterator<String> specialNames = null; @SuppressWarnings("unchecked") public AttributeNamesEnumerator() { parentEnumeration = getRequest().getAttributeNames(); if (specialAttributes != null) { specialNames = specialAttributes.keySet().iterator(); } } @Override public boolean hasMoreElements() { return specialNames != null && specialNames.hasNext() || next != null || (next = findNext()) != null; } @Override public String nextElement() { if (specialNames != null && specialNames.hasNext()) { return specialNames.next(); } String result = next; if (next != null) { next = findNext(); } else { throw new NoSuchElementException(); } return result; } protected String findNext() { String result = null; while (result == null && parentEnumeration.hasMoreElements()) { String current = parentEnumeration.nextElement(); if (!isSpecial(current) || !dispatcherType.equals(DispatcherType.FORWARD) && current.startsWith("jakarta.servlet.forward") && getAttribute(current) != null) { result = current; } } return result; } } public HttpServletRequestImpl getRequestFacade() { if (getRequest() instanceof HttpServletRequestImpl) { return (HttpServletRequestImpl) getRequest(); } else { return ((DispatchedHttpServletRequest) getRequest()).getRequestFacade(); } } private final class GetParameterPrivilegedAction implements PrivilegedAction<String> { public final String name; public GetParameterPrivilegedAction(String name) { this.name = name; } @Override public String run() { return mergedParameters.getParameter(name); } } private final class GetParameterNamesPrivilegedAction implements PrivilegedAction<Set<String>> { @Override public Set<String> run() { return mergedParameters.getParameterNames(); } } private final class GetParameterValuePrivilegedAction implements PrivilegedAction<String[]> { public final String name; public GetParameterValuePrivilegedAction(String name) { this.name = name; } @Override public String[] run() { return mergedParameters.getParameterValues(name); } } private final class GetParameterMapPrivilegedAction implements PrivilegedAction<ParameterMap> { @Override public ParameterMap run() { return getParameterMapInternal(); } } }