ApplicationDispatcher.java
/*
* 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.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>.
*
* @author Craig R. McClanahan
*/
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 = null;
/**
* The outermost response that will be passed on to the invoked servlet.
*/
ServletResponse outerResponse = null;
/**
* 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 = false;
/**
* 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"));
}
try {
response.resetBuffer();
} catch (IllegalStateException e) {
throw e;
}
// 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);
// Handle an HTTP named dispatcher forward
if (servletPath == null && pathInfo == null) {
ApplicationHttpRequest wrequest = (ApplicationHttpRequest) wrapRequest(state);
HttpServletRequest hrequest = state.hrequest;
/*
* 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("GET");
}
wrequest.setRequestURI(hrequest.getRequestURI());
wrequest.setContextPath(hrequest.getContextPath());
wrequest.setServletPath(hrequest.getServletPath());
wrequest.setPathInfo(hrequest.getPathInfo());
wrequest.setQueryString(hrequest.getQueryString());
processRequest(request, response, state);
}
// Handle an HTTP path-based forward
else {
ApplicationHttpRequest wrequest = (ApplicationHttpRequest) wrapRequest(state);
HttpServletRequest hrequest = state.hrequest;
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("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 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 e) {
// 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) {
boolean doInvoke = true;
if (context.getFireRequestListenersOnForwards() && !context.fireRequestInitEvent(request)) {
doInvoke = false;
}
if (doInvoke) {
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);
// Handle an HTTP named dispatcher include
if (name != null) {
ApplicationHttpRequest wrequest = (ApplicationHttpRequest) wrapRequest(state);
wrequest.setAttribute(Globals.NAMED_DISPATCHER_ATTR, name);
if (servletPath != null) {
wrequest.setServletPath(servletPath);
}
wrequest.setAttribute(Globals.DISPATCHER_TYPE_ATTR, DispatcherType.INCLUDE);
wrequest.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, getCombinedPath());
invoke(state.outerRequest, state.outerResponse, state);
}
// Handle an HTTP path based include
else {
ApplicationHttpRequest wrequest = (ApplicationHttpRequest) wrapRequest(state);
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 e) {
ExceptionUtils.handleThrowable(e);
wrapper.getLogger().error(sm.getString("applicationDispatcher.allocateException", wrapper.getName()), e);
servletException =
new ServletException(sm.getString("applicationDispatcher.allocateException", wrapper.getName()), e);
servlet = null;
}
// 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 e) {
wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException", wrapper.getName()), e);
ioException = e;
} 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 e) {
ExceptionUtils.handleThrowable(e);
wrapper.getLogger().error(sm.getString("applicationDispatcher.deallocateException", wrapper.getName()), e);
servletException = new ServletException(
sm.getString("applicationDispatcher.deallocateException", wrapper.getName()), e);
}
// 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 = null;
if (current instanceof ApplicationHttpRequest || current instanceof Request ||
current instanceof HttpServletRequest) {
// Compute a crossContext flag
HttpServletRequest hcurrent = (HttpServletRequest) current;
boolean crossContext = false;
if (state.outerRequest instanceof ApplicationHttpRequest || state.outerRequest instanceof Request ||
state.outerRequest instanceof HttpServletRequest) {
HttpServletRequest houterRequest = (HttpServletRequest) state.outerRequest;
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 = null;
if (current instanceof ApplicationHttpResponse || current instanceof Response ||
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, eg 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 (!same) {
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 (!same) {
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();
}
}
}