| /* |
| * 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.PrintWriter; |
| |
| import jakarta.servlet.AsyncContext; |
| import jakarta.servlet.DispatcherType; |
| import jakarta.servlet.RequestDispatcher; |
| import jakarta.servlet.Servlet; |
| import jakarta.servlet.ServletException; |
| import jakarta.servlet.ServletOutputStream; |
| import jakarta.servlet.ServletRequest; |
| import jakarta.servlet.ServletRequestWrapper; |
| import jakarta.servlet.ServletResponse; |
| import jakarta.servlet.ServletResponseWrapper; |
| import jakarta.servlet.UnavailableException; |
| import jakarta.servlet.http.HttpServletMapping; |
| import jakarta.servlet.http.HttpServletRequest; |
| import jakarta.servlet.http.HttpServletResponse; |
| |
| import org.apache.catalina.AsyncDispatcher; |
| import org.apache.catalina.Context; |
| import org.apache.catalina.Globals; |
| import org.apache.catalina.Wrapper; |
| import org.apache.catalina.connector.Request; |
| import org.apache.catalina.connector.RequestFacade; |
| import org.apache.catalina.connector.Response; |
| import org.apache.catalina.connector.ResponseFacade; |
| import org.apache.coyote.BadRequestException; |
| import org.apache.coyote.CloseNowException; |
| import org.apache.tomcat.util.ExceptionUtils; |
| import org.apache.tomcat.util.http.Method; |
| import org.apache.tomcat.util.res.StringManager; |
| |
| /** |
| * Standard implementation of <code>RequestDispatcher</code> that allows a request to be forwarded to a different |
| * resource to create the ultimate response, or to include the output of another resource in the response from this |
| * resource. This implementation allows application level servlets to wrap the request and/or response objects that are |
| * passed on to the called resource, as long as the wrapping classes extend |
| * <code>jakarta.servlet.ServletRequestWrapper</code> and <code>jakarta.servlet.ServletResponseWrapper</code>. |
| */ |
| final class ApplicationDispatcher implements AsyncDispatcher, RequestDispatcher { |
| |
| /** |
| * Used to pass state when the request dispatcher is used. Using instance variables causes threading issues and |
| * state is too complex to pass and return single ServletRequest or ServletResponse objects. |
| */ |
| private static class State { |
| State(ServletRequest request, ServletResponse response, boolean including) { |
| this.outerRequest = request; |
| this.outerResponse = response; |
| this.including = including; |
| } |
| |
| /** |
| * The outermost request that will be passed on to the invoked servlet. |
| */ |
| ServletRequest outerRequest; |
| |
| |
| /** |
| * The outermost response that will be passed on to the invoked servlet. |
| */ |
| ServletResponse outerResponse; |
| |
| /** |
| * The request wrapper we have created and installed (if any). |
| */ |
| ServletRequest wrapRequest = null; |
| |
| |
| /** |
| * The response wrapper we have created and installed (if any). |
| */ |
| ServletResponse wrapResponse = null; |
| |
| /** |
| * Are we performing an include() instead of a forward()? |
| */ |
| boolean including; |
| |
| /** |
| * Outermost HttpServletRequest in the chain |
| */ |
| HttpServletRequest hrequest = null; |
| |
| /** |
| * Outermost HttpServletResponse in the chain |
| */ |
| HttpServletResponse hresponse = null; |
| } |
| |
| // ----------------------------------------------------------- Constructors |
| |
| |
| /** |
| * Construct a new instance of this class, configured according to the specified parameters. If both servletPath and |
| * pathInfo are <code>null</code>, it will be assumed that this RequestDispatcher was acquired by name, rather than |
| * by path. |
| * |
| * @param wrapper The Wrapper associated with the resource that will be forwarded to or included (required) |
| * @param requestURI The request URI to this resource (if any) |
| * @param servletPath The revised servlet path to this resource (if any) |
| * @param pathInfo The revised extra path information to this resource (if any) |
| * @param queryString Query string parameters included with this request (if any) |
| * @param mapping The mapping for this resource (if any) |
| * @param name Servlet name (if a named dispatcher was created) else <code>null</code> |
| */ |
| ApplicationDispatcher(Wrapper wrapper, String requestURI, String servletPath, String pathInfo, String queryString, |
| HttpServletMapping mapping, String name) { |
| |
| super(); |
| |
| // Save all of our configuration parameters |
| this.wrapper = wrapper; |
| this.context = (Context) wrapper.getParent(); |
| this.requestURI = requestURI; |
| this.servletPath = servletPath; |
| this.pathInfo = pathInfo; |
| this.queryString = queryString; |
| this.mapping = mapping; |
| this.name = name; |
| } |
| |
| |
| // ----------------------------------------------------- Instance Variables |
| |
| /** |
| * The Context this RequestDispatcher is associated with. |
| */ |
| private final Context context; |
| |
| |
| /** |
| * The servlet name for a named dispatcher. |
| */ |
| private final String name; |
| |
| |
| /** |
| * The extra path information for this RequestDispatcher. |
| */ |
| private final String pathInfo; |
| |
| |
| /** |
| * The query string parameters for this RequestDispatcher. |
| */ |
| private final String queryString; |
| |
| |
| /** |
| * The request URI for this RequestDispatcher. |
| */ |
| private final String requestURI; |
| |
| |
| /** |
| * The servlet path for this RequestDispatcher. |
| */ |
| private final String servletPath; |
| |
| |
| /** |
| * The mapping for this RequestDispatcher. |
| */ |
| private final HttpServletMapping mapping; |
| |
| |
| /** |
| * The StringManager for this package. |
| */ |
| private static final StringManager sm = StringManager.getManager(ApplicationDispatcher.class); |
| |
| |
| /** |
| * The Wrapper associated with the resource that will be forwarded to or included. |
| */ |
| private final Wrapper wrapper; |
| |
| |
| // --------------------------------------------------------- Public Methods |
| |
| |
| @Override |
| public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException { |
| // Reset any output that has been buffered, but keep headers/cookies |
| if (response.isCommitted()) { |
| throw new IllegalStateException(sm.getString("applicationDispatcher.forward.ise")); |
| } |
| response.resetBuffer(); |
| |
| // Set up to handle the specified request and response |
| State state = new State(request, response, false); |
| |
| if (context.getDispatcherWrapsSameObject()) { |
| // Check SRV.9.2 / RequestDispatcher Javadoc |
| checkSameObjects(request, response); |
| } |
| |
| wrapResponse(state); |
| ApplicationHttpRequest wrequest = (ApplicationHttpRequest) wrapRequest(state); |
| HttpServletRequest hrequest = state.hrequest; |
| if (servletPath == null && pathInfo == null) { |
| // Handle an HTTP named dispatcher forward |
| |
| // All ERROR dispatches must be GET requests. Use the presence of ERROR_METHOD to determine if this is an |
| // error dispatch as not all components (JSP) set the dispatcher type. |
| if (request.getAttribute(ERROR_METHOD) != null) { |
| wrequest.setMethod(Method.GET); |
| } |
| wrequest.setRequestURI(hrequest.getRequestURI()); |
| wrequest.setContextPath(hrequest.getContextPath()); |
| wrequest.setServletPath(hrequest.getServletPath()); |
| wrequest.setPathInfo(hrequest.getPathInfo()); |
| wrequest.setQueryString(hrequest.getQueryString()); |
| |
| } else { |
| // Handle an HTTP path-based forward |
| |
| if (hrequest.getAttribute(FORWARD_REQUEST_URI) == null) { |
| wrequest.setAttribute(FORWARD_REQUEST_URI, hrequest.getRequestURI()); |
| wrequest.setAttribute(FORWARD_CONTEXT_PATH, hrequest.getContextPath()); |
| wrequest.setAttribute(FORWARD_SERVLET_PATH, hrequest.getServletPath()); |
| wrequest.setAttribute(FORWARD_PATH_INFO, hrequest.getPathInfo()); |
| wrequest.setAttribute(FORWARD_QUERY_STRING, hrequest.getQueryString()); |
| wrequest.setAttribute(FORWARD_MAPPING, hrequest.getHttpServletMapping()); |
| } |
| |
| // All ERROR dispatches must be GET requests. Use the presence of ERROR_METHOD to determine if this is an |
| // error dispatch as not all components (JSP) set the dispatcher type. |
| if (request.getAttribute(ERROR_METHOD) != null) { |
| wrequest.setMethod(Method.GET); |
| } |
| wrequest.setContextPath(context.getEncodedPath()); |
| wrequest.setRequestURI(requestURI); |
| wrequest.setServletPath(servletPath); |
| wrequest.setPathInfo(pathInfo); |
| if (queryString != null) { |
| wrequest.setQueryString(queryString); |
| wrequest.setQueryParams(queryString); |
| } |
| wrequest.setMapping(mapping); |
| |
| } |
| processRequest(request, response, state); |
| |
| if (request.isAsyncStarted()) { |
| // An async request was started during the forward, don't close the |
| // response as it may be written to during the async handling |
| return; |
| } |
| |
| // This is not a real close in order to support error processing |
| if (wrapper.getLogger().isTraceEnabled()) { |
| wrapper.getLogger().trace(" Disabling the response for further output"); |
| } |
| |
| boolean finished = false; |
| if (response instanceof ResponseFacade) { |
| finished = true; |
| ((ResponseFacade) response).finish(); |
| } else if (context.getSuspendWrappedResponseAfterForward() && response instanceof ServletResponseWrapper) { |
| ServletResponse baseResponse = response; |
| do { |
| baseResponse = ((ServletResponseWrapper) baseResponse).getResponse(); |
| } while (baseResponse instanceof ServletResponseWrapper); |
| if (baseResponse instanceof ResponseFacade) { |
| finished = true; |
| ((ResponseFacade) baseResponse).finish(); |
| } |
| } |
| if (!finished) { |
| // Servlet SRV.6.2.2. The Request/Response may have been wrapped |
| // and may no longer be an instance of RequestFacade |
| if (wrapper.getLogger().isDebugEnabled()) { |
| wrapper.getLogger().debug(sm.getString("applicationDispatcher.customResponse", response.getClass())); |
| } |
| // Close anyway |
| try { |
| PrintWriter writer = response.getWriter(); |
| writer.close(); |
| } catch (IllegalStateException e) { |
| try { |
| ServletOutputStream stream = response.getOutputStream(); |
| stream.close(); |
| } catch (IllegalStateException | IOException f) { |
| // Ignore |
| } |
| } catch (IOException ignore) { |
| // Ignore |
| } |
| } |
| |
| } |
| |
| |
| /** |
| * Prepare the request based on the filter configuration. |
| * |
| * @param request The servlet request we are processing |
| * @param response The servlet response we are creating |
| * @param state The RD state |
| * |
| * @exception IOException if an input/output error occurs |
| * @exception ServletException if a servlet error occurs |
| */ |
| private void processRequest(ServletRequest request, ServletResponse response, State state) |
| throws IOException, ServletException { |
| |
| DispatcherType disInt = (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR); |
| if (disInt != null) { |
| if (!context.getFireRequestListenersOnForwards() || context.fireRequestInitEvent(request)) { |
| if (disInt != DispatcherType.ERROR) { |
| state.outerRequest.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, getCombinedPath()); |
| state.outerRequest.setAttribute(Globals.DISPATCHER_TYPE_ATTR, DispatcherType.FORWARD); |
| invoke(state.outerRequest, response, state); |
| } else { |
| invoke(state.outerRequest, response, state); |
| } |
| |
| if (context.getFireRequestListenersOnForwards()) { |
| context.fireRequestDestroyEvent(request); |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Combine the servletPath and the pathInfo. If pathInfo is <code>null</code> it is ignored. If servletPath is |
| * <code>null</code> then <code>null</code> is returned. |
| * |
| * @return The combined path with pathInfo appended to servletInfo |
| */ |
| private String getCombinedPath() { |
| if (servletPath == null) { |
| return null; |
| } |
| if (pathInfo == null) { |
| return servletPath; |
| } |
| return servletPath + pathInfo; |
| } |
| |
| |
| @Override |
| public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException { |
| // Set up to handle the specified request and response |
| State state = new State(request, response, true); |
| |
| if (context.getDispatcherWrapsSameObject()) { |
| // Check SRV.8.2 / SRV.14.2.5.1 compliance |
| checkSameObjects(request, response); |
| } |
| |
| // Create a wrapped response to use for this request |
| wrapResponse(state); |
| |
| ApplicationHttpRequest wrequest = (ApplicationHttpRequest) wrapRequest(state); |
| if (name != null) { |
| // Handle an HTTP named dispatcher include |
| |
| wrequest.setAttribute(Globals.NAMED_DISPATCHER_ATTR, name); |
| if (servletPath != null) { |
| wrequest.setServletPath(servletPath); |
| } |
| } else { |
| // Handle an HTTP path based include |
| |
| String contextPath = context.getPath(); |
| if (requestURI != null) { |
| wrequest.setAttribute(INCLUDE_REQUEST_URI, requestURI); |
| } |
| if (contextPath != null) { |
| wrequest.setAttribute(INCLUDE_CONTEXT_PATH, contextPath); |
| } |
| if (servletPath != null) { |
| wrequest.setAttribute(INCLUDE_SERVLET_PATH, servletPath); |
| } |
| if (pathInfo != null) { |
| wrequest.setAttribute(INCLUDE_PATH_INFO, pathInfo); |
| } |
| if (queryString != null) { |
| wrequest.setAttribute(INCLUDE_QUERY_STRING, queryString); |
| wrequest.setQueryParams(queryString); |
| } |
| if (mapping != null) { |
| wrequest.setAttribute(INCLUDE_MAPPING, mapping); |
| } |
| |
| } |
| wrequest.setAttribute(Globals.DISPATCHER_TYPE_ATTR, DispatcherType.INCLUDE); |
| wrequest.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, getCombinedPath()); |
| invoke(state.outerRequest, state.outerResponse, state); |
| |
| } |
| |
| |
| @Override |
| public void dispatch(ServletRequest request, ServletResponse response) throws ServletException, IOException { |
| // Set up to handle the specified request and response |
| State state = new State(request, response, false); |
| |
| // Create a wrapped response to use for this request |
| wrapResponse(state); |
| |
| ApplicationHttpRequest wrequest = (ApplicationHttpRequest) wrapRequest(state); |
| HttpServletRequest hrequest = state.hrequest; |
| |
| wrequest.setAttribute(Globals.DISPATCHER_TYPE_ATTR, DispatcherType.ASYNC); |
| wrequest.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, getCombinedPath()); |
| wrequest.setAttribute(AsyncContext.ASYNC_MAPPING, hrequest.getHttpServletMapping()); |
| |
| wrequest.setContextPath(context.getEncodedPath()); |
| wrequest.setRequestURI(requestURI); |
| wrequest.setServletPath(servletPath); |
| wrequest.setPathInfo(pathInfo); |
| if (queryString != null) { |
| wrequest.setQueryString(queryString); |
| wrequest.setQueryParams(queryString); |
| } |
| wrequest.setMapping(mapping); |
| |
| invoke(state.outerRequest, state.outerResponse, state); |
| } |
| |
| |
| // -------------------------------------------------------- Private Methods |
| |
| |
| /** |
| * Ask the resource represented by this RequestDispatcher to process the associated request, and create (or append |
| * to) the associated response. |
| * <p> |
| * <strong>IMPLEMENTATION NOTE</strong>: This implementation assumes that no filters are applied to a forwarded or |
| * included resource, because they were already done for the original request. |
| * |
| * @param request The servlet request we are processing |
| * @param response The servlet response we are creating |
| * |
| * @exception IOException if an input/output error occurs |
| * @exception ServletException if a servlet error occurs |
| */ |
| private void invoke(ServletRequest request, ServletResponse response, State state) |
| throws IOException, ServletException { |
| |
| // Checking to see if the context classloader is the current context |
| // classloader. If it's not, we're saving it, and setting the context |
| // classloader to the Context classloader |
| ClassLoader oldCCL = context.bind(null); |
| |
| // Initialize local variables we may need |
| HttpServletResponse hresponse = state.hresponse; |
| Servlet servlet = null; |
| IOException ioException = null; |
| ServletException servletException = null; |
| RuntimeException runtimeException = null; |
| boolean unavailable = false; |
| |
| // Check for the servlet being marked unavailable |
| if (wrapper.isUnavailable()) { |
| wrapper.getLogger().warn(sm.getString("applicationDispatcher.isUnavailable", wrapper.getName())); |
| long available = wrapper.getAvailable(); |
| if (available > 0L && available < Long.MAX_VALUE) { |
| hresponse.setDateHeader("Retry-After", available); |
| } |
| hresponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, |
| sm.getString("applicationDispatcher.isUnavailable", wrapper.getName())); |
| unavailable = true; |
| } |
| |
| // Allocate a servlet instance to process this request |
| try { |
| if (!unavailable) { |
| servlet = wrapper.allocate(); |
| } |
| } catch (ServletException e) { |
| wrapper.getLogger().error(sm.getString("applicationDispatcher.allocateException", wrapper.getName()), |
| StandardWrapper.getRootCause(e)); |
| servletException = e; |
| } catch (Throwable t) { |
| ExceptionUtils.handleThrowable(t); |
| wrapper.getLogger().error(sm.getString("applicationDispatcher.allocateException", wrapper.getName()), t); |
| servletException = |
| new ServletException(sm.getString("applicationDispatcher.allocateException", wrapper.getName()), t); |
| // servlet = null; is already done so no need to do it explicitly |
| } |
| |
| // Get the FilterChain Here |
| ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); |
| |
| // Call the service() method for the allocated servlet instance |
| try { |
| // for includes/forwards |
| if (servlet != null && filterChain != null) { |
| filterChain.doFilter(request, response); |
| } |
| // Servlet Service Method is called by the FilterChain |
| } catch (BadRequestException | CloseNowException e) { |
| ioException = e; |
| } catch (IOException ioe) { |
| wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException", wrapper.getName()), ioe); |
| ioException = ioe; |
| } catch (UnavailableException e) { |
| wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException", wrapper.getName()), e); |
| servletException = e; |
| wrapper.unavailable(e); |
| } catch (ServletException e) { |
| Throwable rootCause = StandardWrapper.getRootCause(e); |
| if (!(rootCause instanceof BadRequestException)) { |
| wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException", wrapper.getName()), |
| rootCause); |
| } |
| servletException = e; |
| } catch (RuntimeException e) { |
| wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException", wrapper.getName()), e); |
| runtimeException = e; |
| } |
| |
| // Release the filter chain (if any) for this request |
| if (filterChain != null) { |
| filterChain.release(); |
| } |
| |
| // Deallocate the allocated servlet instance |
| try { |
| if (servlet != null) { |
| wrapper.deallocate(servlet); |
| } |
| } catch (ServletException e) { |
| wrapper.getLogger().error(sm.getString("applicationDispatcher.deallocateException", wrapper.getName()), e); |
| servletException = e; |
| } catch (Throwable t) { |
| ExceptionUtils.handleThrowable(t); |
| wrapper.getLogger().error(sm.getString("applicationDispatcher.deallocateException", wrapper.getName()), t); |
| servletException = new ServletException( |
| sm.getString("applicationDispatcher.deallocateException", wrapper.getName()), t); |
| } |
| |
| // Reset the old context class loader |
| context.unbind(oldCCL); |
| |
| // Unwrap request/response if needed |
| // See Bugzilla 30949 |
| unwrapRequest(state); |
| unwrapResponse(state); |
| // Recycle request if necessary (also BZ 30949) |
| recycleRequestWrapper(state); |
| |
| // Rethrow an exception if one was thrown by the invoked servlet |
| if (ioException != null) { |
| throw ioException; |
| } |
| if (servletException != null) { |
| throw servletException; |
| } |
| if (runtimeException != null) { |
| throw runtimeException; |
| } |
| } |
| |
| |
| /** |
| * Unwrap the request if we have wrapped it. |
| */ |
| private void unwrapRequest(State state) { |
| |
| if (state.wrapRequest == null) { |
| return; |
| } |
| |
| if (state.outerRequest.isAsyncStarted()) { |
| if (!state.outerRequest.getAsyncContext().hasOriginalRequestAndResponse()) { |
| return; |
| } |
| } |
| |
| ServletRequest previous = null; |
| ServletRequest current = state.outerRequest; |
| while (current != null) { |
| |
| // If we run into the container request we are done |
| if (current instanceof Request || current instanceof RequestFacade) { |
| break; |
| } |
| |
| // Remove the current request if it is our wrapper |
| if (current == state.wrapRequest) { |
| ServletRequest next = ((ServletRequestWrapper) current).getRequest(); |
| if (previous == null) { |
| state.outerRequest = next; |
| } else { |
| ((ServletRequestWrapper) previous).setRequest(next); |
| } |
| break; |
| } |
| |
| // Advance to the next request in the chain |
| previous = current; |
| current = ((ServletRequestWrapper) current).getRequest(); |
| |
| } |
| |
| } |
| |
| /** |
| * Unwrap the response if we have wrapped it. |
| */ |
| private void unwrapResponse(State state) { |
| |
| if (state.wrapResponse == null) { |
| return; |
| } |
| |
| if (state.outerRequest.isAsyncStarted()) { |
| if (!state.outerRequest.getAsyncContext().hasOriginalRequestAndResponse()) { |
| return; |
| } |
| } |
| |
| ServletResponse previous = null; |
| ServletResponse current = state.outerResponse; |
| while (current != null) { |
| |
| // If we run into the container response we are done |
| if (current instanceof Response || current instanceof ResponseFacade) { |
| break; |
| } |
| |
| // Remove the current response if it is our wrapper |
| if (current == state.wrapResponse) { |
| ServletResponse next = ((ServletResponseWrapper) current).getResponse(); |
| if (previous == null) { |
| state.outerResponse = next; |
| } else { |
| ((ServletResponseWrapper) previous).setResponse(next); |
| } |
| break; |
| } |
| |
| // Advance to the next response in the chain |
| previous = current; |
| current = ((ServletResponseWrapper) current).getResponse(); |
| |
| } |
| |
| } |
| |
| |
| /** |
| * Create and return a request wrapper that has been inserted in the appropriate spot in the request chain. |
| */ |
| private ServletRequest wrapRequest(State state) { |
| |
| // Locate the request we should insert in front of |
| ServletRequest previous = null; |
| ServletRequest current = state.outerRequest; |
| while (current != null) { |
| if (state.hrequest == null && current instanceof HttpServletRequest) { |
| state.hrequest = (HttpServletRequest) current; |
| } |
| if (!(current instanceof ServletRequestWrapper)) { |
| break; |
| } |
| if (current instanceof ApplicationHttpRequest) { |
| break; |
| } |
| if (current instanceof ApplicationRequest) { |
| break; |
| } |
| previous = current; |
| current = ((ServletRequestWrapper) current).getRequest(); |
| } |
| |
| // Instantiate a new wrapper at this point and insert it in the chain |
| ServletRequest wrapper; |
| if (current instanceof HttpServletRequest hcurrent) { |
| // Compute a crossContext flag |
| boolean crossContext = false; |
| if (state.outerRequest instanceof HttpServletRequest houterRequest) { |
| Object contextPath = houterRequest.getAttribute(INCLUDE_CONTEXT_PATH); |
| if (contextPath == null) { |
| // Forward |
| contextPath = houterRequest.getContextPath(); |
| } |
| crossContext = !context.getPath().equals(contextPath); |
| } |
| wrapper = new ApplicationHttpRequest(hcurrent, context, crossContext); |
| } else { |
| wrapper = new ApplicationRequest(current); |
| } |
| if (previous == null) { |
| state.outerRequest = wrapper; |
| } else { |
| ((ServletRequestWrapper) previous).setRequest(wrapper); |
| } |
| state.wrapRequest = wrapper; |
| return wrapper; |
| |
| } |
| |
| |
| /** |
| * Create and return a response wrapper that has been inserted in the appropriate spot in the response chain. |
| */ |
| private ServletResponse wrapResponse(State state) { |
| |
| // Locate the response we should insert in front of |
| ServletResponse previous = null; |
| ServletResponse current = state.outerResponse; |
| while (current != null) { |
| if (state.hresponse == null && current instanceof HttpServletResponse) { |
| state.hresponse = (HttpServletResponse) current; |
| if (!state.including) { // Forward only needs hresponse |
| return null; |
| } |
| } |
| if (!(current instanceof ServletResponseWrapper)) { |
| break; |
| } |
| if (current instanceof ApplicationHttpResponse) { |
| break; |
| } |
| if (current instanceof ApplicationResponse) { |
| break; |
| } |
| previous = current; |
| current = ((ServletResponseWrapper) current).getResponse(); |
| } |
| |
| // Instantiate a new wrapper at this point and insert it in the chain |
| ServletResponse wrapper; |
| if (current instanceof HttpServletResponse) { |
| wrapper = new ApplicationHttpResponse((HttpServletResponse) current, state.including); |
| } else { |
| wrapper = new ApplicationResponse(current, state.including); |
| } |
| if (previous == null) { |
| state.outerResponse = wrapper; |
| } else { |
| ((ServletResponseWrapper) previous).setResponse(wrapper); |
| } |
| state.wrapResponse = wrapper; |
| return wrapper; |
| |
| } |
| |
| private void checkSameObjects(ServletRequest appRequest, ServletResponse appResponse) throws ServletException { |
| ServletRequest originalRequest = ApplicationFilterChain.getLastServicedRequest(); |
| ServletResponse originalResponse = ApplicationFilterChain.getLastServicedResponse(); |
| |
| // Some forwards, e.g. from valves will not set original values |
| if (originalRequest == null || originalResponse == null) { |
| return; |
| } |
| |
| boolean same = false; |
| ServletRequest dispatchedRequest = appRequest; |
| |
| // find the request that was passed into the service method |
| while (originalRequest instanceof ServletRequestWrapper && |
| ((ServletRequestWrapper) originalRequest).getRequest() != null) { |
| originalRequest = ((ServletRequestWrapper) originalRequest).getRequest(); |
| } |
| // compare with the dispatched request |
| while (true) { |
| if (originalRequest.equals(dispatchedRequest)) { |
| same = true; |
| } |
| if (!same && dispatchedRequest instanceof ServletRequestWrapper) { |
| dispatchedRequest = ((ServletRequestWrapper) dispatchedRequest).getRequest(); |
| } else { |
| break; |
| } |
| } |
| if (!same) { |
| throw new ServletException(sm.getString("applicationDispatcher.specViolation.request")); |
| } |
| |
| same = false; |
| ServletResponse dispatchedResponse = appResponse; |
| |
| // find the response that was passed into the service method |
| while (originalResponse instanceof ServletResponseWrapper && |
| ((ServletResponseWrapper) originalResponse).getResponse() != null) { |
| originalResponse = ((ServletResponseWrapper) originalResponse).getResponse(); |
| } |
| // compare with the dispatched response |
| while (true) { |
| if (originalResponse.equals(dispatchedResponse)) { |
| same = true; |
| } |
| |
| if (!same && dispatchedResponse instanceof ServletResponseWrapper) { |
| dispatchedResponse = ((ServletResponseWrapper) dispatchedResponse).getResponse(); |
| } else { |
| break; |
| } |
| } |
| |
| if (!same) { |
| throw new ServletException(sm.getString("applicationDispatcher.specViolation.response")); |
| } |
| } |
| |
| private void recycleRequestWrapper(State state) { |
| if (state.wrapRequest instanceof ApplicationHttpRequest) { |
| ((ApplicationHttpRequest) state.wrapRequest).recycle(); |
| } |
| } |
| } |