| /* |
| * 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.catalina.core; |
| |
| import java.io.IOException; |
| import java.io.UnsupportedEncodingException; |
| import java.nio.charset.Charset; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| |
| import jakarta.servlet.DispatcherType; |
| import jakarta.servlet.RequestDispatcher; |
| import jakarta.servlet.ServletContext; |
| import jakarta.servlet.http.HttpServletMapping; |
| import jakarta.servlet.http.HttpServletRequest; |
| import jakarta.servlet.http.HttpServletRequestWrapper; |
| import jakarta.servlet.http.HttpSession; |
| |
| import org.apache.catalina.Context; |
| import org.apache.catalina.Globals; |
| import org.apache.catalina.Manager; |
| import org.apache.catalina.Session; |
| import org.apache.catalina.util.ParameterMap; |
| import org.apache.catalina.util.RequestUtil; |
| import org.apache.catalina.util.URLEncoder; |
| import org.apache.tomcat.util.ExceptionUtils; |
| import org.apache.tomcat.util.buf.B2CConverter; |
| import org.apache.tomcat.util.buf.MessageBytes; |
| import org.apache.tomcat.util.http.Parameters; |
| import org.apache.tomcat.util.res.StringManager; |
| |
| /** |
| * 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, or might be based on the 2.3 |
| * <code>jakarta.servlet.http.HttpServletRequestWrapper</code> class) back into an internal |
| * <code>org.apache.catalina.HttpRequest</code>. |
| * <p> |
| * <strong>WARNING</strong>: Due to Java's lack of support for multiple inheritance, all of the logic in |
| * <code>ApplicationRequest</code> is duplicated in <code>ApplicationHttpRequest</code>. Make sure that you keep these |
| * two classes in synchronization when making changes! |
| */ |
| class ApplicationHttpRequest extends HttpServletRequestWrapper { |
| |
| private static final StringManager sm = StringManager.getManager(ApplicationHttpRequest.class); |
| |
| /** |
| * The set of attribute names that are special for request dispatchers. |
| */ |
| protected static final String[] specials = |
| { RequestDispatcher.INCLUDE_REQUEST_URI, RequestDispatcher.INCLUDE_CONTEXT_PATH, |
| RequestDispatcher.INCLUDE_SERVLET_PATH, RequestDispatcher.INCLUDE_PATH_INFO, |
| RequestDispatcher.INCLUDE_QUERY_STRING, RequestDispatcher.INCLUDE_MAPPING, |
| RequestDispatcher.FORWARD_REQUEST_URI, RequestDispatcher.FORWARD_CONTEXT_PATH, |
| RequestDispatcher.FORWARD_SERVLET_PATH, RequestDispatcher.FORWARD_PATH_INFO, |
| RequestDispatcher.FORWARD_QUERY_STRING, RequestDispatcher.FORWARD_MAPPING }; |
| /* |
| * This duplicates specials to some extent but has been added to improve the performance of [get|set|is]Special(). |
| * It may be possible to remove specials but that will require changes to AttributeNamesEnumerator. |
| */ |
| private static final Map<String,Integer> specialsMap = new HashMap<>(); |
| static { |
| for (int i = 0; i < specials.length; i++) { |
| specialsMap.put(specials[i], Integer.valueOf(i)); |
| } |
| } |
| |
| private static final int shortestSpecialNameLength = |
| specialsMap.keySet().stream().mapToInt(String::length).min().getAsInt(); |
| |
| |
| private static final int SPECIALS_FIRST_FORWARD_INDEX = 6; |
| |
| |
| /** |
| * The context for this request. |
| */ |
| protected final Context context; |
| |
| |
| /** |
| * The context path for this request. |
| */ |
| protected String contextPath = null; |
| |
| |
| /** |
| * If this request is cross context, since this changes session access behavior. |
| */ |
| protected final boolean crossContext; |
| |
| |
| /** |
| * The current dispatcher type. |
| */ |
| protected DispatcherType dispatcherType = null; |
| |
| /** |
| * The HTTP method for this request. |
| */ |
| private String method; |
| |
| /** |
| * The request parameters for this request. This is initialized from the wrapped request. |
| */ |
| protected Map<String,String[]> parameters = null; |
| |
| |
| /** |
| * Have the parameters for this request already been parsed? |
| */ |
| private boolean parsedParams = false; |
| |
| |
| /** |
| * The path information for this request. |
| */ |
| protected String pathInfo = null; |
| |
| |
| /** |
| * The query parameters for the current request. |
| */ |
| private String queryParamString = null; |
| |
| |
| /** |
| * The query string for this request. |
| */ |
| protected String queryString = null; |
| |
| |
| /** |
| * The current request dispatcher path. |
| */ |
| protected Object requestDispatcherPath = null; |
| |
| |
| /** |
| * The request URI for this request. |
| */ |
| protected String requestURI = null; |
| |
| |
| /** |
| * The servlet path for this request. |
| */ |
| protected String servletPath = null; |
| |
| |
| /** |
| * The mapping for this request. |
| */ |
| private HttpServletMapping mapping = null; |
| |
| |
| /** |
| * The currently active session for this request. |
| */ |
| protected Session session = null; |
| |
| |
| /** |
| * Special attributes. |
| */ |
| protected final Object[] specialAttributes = new Object[specials.length]; |
| |
| |
| /* |
| * Used to speed up getAttribute(). See that method for details. |
| */ |
| private ApplicationHttpRequest wrappedApplicationHttpRequest; |
| |
| /** |
| * Construct a new wrapped request around the specified servlet request. |
| * |
| * @param request The servlet request being wrapped |
| * @param context The target context for the wrapped request |
| * @param crossContext {@code true} if the wrapped request will be a cross-context request, otherwise {@code false} |
| */ |
| ApplicationHttpRequest(HttpServletRequest request, Context context, boolean crossContext) { |
| super(request); |
| this.context = context; |
| this.crossContext = crossContext; |
| setRequest(request); |
| } |
| |
| |
| // ------------------------------------------------- ServletRequest Methods |
| |
| @Override |
| public ServletContext getServletContext() { |
| if (context == null) { |
| return null; |
| } |
| return context.getServletContext(); |
| } |
| |
| |
| /** |
| * 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_TYPE_ATTR)) { |
| return dispatcherType; |
| } else if (name.equals(Globals.DISPATCHER_REQUEST_PATH_ATTR)) { |
| if (requestDispatcherPath != null) { |
| return requestDispatcherPath.toString(); |
| } else { |
| return null; |
| } |
| } |
| |
| int pos = getSpecial(name); |
| if (pos == -1) { |
| /* |
| * With nested includes there will be nested ApplicationHttpRequests. The calls to getSpecial() are |
| * relatively expensive and it is known at this point that the attribute is not special. Therefore, jump to |
| * the first wrapped request that isn't an instance of ApplicationHttpRequest before calling getAttribute() |
| * to avoid a call to getSpecial() for each nested ApplicationHttpRequest. |
| */ |
| ApplicationHttpRequest request = this; |
| while (request.wrappedApplicationHttpRequest != null) { |
| request = request.wrappedApplicationHttpRequest; |
| } |
| return request.getRequest().getAttribute(name); |
| } else { |
| if ((specialAttributes[pos] == null) && (specialAttributes[SPECIALS_FIRST_FORWARD_INDEX] == null) && |
| (pos >= SPECIALS_FIRST_FORWARD_INDEX)) { |
| // If it's a forward special attribute, and null, it means this |
| // is an include, so we check the wrapped request since |
| // the request could have been forwarded before the include |
| return getRequest().getAttribute(name); |
| } else { |
| return specialAttributes[pos]; |
| } |
| } |
| } |
| |
| |
| /** |
| * Override the <code>getAttributeNames()</code> method of the wrapped request. |
| */ |
| @Override |
| public Enumeration<String> getAttributeNames() { |
| return new AttributeNamesEnumerator(); |
| } |
| |
| |
| /** |
| * 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 (!removeSpecial(name)) { |
| getRequest().removeAttribute(name); |
| } |
| } |
| |
| |
| /** |
| * 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_TYPE_ATTR)) { |
| dispatcherType = (DispatcherType) value; |
| return; |
| } else if (name.equals(Globals.DISPATCHER_REQUEST_PATH_ATTR)) { |
| requestDispatcherPath = value; |
| return; |
| } |
| |
| if (!setSpecial(name, value)) { |
| 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. |
| * |
| * @param path Path of the resource to be wrapped |
| */ |
| @Override |
| public RequestDispatcher getRequestDispatcher(String path) { |
| |
| if (context == null) { |
| return null; |
| } |
| |
| if (path == null) { |
| return null; |
| } |
| |
| int fragmentPos = path.indexOf('#'); |
| if (fragmentPos > -1) { |
| context.getLogger().warn(sm.getString("applicationHttpRequest.fragmentInDispatchPath", path)); |
| path = path.substring(0, fragmentPos); |
| } |
| |
| // If the path is already context-relative, just pass it through |
| if (path.startsWith("/")) { |
| return context.getServletContext().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; |
| |
| if (pathInfo == null) { |
| requestPath = servletPath; |
| } else { |
| requestPath = servletPath + pathInfo; |
| } |
| |
| int pos = requestPath.lastIndexOf('/'); |
| String relative; |
| if (context.getDispatchersUseEncodedPaths()) { |
| if (pos >= 0) { |
| relative = URLEncoder.DEFAULT.encode(requestPath.substring(0, pos + 1), StandardCharsets.UTF_8) + path; |
| } else { |
| relative = URLEncoder.DEFAULT.encode(requestPath, StandardCharsets.UTF_8) + path; |
| } |
| } else { |
| if (pos >= 0) { |
| relative = requestPath.substring(0, pos + 1) + path; |
| } else { |
| relative = requestPath + path; |
| } |
| } |
| |
| return context.getServletContext().getRequestDispatcher(relative); |
| } |
| |
| |
| /** |
| * Override the getDispatcherType() method of the wrapped request. |
| */ |
| @Override |
| public DispatcherType getDispatcherType() { |
| return dispatcherType; |
| } |
| |
| |
| // --------------------------------------------- HttpServletRequest Methods |
| |
| /** |
| * Override the <code>getContextPath()</code> method of the wrapped request. |
| */ |
| @Override |
| public String getContextPath() { |
| return this.contextPath; |
| } |
| |
| |
| @Override |
| public String getMethod() { |
| return method; |
| } |
| |
| |
| /** |
| * Override the <code>getParameter()</code> method of the wrapped request. |
| * |
| * @param name Name of the requested parameter |
| */ |
| @Override |
| public String getParameter(String name) { |
| parseParameters(); |
| |
| String[] value = parameters.get(name); |
| if (value == null) { |
| return null; |
| } |
| return value[0]; |
| } |
| |
| |
| /** |
| * Override the <code>getParameterMap()</code> method of the wrapped request. |
| */ |
| @Override |
| public Map<String,String[]> getParameterMap() { |
| parseParameters(); |
| return parameters; |
| } |
| |
| |
| /** |
| * Override the <code>getParameterNames()</code> method of the wrapped request. |
| */ |
| @Override |
| public Enumeration<String> getParameterNames() { |
| parseParameters(); |
| return Collections.enumeration(parameters.keySet()); |
| } |
| |
| |
| /** |
| * Override the <code>getParameterValues()</code> method of the wrapped request. |
| * |
| * @param name Name of the requested parameter |
| */ |
| @Override |
| public String[] getParameterValues(String name) { |
| parseParameters(); |
| return parameters.get(name); |
| } |
| |
| |
| /** |
| * Override the <code>getPathInfo()</code> method of the wrapped request. |
| */ |
| @Override |
| public String getPathInfo() { |
| return this.pathInfo; |
| } |
| |
| |
| /** |
| * Override the <code>getPathTranslated()</code> method of the wrapped request. |
| */ |
| @Override |
| public String getPathTranslated() { |
| if (getPathInfo() == null || getServletContext() == null) { |
| return null; |
| } |
| |
| return getServletContext().getRealPath(getPathInfo()); |
| } |
| |
| |
| /** |
| * Override the <code>getQueryString()</code> method of the wrapped request. |
| */ |
| @Override |
| public String getQueryString() { |
| return this.queryString; |
| } |
| |
| |
| /** |
| * Override the <code>getRequestURI()</code> method of the wrapped request. |
| */ |
| @Override |
| public String getRequestURI() { |
| return this.requestURI; |
| } |
| |
| |
| /** |
| * Override the <code>getRequestURL()</code> method of the wrapped request. |
| */ |
| @Override |
| public StringBuffer getRequestURL() { |
| return RequestUtil.getRequestURL(this); |
| } |
| |
| |
| /** |
| * Override the <code>getServletPath()</code> method of the wrapped request. |
| */ |
| @Override |
| public String getServletPath() { |
| return this.servletPath; |
| } |
| |
| |
| @Override |
| public HttpServletMapping getHttpServletMapping() { |
| return mapping; |
| } |
| |
| |
| /** |
| * Return the session associated with this Request, creating one if necessary. |
| */ |
| @Override |
| public HttpSession getSession() { |
| return getSession(true); |
| } |
| |
| |
| /** |
| * Return the session associated with this Request, creating one if necessary and requested. |
| * |
| * @param create Create a new session if one does not exist |
| */ |
| @Override |
| public HttpSession getSession(boolean create) { |
| |
| if (crossContext) { |
| |
| // There cannot be a session if no context has been assigned yet |
| if (context == null) { |
| return null; |
| } |
| |
| // Return the current session if it exists and is valid |
| if (session != null && session.isValid()) { |
| return session.getSession(); |
| } |
| |
| HttpSession other = super.getSession(false); |
| if (create && (other == null)) { |
| // First create a session in the first context: the problem is |
| // that the top level request is the only one which can |
| // create the cookie safely |
| other = super.getSession(true); |
| } |
| if (other != null) { |
| Session localSession = null; |
| try { |
| localSession = context.getManager().findSession(other.getId()); |
| if (localSession != null && !localSession.isValid()) { |
| localSession = null; |
| } |
| } catch (IOException ignore) { |
| // Ignore |
| } |
| if (localSession == null && create) { |
| localSession = context.getManager().createSession(other.getId()); |
| } |
| if (localSession != null) { |
| localSession.access(); |
| session = localSession; |
| return session.getSession(); |
| } |
| } |
| return null; |
| |
| } else { |
| return super.getSession(create); |
| } |
| } |
| |
| |
| /** |
| * Returns true if the request specifies a JSESSIONID that is valid within the context of this |
| * ApplicationHttpRequest, false otherwise. |
| * |
| * @return true if the request specifies a JSESSIONID that is valid within the context of this |
| * ApplicationHttpRequest, false otherwise. |
| */ |
| @Override |
| public boolean isRequestedSessionIdValid() { |
| |
| if (crossContext) { |
| |
| String requestedSessionId = getRequestedSessionId(); |
| if (requestedSessionId == null) { |
| return false; |
| } |
| if (context == null) { |
| return false; |
| } |
| Manager manager = context.getManager(); |
| if (manager == null) { |
| return false; |
| } |
| Session session = null; |
| try { |
| session = manager.findSession(requestedSessionId); |
| } catch (IOException ignore) { |
| // Ignore |
| } |
| return (session != null) && session.isValid(); |
| |
| } else { |
| return super.isRequestedSessionIdValid(); |
| } |
| } |
| |
| |
| // -------------------------------------------------------- Package Methods |
| |
| /** |
| * Recycle this request |
| */ |
| public void recycle() { |
| if (session != null) { |
| try { |
| session.endAccess(); |
| } catch (Throwable t) { |
| ExceptionUtils.handleThrowable(t); |
| context.getLogger().warn(sm.getString("applicationHttpRequest.sessionEndAccessFail"), t); |
| } |
| } |
| } |
| |
| |
| /** |
| * 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. |
| * |
| * @param pathInfo The new path info |
| */ |
| void setPathInfo(String pathInfo) { |
| this.pathInfo = pathInfo; |
| } |
| |
| |
| /** |
| * 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. |
| * |
| * @param request The new wrapped request |
| */ |
| void setRequest(HttpServletRequest request) { |
| |
| super.setRequest(request); |
| |
| // Type specific version of the wrapped request to speed up getAttribute() |
| if (request instanceof ApplicationHttpRequest) { |
| wrappedApplicationHttpRequest = (ApplicationHttpRequest) request; |
| } else { |
| wrappedApplicationHttpRequest = null; |
| } |
| |
| // Initialize the attributes for this request |
| dispatcherType = (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR); |
| requestDispatcherPath = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR); |
| |
| // Initialize the path elements for this request |
| contextPath = request.getContextPath(); |
| method = request.getMethod(); |
| pathInfo = request.getPathInfo(); |
| queryString = request.getQueryString(); |
| requestURI = request.getRequestURI(); |
| servletPath = request.getServletPath(); |
| mapping = request.getHttpServletMapping(); |
| } |
| |
| |
| /** |
| * Set the request method for this request. |
| * |
| * @param method The new request method |
| */ |
| void setMethod(String method) { |
| this.method = method; |
| } |
| |
| |
| /** |
| * 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. |
| * |
| * @param servletPath The new servlet path |
| */ |
| void setServletPath(String servletPath) { |
| this.servletPath = servletPath; |
| } |
| |
| |
| /** |
| * Parses the parameters of this request. If parameters are present in both the query string and the request |
| * content, they are merged. |
| */ |
| void parseParameters() { |
| |
| if (parsedParams) { |
| return; |
| } |
| |
| Map<String,String[]> requestParameters = getRequest().getParameterMap(); |
| if (requestParameters instanceof ParameterMap<String,String[]>) { |
| parameters = new ParameterMap<>((ParameterMap<String,String[]>) requestParameters); |
| } else { |
| parameters = new ParameterMap<>(requestParameters); |
| } |
| mergeParameters(); |
| ((ParameterMap<String,String[]>) parameters).setLocked(true); |
| parsedParams = true; |
| } |
| |
| |
| /** |
| * Save query parameters for this request. |
| * |
| * @param queryString The query string containing parameters for this request |
| */ |
| void setQueryParams(String queryString) { |
| this.queryParamString = queryString; |
| } |
| |
| |
| void setMapping(HttpServletMapping mapping) { |
| this.mapping = mapping; |
| } |
| |
| |
| // ------------------------------------------------------ Protected Methods |
| |
| /** |
| * 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) { |
| // Performance - see BZ 68089 |
| if (name.length() < shortestSpecialNameLength) { |
| return false; |
| } |
| return specialsMap.containsKey(name); |
| } |
| |
| |
| /** |
| * Get a special attribute. |
| * |
| * @return the special attribute pos, or -1 if it is not a special attribute |
| */ |
| protected int getSpecial(String name) { |
| // Performance - see BZ 68089 |
| if (name.length() < shortestSpecialNameLength) { |
| return -1; |
| } |
| Integer index = specialsMap.get(name); |
| if (index == null) { |
| return -1; |
| } |
| return index.intValue(); |
| } |
| |
| |
| /** |
| * Set a special attribute. |
| * |
| * @return true if the attribute was a special attribute, false otherwise |
| */ |
| protected boolean setSpecial(String name, Object value) { |
| // Performance - see BZ 68089 |
| if (name.length() < shortestSpecialNameLength) { |
| return false; |
| } |
| Integer index = specialsMap.get(name); |
| if (index == null) { |
| return false; |
| } |
| specialAttributes[index.intValue()] = value; |
| return true; |
| } |
| |
| |
| /** |
| * Remove a special attribute. |
| * |
| * @return true if the attribute was a special attribute, false otherwise |
| */ |
| protected boolean removeSpecial(String name) { |
| return setSpecial(name, null); |
| } |
| |
| |
| /** |
| * Merge the two sets of parameter values into a single String array. |
| * |
| * @param values1 First set of values |
| * @param values2 Second set of values |
| */ |
| private String[] mergeValues(String[] values1, String[] values2) { |
| |
| List<String> results = new ArrayList<>(); |
| |
| if (values1 != null) { |
| results.addAll(Arrays.asList(values1)); |
| } |
| |
| if (values2 != null) { |
| results.addAll(Arrays.asList(values2)); |
| } |
| |
| return results.toArray(new String[0]); |
| } |
| |
| |
| // ------------------------------------------------------ Private Methods |
| |
| /** |
| * Merge the parameters from the saved query parameter string (if any), and the parameters already present on this |
| * request (if any), such that the parameter values from the query string show up first if there are duplicate |
| * parameter names. |
| */ |
| private void mergeParameters() { |
| |
| if ((queryParamString == null) || (queryParamString.isEmpty())) { |
| return; |
| } |
| |
| // Parse the query string from the dispatch target |
| Parameters paramParser = new Parameters(); |
| MessageBytes queryMB = MessageBytes.newInstance(); |
| queryMB.setString(queryParamString); |
| |
| // TODO |
| // - Should only use body encoding if useBodyEncodingForURI is true |
| // - Otherwise, should use URIEncoding |
| // - The problem is that the connector is not available... |
| // - To add to the fun, the URI default changed in Servlet 4.0 to UTF-8 |
| |
| String encoding = getCharacterEncoding(); |
| Charset charset = null; |
| if (encoding != null) { |
| try { |
| charset = B2CConverter.getCharset(encoding); |
| queryMB.setCharset(charset); |
| } catch (UnsupportedEncodingException e) { |
| // Fall-back to default (ISO-8859-1) |
| charset = StandardCharsets.ISO_8859_1; |
| } |
| } |
| |
| paramParser.setQuery(queryMB); |
| paramParser.setQueryStringCharset(charset); |
| paramParser.handleQueryParameters(); |
| |
| // Insert the additional parameters from the dispatch target |
| Enumeration<String> dispParamNames = paramParser.getParameterNames(); |
| while (dispParamNames.hasMoreElements()) { |
| String dispParamName = dispParamNames.nextElement(); |
| String[] dispParamValues = paramParser.getParameterValues(dispParamName); |
| String[] originalValues = parameters.get(dispParamName); |
| if (originalValues == null) { |
| parameters.put(dispParamName, dispParamValues); |
| continue; |
| } |
| parameters.put(dispParamName, mergeValues(dispParamValues, originalValues)); |
| } |
| } |
| |
| |
| // ----------------------------------- AttributeNamesEnumerator Inner Class |
| |
| /** |
| * Utility class used to expose the special attributes as being available as request attributes. |
| */ |
| protected class AttributeNamesEnumerator implements Enumeration<String> { |
| |
| protected int pos = -1; |
| protected final int last; |
| protected final Enumeration<String> parentEnumeration; |
| protected String next = null; |
| |
| public AttributeNamesEnumerator() { |
| int last = -1; |
| parentEnumeration = getRequest().getAttributeNames(); |
| for (int i = specialAttributes.length - 1; i >= 0; i--) { |
| if (getAttribute(specials[i]) != null) { |
| last = i; |
| break; |
| } |
| } |
| this.last = last; |
| } |
| |
| @Override |
| public boolean hasMoreElements() { |
| return ((pos != last) || (next != null) || ((next = findNext()) != null)); |
| } |
| |
| @Override |
| public String nextElement() { |
| if (pos != last) { |
| for (int i = pos + 1; i <= last; i++) { |
| if (getAttribute(specials[i]) != null) { |
| pos = i; |
| return specials[i]; |
| } |
| } |
| } |
| 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)) { |
| result = current; |
| } |
| } |
| return result; |
| } |
| } |
| } |