View Javadoc

1   // ========================================================================
2   // Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.servlet;
15  
16  import java.io.IOException;
17  import java.util.ArrayList;
18  import java.util.Arrays;
19  import java.util.EnumSet;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Queue;
24  import java.util.concurrent.ConcurrentHashMap;
25  import java.util.concurrent.ConcurrentLinkedQueue;
26  import java.util.concurrent.ConcurrentMap;
27  
28  import javax.servlet.Filter;
29  import javax.servlet.FilterChain;
30  import javax.servlet.RequestDispatcher;
31  import javax.servlet.Servlet;
32  import javax.servlet.ServletContext;
33  import javax.servlet.ServletException;
34  import javax.servlet.ServletRequest;
35  import javax.servlet.ServletResponse;
36  import javax.servlet.UnavailableException;
37  import javax.servlet.http.HttpServletRequest;
38  import javax.servlet.http.HttpServletResponse;
39  
40  import org.eclipse.jetty.continuation.ContinuationThrowable;
41  import org.eclipse.jetty.http.HttpException;
42  import org.eclipse.jetty.http.PathMap;
43  import org.eclipse.jetty.io.EofException;
44  import org.eclipse.jetty.io.RuntimeIOException;
45  import org.eclipse.jetty.security.IdentityService;
46  import org.eclipse.jetty.security.SecurityHandler;
47  import org.eclipse.jetty.server.AbstractHttpConnection;
48  import org.eclipse.jetty.server.Dispatcher;
49  import org.eclipse.jetty.server.DispatcherType;
50  import org.eclipse.jetty.server.Request;
51  import org.eclipse.jetty.server.Server;
52  import org.eclipse.jetty.server.ServletRequestHttpWrapper;
53  import org.eclipse.jetty.server.ServletResponseHttpWrapper;
54  import org.eclipse.jetty.server.UserIdentity;
55  import org.eclipse.jetty.server.handler.ContextHandler;
56  import org.eclipse.jetty.server.handler.ScopedHandler;
57  import org.eclipse.jetty.util.LazyList;
58  import org.eclipse.jetty.util.MultiException;
59  import org.eclipse.jetty.util.MultiMap;
60  import org.eclipse.jetty.util.TypeUtil;
61  import org.eclipse.jetty.util.URIUtil;
62  import org.eclipse.jetty.util.log.Log;
63  import org.eclipse.jetty.util.log.Logger;
64  
65  /* --------------------------------------------------------------------- */
66  /** Servlet HttpHandler.
67   * This handler maps requests to servlets that implement the
68   * javax.servlet.http.HttpServlet API.
69   * <P>
70   * This handler does not implement the full J2EE features and is intended to
71   * be used directly when a full web application is not required.  If a Web application is required,
72   * then this handler should be used as part of a <code>org.eclipse.jetty.webapp.WebAppContext</code>.
73   * <p>
74   * Unless run as part of a {@link ServletContextHandler} or derivative, the {@link #initialize()}
75   * method must be called manually after start().
76   */
77  public class ServletHandler extends ScopedHandler
78  {
79      private static final Logger LOG = Log.getLogger(ServletHandler.class);
80  
81      /* ------------------------------------------------------------ */
82      public static final String __DEFAULT_SERVLET="default";
83          
84      /* ------------------------------------------------------------ */
85      private ServletContextHandler _contextHandler;
86      private ContextHandler.Context _servletContext;
87      private FilterHolder[] _filters=new FilterHolder[0];
88      private FilterMapping[] _filterMappings;
89      private boolean _filterChainsCached=true;
90      private int _maxFilterChainsCacheSize=512;
91      private boolean _startWithUnavailable=true;
92      private IdentityService _identityService;
93      
94      private ServletHolder[] _servlets=new ServletHolder[0];
95      private ServletMapping[] _servletMappings;
96      
97      private final Map<String,FilterHolder> _filterNameMap= new HashMap<String,FilterHolder>();
98      private List<FilterMapping> _filterPathMappings;
99      private MultiMap<String> _filterNameMappings;
100     
101     private final Map<String,ServletHolder> _servletNameMap=new HashMap<String,ServletHolder>();
102     private PathMap _servletPathMap;
103     
104     protected final ConcurrentMap<String,FilterChain> _chainCache[] = new ConcurrentMap[FilterMapping.ALL];
105     protected final Queue<String>[] _chainLRU = new Queue[FilterMapping.ALL];
106 
107 
108     /* ------------------------------------------------------------ */
109     /** Constructor. 
110      */
111     public ServletHandler()
112     {
113     }
114 
115     /* ------------------------------------------------------------ */
116     /* 
117      * @see org.eclipse.jetty.server.handler.AbstractHandler#setServer(org.eclipse.jetty.server.Server)
118      */
119     public void setServer(Server server)
120     {
121         Server old=getServer();
122         if (old!=null && old!=server)
123         {
124             getServer().getContainer().update(this, _filters, null, "filter",true);
125             getServer().getContainer().update(this, _filterMappings, null, "filterMapping",true);
126             getServer().getContainer().update(this, _servlets, null, "servlet",true);
127             getServer().getContainer().update(this, _servletMappings, null, "servletMapping",true);
128         }
129 
130         super.setServer(server);
131         
132         if (server!=null && old!=server)
133         {
134             server.getContainer().update(this, null, _filters, "filter",true);
135             server.getContainer().update(this, null, _filterMappings, "filterMapping",true);
136             server.getContainer().update(this, null, _servlets, "servlet",true);
137             server.getContainer().update(this, null, _servletMappings, "servletMapping",true);
138         }
139     }
140 
141     /* ----------------------------------------------------------------- */
142     @Override
143     protected synchronized void doStart()
144         throws Exception
145     {
146         _servletContext=ContextHandler.getCurrentContext();
147         _contextHandler=(ServletContextHandler)(_servletContext==null?null:_servletContext.getContextHandler());
148 
149         if (_contextHandler!=null)
150         {
151             SecurityHandler security_handler = (SecurityHandler)_contextHandler.getChildHandlerByClass(SecurityHandler.class);
152             if (security_handler!=null)
153                 _identityService=security_handler.getIdentityService();
154         }
155         
156         updateNameMappings();
157         updateMappings();
158         
159         if(_filterChainsCached)
160         {
161             _chainCache[FilterMapping.REQUEST]=new ConcurrentHashMap<String,FilterChain>();
162             _chainCache[FilterMapping.FORWARD]=new ConcurrentHashMap<String,FilterChain>();
163             _chainCache[FilterMapping.INCLUDE]=new ConcurrentHashMap<String,FilterChain>();
164             _chainCache[FilterMapping.ERROR]=new ConcurrentHashMap<String,FilterChain>();
165             _chainCache[FilterMapping.ASYNC]=new ConcurrentHashMap<String,FilterChain>();
166             
167             _chainLRU[FilterMapping.REQUEST]=new ConcurrentLinkedQueue<String>();
168             _chainLRU[FilterMapping.FORWARD]=new ConcurrentLinkedQueue<String>();
169             _chainLRU[FilterMapping.INCLUDE]=new ConcurrentLinkedQueue<String>();
170             _chainLRU[FilterMapping.ERROR]=new ConcurrentLinkedQueue<String>();
171             _chainLRU[FilterMapping.ASYNC]=new ConcurrentLinkedQueue<String>();
172         }
173 
174         super.doStart();
175         
176         if (_contextHandler==null || !(_contextHandler instanceof ServletContextHandler))
177             initialize();
178     }   
179     
180     /* ----------------------------------------------------------------- */
181     @Override
182     protected synchronized void doStop()
183         throws Exception
184     {
185         super.doStop();
186         
187         // Stop filters
188         if (_filters!=null)
189         {
190             for (int i=_filters.length; i-->0;)
191             {
192                 try { _filters[i].stop(); }catch(Exception e){LOG.warn(Log.EXCEPTION,e);}
193             }
194         }
195         
196         // Stop servlets
197         if (_servlets!=null)
198         {
199             for (int i=_servlets.length; i-->0;)
200             {
201                 try { _servlets[i].stop(); }catch(Exception e){LOG.warn(Log.EXCEPTION,e);}
202             }
203         }
204 
205         _filterPathMappings=null;
206         _filterNameMappings=null;
207         
208         _servletPathMap=null;
209     }
210 
211     /* ------------------------------------------------------------ */
212     IdentityService getIdentityService()
213     {
214         return _identityService;
215     }
216     
217     /* ------------------------------------------------------------ */
218     /**
219      * @return Returns the contextLog.
220      */
221     public Object getContextLog()
222     {
223         return null;
224     }
225     
226     /* ------------------------------------------------------------ */
227     /**
228      * @return Returns the filterMappings.
229      */
230     public FilterMapping[] getFilterMappings()
231     {
232         return _filterMappings;
233     }
234     
235     /* ------------------------------------------------------------ */
236     /** Get Filters.
237      * @return Array of defined servlets
238      */
239     public FilterHolder[] getFilters()
240     {
241         return _filters;
242     }
243     
244     /* ------------------------------------------------------------ */
245     /** ServletHolder matching path.
246      * @param pathInContext Path within _context.
247      * @return PathMap Entries pathspec to ServletHolder
248      */
249     public PathMap.Entry getHolderEntry(String pathInContext)
250     {
251         if (_servletPathMap==null)
252             return null;
253         return _servletPathMap.getMatch(pathInContext);
254     }
255 
256     /* ------------------------------------------------------------ */
257     /**
258      * @param uriInContext uri to get dispatcher for
259      * @return A {@link RequestDispatcher dispatcher} wrapping the resource at <code>uriInContext</code>,
260      *  or <code>null</code> if the specified uri cannot be dispatched to.
261      */
262     public RequestDispatcher getRequestDispatcher(String uriInContext)
263     {
264         if (uriInContext == null || _contextHandler==null)
265             return null;
266 
267         if (!uriInContext.startsWith("/"))
268             return null;
269         
270         try
271         {
272             String query=null;
273             int q;
274             if ((q=uriInContext.indexOf('?'))>0)
275             {
276                 query=uriInContext.substring(q+1);
277                 uriInContext=uriInContext.substring(0,q);
278             }
279             if ((q=uriInContext.indexOf(';'))>0)
280                 uriInContext=uriInContext.substring(0,q);
281 
282             String pathInContext=URIUtil.canonicalPath(URIUtil.decodePath(uriInContext));
283             String uri=URIUtil.addPaths(_contextHandler.getContextPath(), uriInContext);
284             return new Dispatcher(_contextHandler, uri, pathInContext, query);
285         }
286         catch(Exception e)
287         {
288             LOG.ignore(e);
289         }
290         return null;
291     }
292 
293     /* ------------------------------------------------------------ */
294     public ServletContext getServletContext()
295     {
296         return _servletContext;
297     }
298     
299     /* ------------------------------------------------------------ */
300     /**
301      * @return Returns the servletMappings.
302      */
303     public ServletMapping[] getServletMappings()
304     {
305         return _servletMappings;
306     }
307     
308     /* ------------------------------------------------------------ */
309     /**
310      * @return Returns the servletMappings.
311      */
312     public ServletMapping getServletMapping(String pattern)
313     {
314         if (_servletMappings!=null)
315         {
316             for (ServletMapping m:_servletMappings)
317             {
318                 String[] paths=m.getPathSpecs();
319                 if (paths!=null)
320                 {
321                     for (String path:paths)
322                     {
323                         if (pattern.equals(path))
324                             return m;
325                     }
326                 }
327             }
328         }
329         return null;
330     }
331         
332     /* ------------------------------------------------------------ */
333     /** Get Servlets.
334      * @return Array of defined servlets
335      */
336     public ServletHolder[] getServlets()
337     {
338         return _servlets;
339     }
340 
341     /* ------------------------------------------------------------ */
342     public ServletHolder getServlet(String name)
343     {
344         return (ServletHolder)_servletNameMap.get(name);
345     }
346 
347     /* ------------------------------------------------------------ */
348     @Override
349     public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
350     {
351         // Get the base requests
352         final String old_servlet_path=baseRequest.getServletPath();
353         final String old_path_info=baseRequest.getPathInfo();
354 
355         DispatcherType type = baseRequest.getDispatcherType();
356        
357         ServletHolder servlet_holder=null;
358         UserIdentity.Scope old_scope=null;
359 
360         // find the servlet
361         if (target.startsWith("/"))
362         {
363             // Look for the servlet by path
364             PathMap.Entry entry=getHolderEntry(target);
365             if (entry!=null)
366             {
367                 servlet_holder=(ServletHolder)entry.getValue();
368 
369                 String servlet_path_spec=(String)entry.getKey(); 
370                 String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target);
371                 String path_info=PathMap.pathInfo(servlet_path_spec,target);
372 
373                 if (DispatcherType.INCLUDE.equals(type))
374                 {
375                     baseRequest.setAttribute(Dispatcher.INCLUDE_SERVLET_PATH,servlet_path);
376                     baseRequest.setAttribute(Dispatcher.INCLUDE_PATH_INFO, path_info);
377                 }
378                 else
379                 {
380                     baseRequest.setServletPath(servlet_path);
381                     baseRequest.setPathInfo(path_info);
382                 }
383             }      
384         }
385         else
386         {
387             // look for a servlet by name!
388             servlet_holder=(ServletHolder)_servletNameMap.get(target);
389         }
390 
391         if (LOG.isDebugEnabled())
392             LOG.debug("servlet {}|{}|{} -> {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),servlet_holder);
393 
394         try
395         {
396             // Do the filter/handling thang
397             old_scope=baseRequest.getUserIdentityScope();
398             baseRequest.setUserIdentityScope(servlet_holder);
399 
400             // start manual inline of nextScope(target,baseRequest,request,response);
401             if (never())
402                 nextScope(target,baseRequest,request,response);
403             else if (_nextScope!=null)
404                 _nextScope.doScope(target,baseRequest,request, response);
405             else if (_outerScope!=null)
406                 _outerScope.doHandle(target,baseRequest,request, response);
407             else 
408                 doHandle(target,baseRequest,request, response);
409             // end manual inline (pathentic attempt to reduce stack depth)
410         }
411         finally
412         {
413             if (old_scope!=null)
414                 baseRequest.setUserIdentityScope(old_scope);
415 
416             if (!(DispatcherType.INCLUDE.equals(type)))
417             {
418                 baseRequest.setServletPath(old_servlet_path);
419                 baseRequest.setPathInfo(old_path_info); 
420             }
421         }
422     }
423     
424     /* ------------------------------------------------------------ */
425     /* 
426      * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
427      */
428     @Override
429     public void doHandle(String target, Request baseRequest,HttpServletRequest request, HttpServletResponse response)
430         throws IOException, ServletException
431     {
432         DispatcherType type = baseRequest.getDispatcherType();
433         
434         ServletHolder servlet_holder=(ServletHolder) baseRequest.getUserIdentityScope();
435         FilterChain chain=null;
436 
437         // find the servlet
438         if (target.startsWith("/"))
439         {
440             if (servlet_holder!=null && _filterMappings!=null && _filterMappings.length>0)
441                 chain=getFilterChain(baseRequest, target, servlet_holder);
442         }
443         else
444         {
445             if (servlet_holder!=null)
446             {
447                 if (_filterMappings!=null && _filterMappings.length>0)
448                 {
449                     chain=getFilterChain(baseRequest, null,servlet_holder);
450                 }
451             }
452         }
453 
454         LOG.debug("chain={}",chain);
455         
456         try
457         {
458             if (servlet_holder==null)
459             {
460                 if (getHandler()==null)
461                     notFound(request, response);
462                 else
463                     nextHandle(target,baseRequest,request,response);
464             }
465             else
466             {
467                 // unwrap any tunnelling of base Servlet request/responses
468                 ServletRequest req = request;
469                 if (req instanceof ServletRequestHttpWrapper)
470                     req = ((ServletRequestHttpWrapper)req).getRequest();
471                 ServletResponse res = response;
472                 if (res instanceof ServletResponseHttpWrapper)
473                     res = ((ServletResponseHttpWrapper)res).getResponse();
474 
475                 // Do the filter/handling thang
476                 if (chain!=null)
477                     chain.doFilter(req, res);
478                 else 
479                     servlet_holder.handle(baseRequest,req,res);
480             }
481         }
482         catch(EofException e)
483         {
484             throw e;
485         }
486         catch(RuntimeIOException e)
487         {
488             throw e;
489         }
490         catch(ContinuationThrowable e)
491         {   
492             throw e;
493         }
494         catch(Exception e)
495         {
496             if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
497             {
498                 if (e instanceof IOException)
499                     throw (IOException)e;
500                 if (e instanceof RuntimeException)
501                     throw (RuntimeException)e;
502                 if (e instanceof ServletException)
503                     throw (ServletException)e;
504             }
505 
506             // unwrap cause
507             Throwable th=e;
508             if (th instanceof UnavailableException)
509             {
510                 LOG.debug(th); 
511             }
512             else if (th instanceof ServletException)
513             {
514                 LOG.debug(th);
515                 Throwable cause=((ServletException)th).getRootCause();
516                 if (cause!=null)
517                     th=cause;
518             }
519             else if (th instanceof RuntimeIOException)
520             {
521                 LOG.debug(th);
522                 Throwable cause=(IOException)((RuntimeIOException)th).getCause();
523                 if (cause!=null)
524                     th=cause;
525             }
526 
527             // handle or log exception
528             if (th instanceof HttpException)
529                 throw (HttpException)th;
530             else if (th instanceof RuntimeIOException)
531                 throw (RuntimeIOException)th;
532             else if (th instanceof EofException)
533                 throw (EofException)th;
534 
535             else if (LOG.isDebugEnabled())
536             {
537                 LOG.warn(request.getRequestURI(), th); 
538                 LOG.debug(request.toString()); 
539             }
540             else if (th instanceof IOException || th instanceof UnavailableException)
541             {
542                 LOG.debug(request.getRequestURI(),th);
543             }
544             else
545             {
546                 LOG.warn(request.getRequestURI(),th);
547             }
548 
549             if (!response.isCommitted())
550             {
551                 request.setAttribute(Dispatcher.ERROR_EXCEPTION_TYPE,th.getClass());
552                 request.setAttribute(Dispatcher.ERROR_EXCEPTION,th);
553                 if (th instanceof UnavailableException)
554                 {
555                     UnavailableException ue = (UnavailableException)th;
556                     if (ue.isPermanent())
557                         response.sendError(HttpServletResponse.SC_NOT_FOUND,th.getMessage());
558                     else
559                         response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,th.getMessage());
560                 }
561                 else
562                     response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,th.getMessage());
563             }
564             else
565                 LOG.debug("Response already committed for handling "+th);
566         }
567         catch(Error e)
568         {   
569             if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
570                 throw e;
571             LOG.warn("Error for "+request.getRequestURI(),e);
572             if(LOG.isDebugEnabled())LOG.debug(request.toString());
573 
574             // TODO httpResponse.getHttpConnection().forceClose();
575             if (!response.isCommitted())
576             {
577                 request.setAttribute(Dispatcher.ERROR_EXCEPTION_TYPE,e.getClass());
578                 request.setAttribute(Dispatcher.ERROR_EXCEPTION,e);
579                 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,e.getMessage());
580             }
581             else
582                 LOG.debug("Response already committed for handling ",e);
583         }
584         finally
585         {
586             if (servlet_holder!=null)
587                 baseRequest.setHandled(true);
588         }
589     }
590 
591     /* ------------------------------------------------------------ */
592     private FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder) 
593     {
594         String key=pathInContext==null?servletHolder.getName():pathInContext;
595         int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType());
596         
597         if (_filterChainsCached && _chainCache!=null)
598         {
599             FilterChain chain = (FilterChain)_chainCache[dispatch].get(key);
600             if (chain!=null)
601                 return chain;
602         }
603         
604         // Build list of filters
605         Object filters= null;
606         // Path filters
607         if (pathInContext!=null && _filterPathMappings!=null)
608         {
609             for (int i= 0; i < _filterPathMappings.size(); i++)
610             {
611                 FilterMapping mapping = (FilterMapping)_filterPathMappings.get(i);
612                 if (mapping.appliesTo(pathInContext, dispatch))
613                     filters= LazyList.add(filters, mapping.getFilterHolder());
614             }
615         }
616 
617         // Servlet name filters
618         if (servletHolder != null && _filterNameMappings!=null && _filterNameMappings.size() > 0)
619         {
620             // Servlet name filters
621             if (_filterNameMappings.size() > 0)
622             {
623                 Object o= _filterNameMappings.get(servletHolder.getName());
624                 for (int i=0; i<LazyList.size(o);i++)
625                 {
626                     FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
627                     if (mapping.appliesTo(dispatch))
628                         filters=LazyList.add(filters,mapping.getFilterHolder());
629                 }
630                 
631                 o= _filterNameMappings.get("*");
632                 for (int i=0; i<LazyList.size(o);i++)
633                 {
634                     FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
635                     if (mapping.appliesTo(dispatch))
636                         filters=LazyList.add(filters,mapping.getFilterHolder());
637                 }
638             }
639         }
640         
641         if (filters==null)
642             return null;
643         
644         
645         FilterChain chain = null;
646         if (_filterChainsCached)
647         {
648             if (LazyList.size(filters) > 0)
649                 chain= new CachedChain(filters, servletHolder);
650 
651             final Map<String,FilterChain> cache=_chainCache[dispatch];
652             final Queue<String> lru=_chainLRU[dispatch];
653 
654         	// Do we have too many cached chains?
655         	while (_maxFilterChainsCacheSize>0 && cache.size()>=_maxFilterChainsCacheSize)
656         	{
657         	    // The LRU list is not atomic with the cache map, so be prepared to invalidate if 
658         	    // a key is not found to delete.
659         	    // Delete by LRU (where U==created)
660         	    String k=lru.poll();
661         	    if (k==null)
662         	    {
663         	        cache.clear();
664         	        break;
665         	    }
666         	    cache.remove(k);
667         	}
668         	
669         	cache.put(key,chain);
670         	lru.add(key);
671         }
672         else if (LazyList.size(filters) > 0)
673             chain = new Chain(baseRequest,filters, servletHolder);
674     
675         return chain;
676     }
677     
678     /* ------------------------------------------------------------ */
679     private void invalidateChainsCache()
680     {
681         if (_chainLRU[FilterMapping.REQUEST]!=null)
682         {
683             _chainLRU[FilterMapping.REQUEST].clear();
684             _chainLRU[FilterMapping.FORWARD].clear();
685             _chainLRU[FilterMapping.INCLUDE].clear();
686             _chainLRU[FilterMapping.ERROR].clear();
687             _chainLRU[FilterMapping.ASYNC].clear();
688 
689             _chainCache[FilterMapping.REQUEST].clear();
690             _chainCache[FilterMapping.FORWARD].clear();
691             _chainCache[FilterMapping.INCLUDE].clear();
692             _chainCache[FilterMapping.ERROR].clear();
693             _chainCache[FilterMapping.ASYNC].clear();
694         }
695     }
696 
697     /* ------------------------------------------------------------ */
698     /**
699      * @return true if the handler is started and there are no unavailable servlets 
700      */
701     public boolean isAvailable()
702     {
703         if (!isStarted())
704             return false;
705         ServletHolder[] holders = getServlets();
706         for (int i=0;i<holders.length;i++)
707         {
708             ServletHolder holder = holders[i];
709             if (holder!=null && !holder.isAvailable())
710                 return false;
711         }
712         return true;
713     }
714     
715     /* ------------------------------------------------------------ */
716     /**
717      * @param start True if this handler will start with unavailable servlets
718      */
719     public void setStartWithUnavailable(boolean start)
720     {
721         _startWithUnavailable=start;
722     }
723     
724     /* ------------------------------------------------------------ */
725     /**
726      * @return True if this handler will start with unavailable servlets
727      */
728     public boolean isStartWithUnavailable()
729     {
730         return _startWithUnavailable;
731     }
732     
733     
734     
735     /* ------------------------------------------------------------ */
736     /** Initialize filters and load-on-startup servlets.
737      * Called automatically from start if autoInitializeServlet is true.
738      */
739     public void initialize()
740         throws Exception
741     {
742         MultiException mx = new MultiException();
743 
744         // Start filters
745         if (_filters!=null)
746         {
747             for (int i=0;i<_filters.length; i++)
748                 _filters[i].start();
749         }
750         
751         if (_servlets!=null)
752         {
753             // Sort and Initialize servlets
754             ServletHolder[] servlets = (ServletHolder[])_servlets.clone();
755             Arrays.sort(servlets);
756             for (int i=0; i<servlets.length; i++)
757             {
758                 try
759                 {
760                     if (servlets[i].getClassName()==null && servlets[i].getForcedPath()!=null)
761                     {
762                         ServletHolder forced_holder = (ServletHolder)_servletPathMap.match(servlets[i].getForcedPath());
763                         if (forced_holder==null || forced_holder.getClassName()==null)
764                         {    
765                             mx.add(new IllegalStateException("No forced path servlet for "+servlets[i].getForcedPath()));
766                             continue;
767                         }
768                         servlets[i].setClassName(forced_holder.getClassName());
769                     }
770                     
771                     servlets[i].start();
772                 }
773                 catch(Throwable e)
774                 {
775                     LOG.debug(Log.EXCEPTION,e);
776                     mx.add(e);
777                 }
778             } 
779             mx.ifExceptionThrow();  
780         }
781     }
782     
783     /* ------------------------------------------------------------ */
784     /**
785      * @return Returns the filterChainsCached.
786      */
787     public boolean isFilterChainsCached()
788     {
789         return _filterChainsCached;
790     }
791 
792     /* ------------------------------------------------------------ */
793     /**
794      * see also newServletHolder(Class)
795      */
796     public ServletHolder newServletHolder()
797     {
798         return new ServletHolder();
799     }
800     
801     /* ------------------------------------------------------------ */
802     public ServletHolder newServletHolder(Class<? extends Servlet> servlet)
803     {
804         return new ServletHolder(servlet);
805     }
806     
807     /* ------------------------------------------------------------ */
808     /** Convenience method to add a servlet.
809      * @return The servlet holder.
810      */
811     public ServletHolder addServletWithMapping (String className,String pathSpec)
812     {
813         ServletHolder holder = newServletHolder(null);
814         holder.setName(className+"-"+LazyList.size(_servlets));
815         holder.setClassName(className);
816         addServletWithMapping(holder,pathSpec);
817         return holder;
818     }   
819     
820     /* ------------------------------------------------------------ */
821     /** conveniance method to add a servlet.
822      * @return The servlet holder.
823      */
824     public ServletHolder addServletWithMapping (Class<? extends Servlet> servlet,String pathSpec)
825     {
826         ServletHolder holder = newServletHolder();
827         holder.setHeldClass(servlet);
828         setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class));
829         addServletWithMapping(holder,pathSpec);
830         
831         return holder;
832     }   
833     
834     /* ------------------------------------------------------------ */
835     /** conveniance method to add a servlet.
836      * @param servlet servlet holder to add
837      * @param pathSpec servlet mappings for the servletHolder
838      */
839     public void addServletWithMapping (ServletHolder servlet,String pathSpec)
840     {
841         ServletHolder[] holders=getServlets();
842         if (holders!=null)
843             holders = holders.clone();
844         
845         try
846         {
847             setServlets((ServletHolder[])LazyList.addToArray(holders, servlet, ServletHolder.class));
848             
849             ServletMapping mapping = new ServletMapping();
850             mapping.setServletName(servlet.getName());
851             mapping.setPathSpec(pathSpec);
852             setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class));
853         }
854         catch (Exception e)
855         {
856             setServlets(holders);
857             if (e instanceof RuntimeException)
858                 throw (RuntimeException)e;
859             throw new RuntimeException(e);
860         }
861     }
862 
863     
864     /* ------------------------------------------------------------ */    
865     /**Convenience method to add a pre-constructed ServletHolder.
866      * @param holder
867      */
868     public void addServlet(ServletHolder holder)
869     {
870         setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class));
871     }
872     
873     /* ------------------------------------------------------------ */    
874     /** Convenience method to add a pre-constructed ServletMapping.
875      * @param mapping
876      */
877     public void addServletMapping (ServletMapping mapping)
878     {
879         setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class));
880     }
881     
882     /* ------------------------------------------------------------ */
883     public FilterHolder newFilterHolder(Class<? extends Filter> filter)
884     {
885         return new FilterHolder(filter);
886     }
887     
888     /* ------------------------------------------------------------ */
889     /** 
890      * @see #newFilterHolder(Class)
891      */
892     public FilterHolder newFilterHolder()
893     {
894         return new FilterHolder();
895     }
896 
897 
898     /* ------------------------------------------------------------ */
899     public FilterHolder getFilter(String name)
900     {
901         return (FilterHolder)_filterNameMap.get(name);
902     }
903 
904     
905     /* ------------------------------------------------------------ */
906     /** Convenience method to add a filter.
907      * @param filter  class of filter to create
908      * @param pathSpec filter mappings for filter
909      * @param dispatches see {@link FilterMapping#setDispatches(int)}
910      * @return The filter holder.
911      */
912     public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,EnumSet<DispatcherType> dispatches)
913     {
914         FilterHolder holder = newFilterHolder();
915         holder.setHeldClass(filter);
916         addFilterWithMapping(holder,pathSpec,dispatches);
917         
918         return holder;
919     }
920     
921     /* ------------------------------------------------------------ */
922     /** Convenience method to add a filter.
923      * @param className of filter
924      * @param pathSpec filter mappings for filter
925      * @param dispatches see {@link FilterMapping#setDispatches(int)}
926      * @return The filter holder.
927      */
928     public FilterHolder addFilterWithMapping (String className,String pathSpec,EnumSet<DispatcherType> dispatches)
929     {
930         FilterHolder holder = newFilterHolder();
931         holder.setName(className+"-"+_filters.length);
932         holder.setClassName(className);
933         
934         addFilterWithMapping(holder,pathSpec,dispatches);
935         return holder;
936     }
937     
938     /* ------------------------------------------------------------ */
939     /** Convenience method to add a filter.
940      * @param holder filter holder to add
941      * @param pathSpec filter mappings for filter
942      * @param dispatches see {@link FilterMapping#setDispatches(int)}
943      */
944     public void addFilterWithMapping (FilterHolder holder,String pathSpec,EnumSet<DispatcherType> dispatches)
945     {
946         FilterHolder[] holders = getFilters();
947         if (holders!=null)
948             holders = (FilterHolder[])holders.clone();
949         
950         try
951         {
952             setFilters((FilterHolder[])LazyList.addToArray(holders, holder, FilterHolder.class));
953             
954             FilterMapping mapping = new FilterMapping();
955             mapping.setFilterName(holder.getName());
956             mapping.setPathSpec(pathSpec);
957             mapping.setDispatcherTypes(dispatches);
958             setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class));
959         }
960         catch (RuntimeException e)
961         {
962             setFilters(holders);
963             throw e;
964         }
965         catch (Error e)
966         {
967             setFilters(holders);
968             throw e;
969         }
970             
971     }
972     
973     
974     /* ------------------------------------------------------------ */
975     /** Convenience method to add a filter.
976      * @param filter  class of filter to create
977      * @param pathSpec filter mappings for filter
978      * @param dispatches see {@link FilterMapping#setDispatches(int)}
979      * @return The filter holder.
980      */
981     public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,int dispatches)
982     {
983         FilterHolder holder = newFilterHolder(filter);
984         addFilterWithMapping(holder,pathSpec,dispatches);
985         
986         return holder;
987     }
988     
989     /* ------------------------------------------------------------ */
990     /** Convenience method to add a filter.
991      * @param className of filter
992      * @param pathSpec filter mappings for filter
993      * @param dispatches see {@link FilterMapping#setDispatches(int)}
994      * @return The filter holder.
995      */
996     public FilterHolder addFilterWithMapping (String className,String pathSpec,int dispatches)
997     {
998         FilterHolder holder = newFilterHolder(null);
999         holder.setName(className+"-"+_filters.length);
1000         holder.setClassName(className);
1001         
1002         addFilterWithMapping(holder,pathSpec,dispatches);
1003         return holder;
1004     }
1005     
1006     /* ------------------------------------------------------------ */
1007     /** Convenience method to add a filter.
1008      * @param holder filter holder to add
1009      * @param pathSpec filter mappings for filter
1010      * @param dispatches see {@link FilterMapping#setDispatches(int)}
1011      */
1012     public void addFilterWithMapping (FilterHolder holder,String pathSpec,int dispatches)
1013     {
1014         FilterHolder[] holders = getFilters();
1015         if (holders!=null)
1016             holders = (FilterHolder[])holders.clone();
1017         
1018         try
1019         {
1020             setFilters((FilterHolder[])LazyList.addToArray(holders, holder, FilterHolder.class));
1021             
1022             FilterMapping mapping = new FilterMapping();
1023             mapping.setFilterName(holder.getName());
1024             mapping.setPathSpec(pathSpec);
1025             mapping.setDispatches(dispatches);
1026             setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class));
1027         }
1028         catch (RuntimeException e)
1029         {
1030             setFilters(holders);
1031             throw e;
1032         }
1033         catch (Error e)
1034         {
1035             setFilters(holders);
1036             throw e;
1037         }
1038             
1039     }
1040     
1041     
1042     /* ------------------------------------------------------------ */
1043     /** Convenience method to add a filter with a mapping
1044      * @param className
1045      * @param pathSpec
1046      * @param dispatches
1047      * @return the filter holder created
1048      * @deprecated use {@link #addFilterWithMapping(Class, String, int)} instead
1049      */
1050     public FilterHolder addFilter (String className,String pathSpec,EnumSet<DispatcherType> dispatches)
1051     {
1052         return addFilterWithMapping(className, pathSpec, dispatches);
1053     }
1054     
1055     /* ------------------------------------------------------------ */
1056     /**
1057      * convenience method to add a filter and mapping
1058      * @param filter
1059      * @param filterMapping
1060      */
1061     public void addFilter (FilterHolder filter, FilterMapping filterMapping)
1062     {
1063         if (filter != null)
1064             setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class));
1065         if (filterMapping != null)
1066             setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), filterMapping, FilterMapping.class));
1067     }
1068     
1069     /* ------------------------------------------------------------ */  
1070     /** Convenience method to add a preconstructed FilterHolder
1071      * @param filter
1072      */
1073     public void addFilter (FilterHolder filter)
1074     {
1075         if (filter != null)
1076             setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class));
1077     }
1078     
1079     /* ------------------------------------------------------------ */
1080     /** Convenience method to add a preconstructed FilterMapping
1081      * @param mapping
1082      */
1083     public void addFilterMapping (FilterMapping mapping)
1084     {
1085         if (mapping != null)
1086             setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class));
1087     }
1088     
1089     /* ------------------------------------------------------------ */
1090     /** Convenience method to add a preconstructed FilterMapping
1091      * @param mapping
1092      */
1093     public void prependFilterMapping (FilterMapping mapping)
1094     {
1095         if (mapping != null)
1096         {
1097             FilterMapping[] mappings =getFilterMappings();
1098             if (mappings==null || mappings.length==0)
1099                 setFilterMappings(new FilterMapping[] {mapping});
1100             else
1101             {
1102 
1103                 FilterMapping[] new_mappings=new FilterMapping[mappings.length+1];
1104                 System.arraycopy(mappings,0,new_mappings,1,mappings.length);
1105                 new_mappings[0]=mapping;
1106                 setFilterMappings(new_mappings);
1107             }
1108         }
1109     }
1110 
1111     /* ------------------------------------------------------------ */
1112     protected synchronized void updateNameMappings()
1113     {   
1114         // update filter name map
1115         _filterNameMap.clear();
1116         if (_filters!=null)
1117         {   
1118             for (int i=0;i<_filters.length;i++)
1119             {
1120                 _filterNameMap.put(_filters[i].getName(),_filters[i]);
1121                 _filters[i].setServletHandler(this);
1122             }
1123         }
1124 
1125         // Map servlet names to holders
1126         _servletNameMap.clear();
1127         if (_servlets!=null)
1128         {   
1129             // update the maps
1130             for (int i=0;i<_servlets.length;i++)
1131             {
1132                 _servletNameMap.put(_servlets[i].getName(),_servlets[i]);
1133                 _servlets[i].setServletHandler(this);
1134             }
1135         }
1136     }
1137     
1138     /* ------------------------------------------------------------ */
1139     protected synchronized void updateMappings()
1140     {   
1141         // update filter mappings
1142         if (_filterMappings==null)
1143         {
1144             _filterPathMappings=null;
1145             _filterNameMappings=null;
1146         }
1147         else 
1148         {
1149             _filterPathMappings=new ArrayList();
1150             _filterNameMappings=new MultiMap();
1151             for (int i=0;i<_filterMappings.length;i++)
1152             {
1153                 FilterHolder filter_holder = (FilterHolder)_filterNameMap.get(_filterMappings[i].getFilterName());
1154                 if (filter_holder==null)
1155                     throw new IllegalStateException("No filter named "+_filterMappings[i].getFilterName());
1156                 _filterMappings[i].setFilterHolder(filter_holder);    
1157                 if (_filterMappings[i].getPathSpecs()!=null)
1158                     _filterPathMappings.add(_filterMappings[i]);
1159                 
1160                 if (_filterMappings[i].getServletNames()!=null)
1161                 {
1162                     String[] names=_filterMappings[i].getServletNames();
1163                     for (int j=0;j<names.length;j++)
1164                     {
1165                         if (names[j]!=null)
1166                             _filterNameMappings.add(names[j], _filterMappings[i]);  
1167                     }
1168                 }
1169             }
1170         }
1171 
1172         // Map servlet paths to holders
1173         if (_servletMappings==null || _servletNameMap==null)
1174         {
1175             _servletPathMap=null;
1176         }
1177         else
1178         {
1179             PathMap pm = new PathMap();
1180             
1181             // update the maps
1182             for (int i=0;i<_servletMappings.length;i++)
1183             {
1184                 ServletHolder servlet_holder = (ServletHolder)_servletNameMap.get(_servletMappings[i].getServletName());
1185                 if (servlet_holder==null)
1186                     throw new IllegalStateException("No such servlet: "+_servletMappings[i].getServletName());
1187                 else if (_servletMappings[i].getPathSpecs()!=null)
1188                 {
1189                     String[] pathSpecs = _servletMappings[i].getPathSpecs();
1190                     for (int j=0;j<pathSpecs.length;j++)
1191                         if (pathSpecs[j]!=null)
1192                             pm.put(pathSpecs[j],servlet_holder);
1193                 }
1194             }
1195             
1196             _servletPathMap=pm;
1197         }
1198         
1199         // flush filter chain cache
1200         if (_chainCache!=null)
1201         {
1202             for (int i=_chainCache.length;i-->0;)
1203             {
1204                 if (_chainCache[i]!=null)
1205                     _chainCache[i].clear();
1206             }
1207         }
1208 
1209         if (LOG.isDebugEnabled()) 
1210         {
1211             LOG.debug("filterNameMap="+_filterNameMap);
1212             LOG.debug("pathFilters="+_filterPathMappings);
1213             LOG.debug("servletFilterMap="+_filterNameMappings);
1214             LOG.debug("servletPathMap="+_servletPathMap);
1215             LOG.debug("servletNameMap="+_servletNameMap);
1216         }
1217         
1218         try
1219         {
1220             if (isStarted())
1221                 initialize();
1222         }
1223         catch (Exception e)
1224         {
1225             throw new RuntimeException(e);
1226         }
1227     }
1228 
1229     /* ------------------------------------------------------------ */
1230     protected void notFound(HttpServletRequest request,
1231                   HttpServletResponse response)
1232         throws IOException
1233     {
1234         if(LOG.isDebugEnabled())
1235             LOG.debug("Not Found "+request.getRequestURI());
1236         //Override to send an error back, eg with: response.sendError(HttpServletResponse.SC_NOT_FOUND);
1237     }
1238     
1239     /* ------------------------------------------------------------ */
1240     /**
1241      * @param filterChainsCached The filterChainsCached to set.
1242      */
1243     public void setFilterChainsCached(boolean filterChainsCached)
1244     {
1245         _filterChainsCached = filterChainsCached;
1246     }
1247     
1248     /* ------------------------------------------------------------ */
1249     /**
1250      * @param filterMappings The filterMappings to set.
1251      */
1252     public void setFilterMappings(FilterMapping[] filterMappings)
1253     {
1254         if (getServer()!=null)
1255             getServer().getContainer().update(this,_filterMappings,filterMappings,"filterMapping",true);
1256         _filterMappings = filterMappings;
1257         updateMappings();
1258         invalidateChainsCache();
1259     }
1260     
1261     /* ------------------------------------------------------------ */
1262     public synchronized void setFilters(FilterHolder[] holders)
1263     {
1264         if (getServer()!=null)
1265             getServer().getContainer().update(this,_filters,holders,"filter",true);
1266         _filters=holders;
1267         updateNameMappings();
1268         invalidateChainsCache();
1269     }
1270     
1271     /* ------------------------------------------------------------ */
1272     /**
1273      * @param servletMappings The servletMappings to set.
1274      */
1275     public void setServletMappings(ServletMapping[] servletMappings)
1276     {
1277         if (getServer()!=null)
1278             getServer().getContainer().update(this,_servletMappings,servletMappings,"servletMapping",true);
1279         _servletMappings = servletMappings;
1280         updateMappings();
1281         invalidateChainsCache();
1282     }
1283     
1284     /* ------------------------------------------------------------ */
1285     /** Set Servlets.
1286      * @param holders Array of servletsto define
1287      */
1288     public synchronized void setServlets(ServletHolder[] holders)
1289     {
1290         if (getServer()!=null)
1291             getServer().getContainer().update(this,_servlets,holders,"servlet",true);
1292         _servlets=holders;
1293         updateNameMappings();
1294         invalidateChainsCache();
1295     }
1296 
1297 
1298     /* ------------------------------------------------------------ */
1299     /* ------------------------------------------------------------ */
1300     private class CachedChain implements FilterChain
1301     {
1302         FilterHolder _filterHolder;
1303         CachedChain _next;
1304         ServletHolder _servletHolder;
1305 
1306         /* ------------------------------------------------------------ */
1307         CachedChain(Object filters, ServletHolder servletHolder)
1308         {
1309             if (LazyList.size(filters)>0)
1310             {
1311                 _filterHolder=(FilterHolder)LazyList.get(filters, 0);
1312                 filters=LazyList.remove(filters,0);
1313                 _next=new CachedChain(filters,servletHolder);
1314             }
1315             else
1316                 _servletHolder=servletHolder;
1317         }
1318 
1319         /* ------------------------------------------------------------ */
1320         public void doFilter(ServletRequest request, ServletResponse response) 
1321             throws IOException, ServletException
1322         {                   
1323             final Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
1324 
1325             // pass to next filter
1326             if (_filterHolder!=null)
1327             {
1328                 if (LOG.isDebugEnabled())
1329                     LOG.debug("call filter " + _filterHolder);
1330                 Filter filter= _filterHolder.getFilter();
1331                 if (_filterHolder.isAsyncSupported())
1332                     filter.doFilter(request, response, _next);
1333                 else
1334                 {
1335                     final boolean suspendable=baseRequest.isAsyncSupported();
1336                     if (suspendable)
1337                     {
1338                         try
1339                         {
1340                             baseRequest.setAsyncSupported(false);
1341                             filter.doFilter(request, response, _next);
1342                         }
1343                         finally
1344                         {
1345                             baseRequest.setAsyncSupported(true);
1346                         }
1347                     }
1348                     else
1349                         filter.doFilter(request, response, _next);
1350                 }
1351                 return;
1352             }
1353 
1354             // Call servlet
1355             
1356             HttpServletRequest srequest = (HttpServletRequest)request;
1357             if (_servletHolder != null)
1358             {
1359                 if (LOG.isDebugEnabled())
1360                     LOG.debug("call servlet " + _servletHolder);
1361                 _servletHolder.handle(baseRequest,request, response);
1362             }
1363             else if (getHandler()==null)
1364                 notFound(srequest, (HttpServletResponse)response);
1365             else
1366                 nextHandle(URIUtil.addPaths(srequest.getServletPath(),srequest.getPathInfo()),
1367                            baseRequest,srequest,(HttpServletResponse)response);
1368             
1369         }
1370         
1371         public String toString()
1372         {
1373             if (_filterHolder!=null)
1374                 return _filterHolder+"->"+_next.toString();
1375             if (_servletHolder!=null)
1376                 return _servletHolder.toString();
1377             return "null";
1378         }
1379     }  
1380     
1381     /* ------------------------------------------------------------ */
1382     /* ------------------------------------------------------------ */
1383     private class Chain implements FilterChain
1384     {
1385         final Request _baseRequest;
1386         final Object _chain;
1387         final ServletHolder _servletHolder;
1388         int _filter= 0;
1389 
1390         /* ------------------------------------------------------------ */
1391         Chain(Request baseRequest, Object filters, ServletHolder servletHolder)
1392         {
1393             _baseRequest=baseRequest;
1394             _chain= filters;
1395             _servletHolder= servletHolder;
1396         }
1397 
1398         /* ------------------------------------------------------------ */
1399         public void doFilter(ServletRequest request, ServletResponse response)
1400             throws IOException, ServletException
1401         {
1402             if (LOG.isDebugEnabled()) 
1403                 LOG.debug("doFilter " + _filter);
1404 
1405             // pass to next filter
1406             if (_filter < LazyList.size(_chain))
1407             {
1408                 FilterHolder holder= (FilterHolder)LazyList.get(_chain, _filter++);
1409                 if (LOG.isDebugEnabled()) 
1410                     LOG.debug("call filter " + holder);
1411                 Filter filter= holder.getFilter();
1412                 
1413                 if (holder.isAsyncSupported() || !_baseRequest.isAsyncSupported())
1414                 {
1415                     filter.doFilter(request, response, this);
1416                 }
1417                 else
1418                 {
1419                     try
1420                     {
1421                         _baseRequest.setAsyncSupported(false);
1422                         filter.doFilter(request, response, this);
1423                     }
1424                     finally
1425                     {
1426                         _baseRequest.setAsyncSupported(true);
1427                     }
1428                 }
1429                     
1430                 return;
1431             }
1432 
1433             // Call servlet
1434             HttpServletRequest srequest = (HttpServletRequest)request;
1435             if (_servletHolder != null)
1436             {
1437                 if (LOG.isDebugEnabled()) 
1438                     LOG.debug("call servlet " + _servletHolder);
1439                 _servletHolder.handle(_baseRequest,request, response);
1440             }
1441             else if (getHandler()==null)
1442                 notFound(srequest, (HttpServletResponse)response);
1443             else
1444             {            
1445                 Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
1446                 nextHandle(URIUtil.addPaths(srequest.getServletPath(),srequest.getPathInfo()),
1447                            baseRequest,srequest,(HttpServletResponse)response);
1448             }
1449         }
1450 
1451         /* ------------------------------------------------------------ */
1452         public String toString()
1453         {
1454             StringBuilder b = new StringBuilder();
1455             for (int i=0; i<LazyList.size(_chain);i++)
1456             {
1457                 Object o=LazyList.get(_chain, i);
1458                 b.append(o.toString());
1459                 b.append("->");
1460             }
1461             b.append(_servletHolder);
1462             return b.toString();
1463         }
1464     }
1465 
1466     /* ------------------------------------------------------------ */
1467     /**
1468      * @return The maximum entries in a filter chain cache.
1469      */
1470     public int getMaxFilterChainsCacheSize()
1471     {
1472         return _maxFilterChainsCacheSize;
1473     }
1474 
1475     /* ------------------------------------------------------------ */
1476     /** Set the maximum filter chain cache size.
1477      * Filter chains are cached if {@link #isFilterChainsCached()} is true. If the max cache size
1478      * is greater than zero, then the cache is flushed whenever it grows to be this size.
1479      * 
1480      * @param maxFilterChainsCacheSize  the maximum number of entries in a filter chain cache.
1481      */
1482     public void setMaxFilterChainsCacheSize(int maxFilterChainsCacheSize)
1483     {
1484         _maxFilterChainsCacheSize = maxFilterChainsCacheSize;
1485     }
1486     
1487     /* ------------------------------------------------------------ */
1488     void destroyServlet(Servlet servlet)
1489     {
1490         if (_contextHandler!=null)
1491             _contextHandler.destroyServlet(servlet);
1492     }
1493 
1494     /* ------------------------------------------------------------ */
1495     void destroyFilter(Filter filter)
1496     {
1497         if (_contextHandler!=null)
1498             _contextHandler.destroyFilter(filter);
1499     }
1500     
1501     /* ------------------------------------------------------------ */
1502     @Override
1503     public void dump(Appendable out,String indent) throws IOException
1504     {
1505         super.dumpThis(out);
1506         dump(out,indent,
1507                 TypeUtil.asList(getHandlers()),
1508                 getBeans(),
1509                 TypeUtil.asList(getFilterMappings()),
1510                 TypeUtil.asList(getFilters()),
1511                 TypeUtil.asList(getServletMappings()),
1512                 TypeUtil.asList(getServlets()));
1513     }
1514 }