1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.catalina.core;
18
19 import java.io.IOException;
20 import java.io.PrintWriter;
21 import java.security.AccessController;
22 import java.security.PrivilegedActionException;
23 import java.security.PrivilegedExceptionAction;
24
25 import javax.servlet.AsyncContext;
26 import javax.servlet.DispatcherType;
27 import javax.servlet.RequestDispatcher;
28 import javax.servlet.Servlet;
29 import javax.servlet.ServletException;
30 import javax.servlet.ServletOutputStream;
31 import javax.servlet.ServletRequest;
32 import javax.servlet.ServletRequestWrapper;
33 import javax.servlet.ServletResponse;
34 import javax.servlet.ServletResponseWrapper;
35 import javax.servlet.UnavailableException;
36 import javax.servlet.http.HttpServletMapping;
37 import javax.servlet.http.HttpServletRequest;
38 import javax.servlet.http.HttpServletResponse;
39
40 import org.apache.catalina.AsyncDispatcher;
41 import org.apache.catalina.Context;
42 import org.apache.catalina.Globals;
43 import org.apache.catalina.Wrapper;
44 import org.apache.catalina.connector.ClientAbortException;
45 import org.apache.catalina.connector.Request;
46 import org.apache.catalina.connector.RequestFacade;
47 import org.apache.catalina.connector.Response;
48 import org.apache.catalina.connector.ResponseFacade;
49 import org.apache.tomcat.util.ExceptionUtils;
50 import org.apache.tomcat.util.res.StringManager;
51
52 /**
53  * Standard implementation of <code>RequestDispatcher</code> that allows a
54  * request to be forwarded to a different resource to create the ultimate
55  * response, or to include the output of another resource in the response
56  * from this resource.  This implementation allows application level servlets
57  * to wrap the request and/or response objects that are passed on to the
58  * called resource, as long as the wrapping classes extend
59  * <code>javax.servlet.ServletRequestWrapper</code> and
60  * <code>javax.servlet.ServletResponseWrapper</code>.
61  *
62  * @author Craig R. McClanahan
63  */

64 final class ApplicationDispatcher implements AsyncDispatcher, RequestDispatcher {
65
66     static final boolean STRICT_SERVLET_COMPLIANCE;
67
68     static final boolean WRAP_SAME_OBJECT;
69
70
71     static {
72         STRICT_SERVLET_COMPLIANCE = Globals.STRICT_SERVLET_COMPLIANCE;
73
74         String wrapSameObject = System.getProperty(
75                 "org.apache.catalina.core.ApplicationDispatcher.WRAP_SAME_OBJECT");
76         if (wrapSameObject == null) {
77             WRAP_SAME_OBJECT = STRICT_SERVLET_COMPLIANCE;
78         } else {
79             WRAP_SAME_OBJECT = Boolean.parseBoolean(wrapSameObject);
80         }
81     }
82
83
84     protected class PrivilegedForward
85             implements PrivilegedExceptionAction<Void> {
86         private final ServletRequest request;
87         private final ServletResponse response;
88
89         PrivilegedForward(ServletRequest request, ServletResponse response) {
90             this.request = request;
91             this.response = response;
92         }
93
94         @Override
95         public Void run() throws java.lang.Exception {
96             doForward(request,response);
97             return null;
98         }
99     }
100
101     protected class PrivilegedInclude implements
102             PrivilegedExceptionAction<Void> {
103         private final ServletRequest request;
104         private final ServletResponse response;
105
106         PrivilegedInclude(ServletRequest request, ServletResponse response) {
107             this.request = request;
108             this.response = response;
109         }
110
111         @Override
112         public Void run() throws ServletException, IOException {
113             doInclude(request, response);
114             return null;
115         }
116     }
117
118     protected class PrivilegedDispatch implements
119             PrivilegedExceptionAction<Void> {
120         private final ServletRequest request;
121         private final ServletResponse response;
122
123         PrivilegedDispatch(ServletRequest request, ServletResponse response) {
124             this.request = request;
125             this.response = response;
126         }
127
128         @Override
129         public Void run() throws ServletException, IOException {
130             doDispatch(request, response);
131             return null;
132         }
133     }
134
135
136     /**
137      * Used to pass state when the request dispatcher is used. Using instance
138      * variables causes threading issues and state is too complex to pass and
139      * return single ServletRequest or ServletResponse objects.
140      */

141     private static class State {
142         State(ServletRequest request, ServletResponse response,
143                 boolean including) {
144             this.outerRequest = request;
145             this.outerResponse = response;
146             this.including = including;
147         }
148
149         /**
150          * The outermost request that will be passed on to the invoked servlet.
151          */

152         ServletRequest outerRequest = null;
153
154
155         /**
156          * The outermost response that will be passed on to the invoked servlet.
157          */

158         ServletResponse outerResponse = null;
159
160         /**
161          * The request wrapper we have created and installed (if any).
162          */

163         ServletRequest wrapRequest = null;
164
165
166         /**
167          * The response wrapper we have created and installed (if any).
168          */

169         ServletResponse wrapResponse = null;
170
171         /**
172          * Are we performing an include() instead of a forward()?
173          */

174         boolean including = false;
175
176         /**
177          * Outermost HttpServletRequest in the chain
178          */

179         HttpServletRequest hrequest = null;
180
181         /**
182          * Outermost HttpServletResponse in the chain
183          */

184         HttpServletResponse hresponse = null;
185     }
186
187     // ----------------------------------------------------------- Constructors
188
189
190     /**
191      * Construct a new instance of this class, configured according to the
192      * specified parameters.  If both servletPath and pathInfo are
193      * <code>null</code>, it will be assumed that this RequestDispatcher
194      * was acquired by name, rather than by path.
195      *
196      * @param wrapper The Wrapper associated with the resource that will
197      *  be forwarded to or included (required)
198      * @param requestURI The request URI to this resource (if any)
199      * @param servletPath The revised servlet path to this resource (if any)
200      * @param pathInfo The revised extra path information to this resource
201      *  (if any)
202      * @param queryString Query string parameters included with this request
203      *  (if any)
204      * @param mapping The mapping for this resource (if any)
205      * @param name Servlet name (if a named dispatcher was created)
206      *  else <code>null</code>
207      */

208     public ApplicationDispatcher
209         (Wrapper wrapper, String requestURI, String servletPath,
210          String pathInfo, String queryString, HttpServletMapping mapping, String name) {
211
212         super();
213
214         // Save all of our configuration parameters
215         this.wrapper = wrapper;
216         this.context = (Context) wrapper.getParent();
217         this.requestURI = requestURI;
218         this.servletPath = servletPath;
219         this.pathInfo = pathInfo;
220         this.queryString = queryString;
221         this.mapping = mapping;
222         this.name = name;
223     }
224
225
226     // ----------------------------------------------------- Instance Variables
227
228     /**
229      * The Context this RequestDispatcher is associated with.
230      */

231     private final Context context;
232
233
234     /**
235      * The servlet name for a named dispatcher.
236      */

237     private final String name;
238
239
240     /**
241      * The extra path information for this RequestDispatcher.
242      */

243     private final String pathInfo;
244
245
246     /**
247      * The query string parameters for this RequestDispatcher.
248      */

249     private final String queryString;
250
251
252     /**
253      * The request URI for this RequestDispatcher.
254      */

255     private final String requestURI;
256
257
258     /**
259      * The servlet path for this RequestDispatcher.
260      */

261     private final String servletPath;
262
263
264     /**
265      * The mapping for this RequestDispatcher.
266      */

267     private final HttpServletMapping mapping;
268
269
270     /**
271      * The StringManager for this package.
272      */

273     private static final StringManager sm = StringManager.getManager(Constants.Package);
274
275
276     /**
277      * The Wrapper associated with the resource that will be forwarded to
278      * or included.
279      */

280     private final Wrapper wrapper;
281
282
283     // --------------------------------------------------------- Public Methods
284
285
286     /**
287      * Forward this request and response to another resource for processing.
288      * Any runtime exception, IOException, or ServletException thrown by the
289      * called servlet will be propagated to the caller.
290      *
291      * @param request The servlet request to be forwarded
292      * @param response The servlet response to be forwarded
293      *
294      * @exception IOException if an input/output error occurs
295      * @exception ServletException if a servlet exception occurs
296      */

297     @Override
298     public void forward(ServletRequest request, ServletResponse response)
299         throws ServletException, IOException
300     {
301         if (Globals.IS_SECURITY_ENABLED) {
302             try {
303                 PrivilegedForward dp = new PrivilegedForward(request,response);
304                 AccessController.doPrivileged(dp);
305             } catch (PrivilegedActionException pe) {
306                 Exception e = pe.getException();
307                 if (e instanceof ServletException)
308                     throw (ServletException) e;
309                 throw (IOException) e;
310             }
311         } else {
312             doForward(request,response);
313         }
314     }
315
316     private void doForward(ServletRequest request, ServletResponse response)
317         throws ServletException, IOException
318     {
319
320         // Reset any output that has been buffered, but keep headers/cookies
321         if (response.isCommitted()) {
322             throw new IllegalStateException
323                 (sm.getString("applicationDispatcher.forward.ise"));
324         }
325         try {
326             response.resetBuffer();
327         } catch (IllegalStateException e) {
328             throw e;
329         }
330
331         // Set up to handle the specified request and response
332         State state = new State(request, response, false);
333
334         if (WRAP_SAME_OBJECT) {
335             // Check SRV.8.2 / SRV.14.2.5.1 compliance
336             checkSameObjects(request, response);
337         }
338
339         wrapResponse(state);
340         // Handle an HTTP named dispatcher forward
341         if ((servletPath == null) && (pathInfo == null)) {
342
343             ApplicationHttpRequest wrequest =
344                 (ApplicationHttpRequest) wrapRequest(state);
345             HttpServletRequest hrequest = state.hrequest;
346             wrequest.setRequestURI(hrequest.getRequestURI());
347             wrequest.setContextPath(hrequest.getContextPath());
348             wrequest.setServletPath(hrequest.getServletPath());
349             wrequest.setPathInfo(hrequest.getPathInfo());
350             wrequest.setQueryString(hrequest.getQueryString());
351
352             processRequest(request,response,state);
353         }
354
355         // Handle an HTTP path-based forward
356         else {
357
358             ApplicationHttpRequest wrequest = (ApplicationHttpRequest) wrapRequest(state);
359             HttpServletRequest hrequest = state.hrequest;
360             if (hrequest.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI) == null) {
361                 wrequest.setAttribute(RequestDispatcher.FORWARD_REQUEST_URI,
362                                       hrequest.getRequestURI());
363                 wrequest.setAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH,
364                                       hrequest.getContextPath());
365                 wrequest.setAttribute(RequestDispatcher.FORWARD_SERVLET_PATH,
366                                       hrequest.getServletPath());
367                 wrequest.setAttribute(RequestDispatcher.FORWARD_PATH_INFO,
368                                       hrequest.getPathInfo());
369                 wrequest.setAttribute(RequestDispatcher.FORWARD_QUERY_STRING,
370                                       hrequest.getQueryString());
371                 wrequest.setAttribute(RequestDispatcher.FORWARD_MAPPING, hrequest.getHttpServletMapping());
372             }
373
374             wrequest.setContextPath(context.getEncodedPath());
375             wrequest.setRequestURI(requestURI);
376             wrequest.setServletPath(servletPath);
377             wrequest.setPathInfo(pathInfo);
378             if (queryString != null) {
379                 wrequest.setQueryString(queryString);
380                 wrequest.setQueryParams(queryString);
381             }
382             wrequest.setMapping(mapping);
383
384             processRequest(request,response,state);
385         }
386
387         if (request.isAsyncStarted()) {
388             // An async request was started during the forward, don't close the
389             // response as it may be written to during the async handling
390             return;
391         }
392
393         // This is not a real close in order to support error processing
394         if (wrapper.getLogger().isDebugEnabled() )
395             wrapper.getLogger().debug(" Disabling the response for further output");
396
397         if  (response instanceof ResponseFacade) {
398             ((ResponseFacade) response).finish();
399         } else {
400             // Servlet SRV.6.2.2. The Request/Response may have been wrapped
401             // and may no longer be instance of RequestFacade
402             if (wrapper.getLogger().isDebugEnabled()){
403                 wrapper.getLogger().debug( " The Response is vehiculed using a wrapper: "
404                            + response.getClass().getName() );
405             }
406
407             // Close anyway
408             try {
409                 PrintWriter writer = response.getWriter();
410                 writer.close();
411             } catch (IllegalStateException e) {
412                 try {
413                     ServletOutputStream stream = response.getOutputStream();
414                     stream.close();
415                 } catch (IllegalStateException f) {
416                     // Ignore
417                 } catch (IOException f) {
418                     // Ignore
419                 }
420             } catch (IOException e) {
421                 // Ignore
422             }
423         }
424
425     }
426
427
428     /**
429      * Prepare the request based on the filter configuration.
430      * @param request The servlet request we are processing
431      * @param response The servlet response we are creating
432      * @param state The RD state
433      *
434      * @exception IOException if an input/output error occurs
435      * @exception ServletException if a servlet error occurs
436      */

437     private void processRequest(ServletRequest request,
438                                 ServletResponse response,
439                                 State state)
440         throws IOException, ServletException {
441
442         DispatcherType disInt = (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
443         if (disInt != null) {
444             boolean doInvoke = true;
445
446             if (context.getFireRequestListenersOnForwards() &&
447                     !context.fireRequestInitEvent(request)) {
448                 doInvoke = false;
449             }
450
451             if (doInvoke) {
452                 if (disInt != DispatcherType.ERROR) {
453                     state.outerRequest.setAttribute(
454                             Globals.DISPATCHER_REQUEST_PATH_ATTR,
455                             getCombinedPath());
456                     state.outerRequest.setAttribute(
457                             Globals.DISPATCHER_TYPE_ATTR,
458                             DispatcherType.FORWARD);
459                     invoke(state.outerRequest, response, state);
460                 } else {
461                     invoke(state.outerRequest, response, state);
462                 }
463
464                 if (context.getFireRequestListenersOnForwards()) {
465                     context.fireRequestDestroyEvent(request);
466                 }
467             }
468         }
469     }
470
471
472     /**
473      * Combine the servletPath and the pathInfo. If pathInfo is
474      * <code>null</code> it is ignored. If servletPath is <code>null</code> then
475      * <code>null</code> is returned.
476      * @return The combined path with pathInfo appended to servletInfo
477      */

478     private String getCombinedPath() {
479         if (servletPath == null) {
480             return null;
481         }
482         if (pathInfo == null) {
483             return servletPath;
484         }
485         return servletPath + pathInfo;
486     }
487
488
489     /**
490      * Include the response from another resource in the current response.
491      * Any runtime exception, IOException, or ServletException thrown by the
492      * called servlet will be propagated to the caller.
493      *
494      * @param request The servlet request that is including this one
495      * @param response The servlet response to be appended to
496      *
497      * @exception IOException if an input/output error occurs
498      * @exception ServletException if a servlet exception occurs
499      */

500     @Override
501     public void include(ServletRequest request, ServletResponse response)
502         throws ServletException, IOException
503     {
504         if (Globals.IS_SECURITY_ENABLED) {
505             try {
506                 PrivilegedInclude dp = new PrivilegedInclude(request,response);
507                 AccessController.doPrivileged(dp);
508             } catch (PrivilegedActionException pe) {
509                 Exception e = pe.getException();
510
511                 if (e instanceof ServletException)
512                     throw (ServletException) e;
513                 throw (IOException) e;
514             }
515         } else {
516             doInclude(request, response);
517         }
518     }
519
520     private void doInclude(ServletRequest request, ServletResponse response)
521             throws ServletException, IOException {
522
523         // Set up to handle the specified request and response
524         State state = new State(request, response, true);
525
526         if (WRAP_SAME_OBJECT) {
527             // Check SRV.8.2 / SRV.14.2.5.1 compliance
528             checkSameObjects(request, response);
529         }
530
531         // Create a wrapped response to use for this request
532         wrapResponse(state);
533
534         // Handle an HTTP named dispatcher include
535         if (name != null) {
536
537             ApplicationHttpRequest wrequest =
538                 (ApplicationHttpRequest) wrapRequest(state);
539             wrequest.setAttribute(Globals.NAMED_DISPATCHER_ATTR, name);
540             if (servletPath != null)
541                 wrequest.setServletPath(servletPath);
542             wrequest.setAttribute(Globals.DISPATCHER_TYPE_ATTR,
543                     DispatcherType.INCLUDE);
544             wrequest.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
545                     getCombinedPath());
546             invoke(state.outerRequest, state.outerResponse, state);
547         }
548
549         // Handle an HTTP path based include
550         else {
551
552             ApplicationHttpRequest wrequest =
553                 (ApplicationHttpRequest) wrapRequest(state);
554             String contextPath = context.getPath();
555             if (requestURI != null)
556                 wrequest.setAttribute(RequestDispatcher.INCLUDE_REQUEST_URI,
557                                       requestURI);
558             if (contextPath != null)
559                 wrequest.setAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH,
560                                       contextPath);
561             if (servletPath != null)
562                 wrequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH,
563                                       servletPath);
564             if (pathInfo != null)
565                 wrequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO,
566                                       pathInfo);
567             if (queryString != null) {
568                 wrequest.setAttribute(RequestDispatcher.INCLUDE_QUERY_STRING,
569                                       queryString);
570                 wrequest.setQueryParams(queryString);
571             }
572             if (mapping != null) {
573                 wrequest.setAttribute(RequestDispatcher.INCLUDE_MAPPING, mapping);
574             }
575
576             wrequest.setAttribute(Globals.DISPATCHER_TYPE_ATTR,
577                     DispatcherType.INCLUDE);
578             wrequest.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
579                     getCombinedPath());
580             invoke(state.outerRequest, state.outerResponse, state);
581         }
582
583     }
584
585
586     @Override
587     public void dispatch(ServletRequest request, ServletResponse response)
588             throws ServletException, IOException {
589         if (Globals.IS_SECURITY_ENABLED) {
590             try {
591                 PrivilegedDispatch dp = new PrivilegedDispatch(request,response);
592                 AccessController.doPrivileged(dp);
593             } catch (PrivilegedActionException pe) {
594                 Exception e = pe.getException();
595
596                 if (e instanceof ServletException)
597                     throw (ServletException) e;
598                 throw (IOException) e;
599             }
600         } else {
601             doDispatch(request, response);
602         }
603     }
604
605     private void doDispatch(ServletRequest request, ServletResponse response)
606             throws ServletException, IOException {
607
608         // Set up to handle the specified request and response
609         State state = new State(request, response, false);
610
611         // Create a wrapped response to use for this request
612         wrapResponse(state);
613
614         ApplicationHttpRequest wrequest = (ApplicationHttpRequest) wrapRequest(state);
615         HttpServletRequest hrequest = state.hrequest;
616
617         wrequest.setAttribute(Globals.DISPATCHER_TYPE_ATTR, DispatcherType.ASYNC);
618         wrequest.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, getCombinedPath());
619         wrequest.setAttribute(AsyncContext.ASYNC_MAPPING, hrequest.getHttpServletMapping());
620
621         wrequest.setContextPath(context.getEncodedPath());
622         wrequest.setRequestURI(requestURI);
623         wrequest.setServletPath(servletPath);
624         wrequest.setPathInfo(pathInfo);
625         if (queryString != null) {
626             wrequest.setQueryString(queryString);
627             wrequest.setQueryParams(queryString);
628         }
629         if (!Globals.STRICT_SERVLET_COMPLIANCE) {
630             wrequest.setMapping(mapping);
631         }
632
633         invoke(state.outerRequest, state.outerResponse, state);
634     }
635
636
637     // -------------------------------------------------------- Private Methods
638
639
640     /**
641      * Ask the resource represented by this RequestDispatcher to process
642      * the associated request, and create (or append to) the associated
643      * response.
644      * <p>
645      * <strong>IMPLEMENTATION NOTE</strong>: This implementation assumes
646      * that no filters are applied to a forwarded or included resource,
647      * because they were already done for the original request.
648      *
649      * @param request The servlet request we are processing
650      * @param response The servlet response we are creating
651      *
652      * @exception IOException if an input/output error occurs
653      * @exception ServletException if a servlet error occurs
654      */

655     private void invoke(ServletRequest request, ServletResponse response,
656             State state) throws IOException, ServletException {
657
658         // Checking to see if the context classloader is the current context
659         // classloader. If it's not, we're saving it, and setting the context
660         // classloader to the Context classloader
661         ClassLoader oldCCL = context.bind(falsenull);
662
663         // Initialize local variables we may need
664         HttpServletResponse hresponse = state.hresponse;
665         Servlet servlet = null;
666         IOException ioException = null;
667         ServletException servletException = null;
668         RuntimeException runtimeException = null;
669         boolean unavailable = false;
670
671         // Check for the servlet being marked unavailable
672         if (wrapper.isUnavailable()) {
673             wrapper.getLogger().warn(
674                     sm.getString("applicationDispatcher.isUnavailable",
675                     wrapper.getName()));
676             long available = wrapper.getAvailable();
677             if ((available > 0L) && (available < Long.MAX_VALUE))
678                 hresponse.setDateHeader("Retry-After", available);
679             hresponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm
680                     .getString("applicationDispatcher.isUnavailable", wrapper
681                             .getName()));
682             unavailable = true;
683         }
684
685         // Allocate a servlet instance to process this request
686         try {
687             if (!unavailable) {
688                 servlet = wrapper.allocate();
689             }
690         } catch (ServletException e) {
691             wrapper.getLogger().error(sm.getString("applicationDispatcher.allocateException",
692                              wrapper.getName()), StandardWrapper.getRootCause(e));
693             servletException = e;
694         } catch (Throwable e) {
695             ExceptionUtils.handleThrowable(e);
696             wrapper.getLogger().error(sm.getString("applicationDispatcher.allocateException",
697                              wrapper.getName()), e);
698             servletException = new ServletException
699                 (sm.getString("applicationDispatcher.allocateException",
700                               wrapper.getName()), e);
701             servlet = null;
702         }
703
704         // Get the FilterChain Here
705         ApplicationFilterChain filterChain =
706                 ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
707
708         // Call the service() method for the allocated servlet instance
709         try {
710             // for includes/forwards
711             if ((servlet != null) && (filterChain != null)) {
712                filterChain.doFilter(request, response);
713              }
714             // Servlet Service Method is called by the FilterChain
715         } catch (ClientAbortException e) {
716             ioException = e;
717         } catch (IOException e) {
718             wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException",
719                              wrapper.getName()), e);
720             ioException = e;
721         } catch (UnavailableException e) {
722             wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException",
723                              wrapper.getName()), e);
724             servletException = e;
725             wrapper.unavailable(e);
726         } catch (ServletException e) {
727             Throwable rootCause = StandardWrapper.getRootCause(e);
728             if (!(rootCause instanceof ClientAbortException)) {
729                 wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException",
730                         wrapper.getName()), rootCause);
731             }
732             servletException = e;
733         } catch (RuntimeException e) {
734             wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException",
735                              wrapper.getName()), e);
736             runtimeException = e;
737         }
738
739         // Release the filter chain (if any) for this request
740         try {
741             if (filterChain != null)
742                 filterChain.release();
743         } catch (Throwable e) {
744             ExceptionUtils.handleThrowable(e);
745             wrapper.getLogger().error(sm.getString("standardWrapper.releaseFilters",
746                              wrapper.getName()), e);
747             // FIXME: Exception handling needs to be similar to what is in the StandardWrapperValue
748         }
749
750         // Deallocate the allocated servlet instance
751         try {
752             if (servlet != null) {
753                 wrapper.deallocate(servlet);
754             }
755         } catch (ServletException e) {
756             wrapper.getLogger().error(sm.getString("applicationDispatcher.deallocateException",
757                              wrapper.getName()), e);
758             servletException = e;
759         } catch (Throwable e) {
760             ExceptionUtils.handleThrowable(e);
761             wrapper.getLogger().error(sm.getString("applicationDispatcher.deallocateException",
762                              wrapper.getName()), e);
763             servletException = new ServletException
764                 (sm.getString("applicationDispatcher.deallocateException",
765                               wrapper.getName()), e);
766         }
767
768         // Reset the old context class loader
769         context.unbind(false, oldCCL);
770
771         // Unwrap request/response if needed
772         // See Bugzilla 30949
773         unwrapRequest(state);
774         unwrapResponse(state);
775         // Recycle request if necessary (also BZ 30949)
776         recycleRequestWrapper(state);
777
778         // Rethrow an exception if one was thrown by the invoked servlet
779         if (ioException != null)
780             throw ioException;
781         if (servletException != null)
782             throw servletException;
783         if (runtimeException != null)
784             throw runtimeException;
785
786     }
787
788
789     /**
790      * Unwrap the request if we have wrapped it.
791      */

792     private void unwrapRequest(State state) {
793
794         if (state.wrapRequest == null)
795             return;
796
797         if (state.outerRequest.isAsyncStarted()) {
798             if (!state.outerRequest.getAsyncContext().hasOriginalRequestAndResponse()) {
799                 return;
800             }
801         }
802
803         ServletRequest previous = null;
804         ServletRequest current = state.outerRequest;
805         while (current != null) {
806
807             // If we run into the container request we are done
808             if ((current instanceof Request)
809                 || (current instanceof RequestFacade))
810                 break;
811
812             // Remove the current request if it is our wrapper
813             if (current == state.wrapRequest) {
814                 ServletRequest next =
815                   ((ServletRequestWrapper) current).getRequest();
816                 if (previous == null)
817                     state.outerRequest = next;
818                 else
819                     ((ServletRequestWrapper) previous).setRequest(next);
820                 break;
821             }
822
823             // Advance to the next request in the chain
824             previous = current;
825             current = ((ServletRequestWrapper) current).getRequest();
826
827         }
828
829     }
830
831     /**
832      * Unwrap the response if we have wrapped it.
833      */

834     private void unwrapResponse(State state) {
835
836         if (state.wrapResponse == null)
837             return;
838
839         if (state.outerRequest.isAsyncStarted()) {
840             if (!state.outerRequest.getAsyncContext().hasOriginalRequestAndResponse()) {
841                 return;
842             }
843         }
844
845         ServletResponse previous = null;
846         ServletResponse current = state.outerResponse;
847         while (current != null) {
848
849             // If we run into the container response we are done
850             if ((current instanceof Response)
851                 || (current instanceof ResponseFacade))
852                 break;
853
854             // Remove the current response if it is our wrapper
855             if (current == state.wrapResponse) {
856                 ServletResponse next =
857                   ((ServletResponseWrapper) current).getResponse();
858                 if (previous == null)
859                     state.outerResponse = next;
860                 else
861                     ((ServletResponseWrapper) previous).setResponse(next);
862                 break;
863             }
864
865             // Advance to the next response in the chain
866             previous = current;
867             current = ((ServletResponseWrapper) current).getResponse();
868
869         }
870
871     }
872
873
874     /**
875      * Create and return a request wrapper that has been inserted in the
876      * appropriate spot in the request chain.
877      */

878     private ServletRequest wrapRequest(State state) {
879
880         // Locate the request we should insert in front of
881         ServletRequest previous = null;
882         ServletRequest current = state.outerRequest;
883         while (current != null) {
884             if(state.hrequest == null && (current instanceof HttpServletRequest))
885                 state.hrequest = (HttpServletRequest)current;
886             if (!(current instanceof ServletRequestWrapper))
887                 break;
888             if (current instanceof ApplicationHttpRequest)
889                 break;
890             if (current instanceof ApplicationRequest)
891                 break;
892             previous = current;
893             current = ((ServletRequestWrapper) current).getRequest();
894         }
895
896         // Instantiate a new wrapper at this point and insert it in the chain
897         ServletRequest wrapper = null;
898         if ((current instanceof ApplicationHttpRequest) ||
899             (current instanceof Request) ||
900             (current instanceof HttpServletRequest)) {
901             // Compute a crossContext flag
902             HttpServletRequest hcurrent = (HttpServletRequest) current;
903             boolean crossContext = false;
904             if ((state.outerRequest instanceof ApplicationHttpRequest) ||
905                 (state.outerRequest instanceof Request) ||
906                 (state.outerRequest instanceof HttpServletRequest)) {
907                 HttpServletRequest houterRequest =
908                     (HttpServletRequest) state.outerRequest;
909                 Object contextPath = houterRequest.getAttribute(
910                         RequestDispatcher.INCLUDE_CONTEXT_PATH);
911                 if (contextPath == null) {
912                     // Forward
913                     contextPath = houterRequest.getContextPath();
914                 }
915                 crossContext = !(context.getPath().equals(contextPath));
916             }
917             wrapper = new ApplicationHttpRequest
918                 (hcurrent, context, crossContext);
919         } else {
920             wrapper = new ApplicationRequest(current);
921         }
922         if (previous == null)
923             state.outerRequest = wrapper;
924         else
925             ((ServletRequestWrapper) previous).setRequest(wrapper);
926         state.wrapRequest = wrapper;
927         return wrapper;
928
929     }
930
931
932     /**
933      * Create and return a response wrapper that has been inserted in the
934      * appropriate spot in the response chain.
935      */

936     private ServletResponse wrapResponse(State state) {
937
938         // Locate the response we should insert in front of
939         ServletResponse previous = null;
940         ServletResponse current = state.outerResponse;
941         while (current != null) {
942             if(state.hresponse == null && (current instanceof HttpServletResponse)) {
943                 state.hresponse = (HttpServletResponse)current;
944                 if(!state.including) // Forward only needs hresponse
945                     return null;
946             }
947             if (!(current instanceof ServletResponseWrapper))
948                 break;
949             if (current instanceof ApplicationHttpResponse)
950                 break;
951             if (current instanceof ApplicationResponse)
952                 break;
953             previous = current;
954             current = ((ServletResponseWrapper) current).getResponse();
955         }
956
957         // Instantiate a new wrapper at this point and insert it in the chain
958         ServletResponse wrapper = null;
959         if ((current instanceof ApplicationHttpResponse) ||
960             (current instanceof Response) ||
961             (current instanceof HttpServletResponse))
962             wrapper =
963                 new ApplicationHttpResponse((HttpServletResponse) current,
964                         state.including);
965         else
966             wrapper = new ApplicationResponse(current, state.including);
967         if (previous == null)
968             state.outerResponse = wrapper;
969         else
970             ((ServletResponseWrapper) previous).setResponse(wrapper);
971         state.wrapResponse = wrapper;
972         return wrapper;
973
974     }
975
976     private void checkSameObjects(ServletRequest appRequest,
977             ServletResponse appResponse) throws ServletException {
978         ServletRequest originalRequest =
979             ApplicationFilterChain.getLastServicedRequest();
980         ServletResponse originalResponse =
981             ApplicationFilterChain.getLastServicedResponse();
982
983         // Some forwards, eg from valves will not set original values
984         if (originalRequest == null || originalResponse == null) {
985             return;
986         }
987
988         boolean same = false;
989         ServletRequest dispatchedRequest = appRequest;
990
991         //find the request that was passed into the service method
992         while (originalRequest instanceof ServletRequestWrapper &&
993                 ((ServletRequestWrapper) originalRequest).getRequest()!=null ) {
994             originalRequest =
995                 ((ServletRequestWrapper) originalRequest).getRequest();
996         }
997         //compare with the dispatched request
998         while (!same) {
999             if (originalRequest.equals(dispatchedRequest)) {
1000                 same = true;
1001             }
1002             if (!same && dispatchedRequest instanceof ServletRequestWrapper) {
1003                 dispatchedRequest =
1004                     ((ServletRequestWrapper) dispatchedRequest).getRequest();
1005             } else {
1006                 break;
1007             }
1008         }
1009         if (!same) {
1010             throw new ServletException(sm.getString(
1011                     "applicationDispatcher.specViolation.request"));
1012         }
1013
1014         same = false;
1015         ServletResponse dispatchedResponse = appResponse;
1016
1017         //find the response that was passed into the service method
1018         while (originalResponse instanceof ServletResponseWrapper &&
1019                 ((ServletResponseWrapper) originalResponse).getResponse() !=
1020                     null ) {
1021             originalResponse =
1022                 ((ServletResponseWrapper) originalResponse).getResponse();
1023         }
1024         //compare with the dispatched response
1025         while (!same) {
1026             if (originalResponse.equals(dispatchedResponse)) {
1027                 same = true;
1028             }
1029
1030             if (!same && dispatchedResponse instanceof ServletResponseWrapper) {
1031                 dispatchedResponse =
1032                     ((ServletResponseWrapper) dispatchedResponse).getResponse();
1033             } else {
1034                 break;
1035             }
1036         }
1037
1038         if (!same) {
1039             throw new ServletException(sm.getString(
1040                     "applicationDispatcher.specViolation.response"));
1041         }
1042     }
1043
1044     private void recycleRequestWrapper(State state) {
1045         if (state.wrapRequest instanceof ApplicationHttpRequest) {
1046             ((ApplicationHttpRequest) state.wrapRequest).recycle();
1047         }
1048     }
1049 }
1050