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             if (servlet_holder!=null)
398             {
399                 old_scope=baseRequest.getUserIdentityScope();
400                 baseRequest.setUserIdentityScope(servlet_holder);
401 
402                 // start manual inline of nextScope(target,baseRequest,request,response);
403                 if (never())
404                     nextScope(target,baseRequest,request,response);
405                 else if (_nextScope!=null)
406                     _nextScope.doScope(target,baseRequest,request, response);
407                 else if (_outerScope!=null)
408                     _outerScope.doHandle(target,baseRequest,request, response);
409                 else 
410                     doHandle(target,baseRequest,request, response);
411                 // end manual inline (pathentic attempt to reduce stack depth)
412             }
413         }
414         finally
415         {
416             if (old_scope!=null)
417                 baseRequest.setUserIdentityScope(old_scope);
418 
419             if (!(DispatcherType.INCLUDE.equals(type)))
420             {
421                 baseRequest.setServletPath(old_servlet_path);
422                 baseRequest.setPathInfo(old_path_info); 
423             }
424         }
425     }
426     
427     /* ------------------------------------------------------------ */
428     /* 
429      * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
430      */
431     @Override
432     public void doHandle(String target, Request baseRequest,HttpServletRequest request, HttpServletResponse response)
433         throws IOException, ServletException
434     {
435         DispatcherType type = baseRequest.getDispatcherType();
436         
437         ServletHolder servlet_holder=(ServletHolder) baseRequest.getUserIdentityScope();
438         FilterChain chain=null;
439 
440         // find the servlet
441         if (target.startsWith("/"))
442         {
443             if (servlet_holder!=null && _filterMappings!=null && _filterMappings.length>0)
444                 chain=getFilterChain(baseRequest, target, servlet_holder);
445         }
446         else
447         {
448             if (servlet_holder!=null)
449             {
450                 if (_filterMappings!=null && _filterMappings.length>0)
451                 {
452                     chain=getFilterChain(baseRequest, null,servlet_holder);
453                 }
454             }
455         }
456 
457         LOG.debug("chain={}",chain);
458         
459         try
460         {
461             if (servlet_holder==null)
462             {
463                 notFound(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             baseRequest.setHandled(true);
587         }
588     }
589 
590     /* ------------------------------------------------------------ */
591     private FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder) 
592     {
593         String key=pathInContext==null?servletHolder.getName():pathInContext;
594         int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType());
595         
596         if (_filterChainsCached && _chainCache!=null)
597         {
598             FilterChain chain = (FilterChain)_chainCache[dispatch].get(key);
599             if (chain!=null)
600                 return chain;
601         }
602         
603         // Build list of filters
604         Object filters= null;
605         // Path filters
606         if (pathInContext!=null && _filterPathMappings!=null)
607         {
608             for (int i= 0; i < _filterPathMappings.size(); i++)
609             {
610                 FilterMapping mapping = (FilterMapping)_filterPathMappings.get(i);
611                 if (mapping.appliesTo(pathInContext, dispatch))
612                     filters= LazyList.add(filters, mapping.getFilterHolder());
613             }
614         }
615 
616         // Servlet name filters
617         if (servletHolder != null && _filterNameMappings!=null && _filterNameMappings.size() > 0)
618         {
619             // Servlet name filters
620             if (_filterNameMappings.size() > 0)
621             {
622                 Object o= _filterNameMappings.get(servletHolder.getName());
623                 for (int i=0; i<LazyList.size(o);i++)
624                 {
625                     FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
626                     if (mapping.appliesTo(dispatch))
627                         filters=LazyList.add(filters,mapping.getFilterHolder());
628                 }
629                 
630                 o= _filterNameMappings.get("*");
631                 for (int i=0; i<LazyList.size(o);i++)
632                 {
633                     FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
634                     if (mapping.appliesTo(dispatch))
635                         filters=LazyList.add(filters,mapping.getFilterHolder());
636                 }
637             }
638         }
639         
640         if (filters==null)
641             return null;
642         
643         
644         FilterChain chain = null;
645         if (_filterChainsCached)
646         {
647             if (LazyList.size(filters) > 0)
648                 chain= new CachedChain(filters, servletHolder);
649 
650             final Map<String,FilterChain> cache=_chainCache[dispatch];
651             final Queue<String> lru=_chainLRU[dispatch];
652 
653         	// Do we have too many cached chains?
654         	while (_maxFilterChainsCacheSize>0 && cache.size()>=_maxFilterChainsCacheSize)
655         	{
656         	    // The LRU list is not atomic with the cache map, so be prepared to invalidate if 
657         	    // a key is not found to delete.
658         	    // Delete by LRU (where U==created)
659         	    String k=lru.poll();
660         	    if (k==null)
661         	    {
662         	        cache.clear();
663         	        break;
664         	    }
665         	    cache.remove(k);
666         	}
667         	
668         	cache.put(key,chain);
669         	lru.add(key);
670         }
671         else if (LazyList.size(filters) > 0)
672             chain = new Chain(baseRequest,filters, servletHolder);
673     
674         return chain;
675     }
676     
677     /* ------------------------------------------------------------ */
678     private void invalidateChainsCache()
679     {
680         if (_chainLRU[FilterMapping.REQUEST]!=null)
681         {
682             _chainLRU[FilterMapping.REQUEST].clear();
683             _chainLRU[FilterMapping.FORWARD].clear();
684             _chainLRU[FilterMapping.INCLUDE].clear();
685             _chainLRU[FilterMapping.ERROR].clear();
686             _chainLRU[FilterMapping.ASYNC].clear();
687 
688             _chainCache[FilterMapping.REQUEST].clear();
689             _chainCache[FilterMapping.FORWARD].clear();
690             _chainCache[FilterMapping.INCLUDE].clear();
691             _chainCache[FilterMapping.ERROR].clear();
692             _chainCache[FilterMapping.ASYNC].clear();
693         }
694     }
695 
696     /* ------------------------------------------------------------ */
697     /**
698      * @return true if the handler is started and there are no unavailable servlets 
699      */
700     public boolean isAvailable()
701     {
702         if (!isStarted())
703             return false;
704         ServletHolder[] holders = getServlets();
705         for (int i=0;i<holders.length;i++)
706         {
707             ServletHolder holder = holders[i];
708             if (holder!=null && !holder.isAvailable())
709                 return false;
710         }
711         return true;
712     }
713     
714     /* ------------------------------------------------------------ */
715     /**
716      * @param start True if this handler will start with unavailable servlets
717      */
718     public void setStartWithUnavailable(boolean start)
719     {
720         _startWithUnavailable=start;
721     }
722     
723     /* ------------------------------------------------------------ */
724     /**
725      * @return True if this handler will start with unavailable servlets
726      */
727     public boolean isStartWithUnavailable()
728     {
729         return _startWithUnavailable;
730     }
731     
732     
733     
734     /* ------------------------------------------------------------ */
735     /** Initialize filters and load-on-startup servlets.
736      * Called automatically from start if autoInitializeServlet is true.
737      */
738     public void initialize()
739         throws Exception
740     {
741         MultiException mx = new MultiException();
742 
743         // Start filters
744         if (_filters!=null)
745         {
746             for (int i=0;i<_filters.length; i++)
747                 _filters[i].start();
748         }
749         
750         if (_servlets!=null)
751         {
752             // Sort and Initialize servlets
753             ServletHolder[] servlets = (ServletHolder[])_servlets.clone();
754             Arrays.sort(servlets);
755             for (int i=0; i<servlets.length; i++)
756             {
757                 try
758                 {
759                     if (servlets[i].getClassName()==null && servlets[i].getForcedPath()!=null)
760                     {
761                         ServletHolder forced_holder = (ServletHolder)_servletPathMap.match(servlets[i].getForcedPath());
762                         if (forced_holder==null || forced_holder.getClassName()==null)
763                         {    
764                             mx.add(new IllegalStateException("No forced path servlet for "+servlets[i].getForcedPath()));
765                             continue;
766                         }
767                         servlets[i].setClassName(forced_holder.getClassName());
768                     }
769                     
770                     servlets[i].start();
771                 }
772                 catch(Throwable e)
773                 {
774                     LOG.debug(Log.EXCEPTION,e);
775                     mx.add(e);
776                 }
777             } 
778             mx.ifExceptionThrow();  
779         }
780     }
781     
782     /* ------------------------------------------------------------ */
783     /**
784      * @return Returns the filterChainsCached.
785      */
786     public boolean isFilterChainsCached()
787     {
788         return _filterChainsCached;
789     }
790 
791     /* ------------------------------------------------------------ */
792     /**
793      * see also newServletHolder(Class)
794      */
795     public ServletHolder newServletHolder()
796     {
797         return new ServletHolder();
798     }
799     
800     /* ------------------------------------------------------------ */
801     public ServletHolder newServletHolder(Class<? extends Servlet> servlet)
802     {
803         return new ServletHolder(servlet);
804     }
805     
806     /* ------------------------------------------------------------ */
807     /** Convenience method to add a servlet.
808      * @return The servlet holder.
809      */
810     public ServletHolder addServletWithMapping (String className,String pathSpec)
811     {
812         ServletHolder holder = newServletHolder(null);
813         holder.setName(className+"-"+LazyList.size(_servlets));
814         holder.setClassName(className);
815         addServletWithMapping(holder,pathSpec);
816         return holder;
817     }   
818     
819     /* ------------------------------------------------------------ */
820     /** conveniance method to add a servlet.
821      * @return The servlet holder.
822      */
823     public ServletHolder addServletWithMapping (Class<? extends Servlet> servlet,String pathSpec)
824     {
825         ServletHolder holder = newServletHolder();
826         holder.setHeldClass(servlet);
827         setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class));
828         addServletWithMapping(holder,pathSpec);
829         
830         return holder;
831     }   
832     
833     /* ------------------------------------------------------------ */
834     /** conveniance method to add a servlet.
835      * @param servlet servlet holder to add
836      * @param pathSpec servlet mappings for the servletHolder
837      */
838     public void addServletWithMapping (ServletHolder servlet,String pathSpec)
839     {
840         ServletHolder[] holders=getServlets();
841         if (holders!=null)
842             holders = holders.clone();
843         
844         try
845         {
846             setServlets((ServletHolder[])LazyList.addToArray(holders, servlet, ServletHolder.class));
847             
848             ServletMapping mapping = new ServletMapping();
849             mapping.setServletName(servlet.getName());
850             mapping.setPathSpec(pathSpec);
851             setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class));
852         }
853         catch (Exception e)
854         {
855             setServlets(holders);
856             if (e instanceof RuntimeException)
857                 throw (RuntimeException)e;
858             throw new RuntimeException(e);
859         }
860     }
861 
862     
863     /* ------------------------------------------------------------ */    
864     /**Convenience method to add a pre-constructed ServletHolder.
865      * @param holder
866      */
867     public void addServlet(ServletHolder holder)
868     {
869         setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class));
870     }
871     
872     /* ------------------------------------------------------------ */    
873     /** Convenience method to add a pre-constructed ServletMapping.
874      * @param mapping
875      */
876     public void addServletMapping (ServletMapping mapping)
877     {
878         setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class));
879     }
880     
881     /* ------------------------------------------------------------ */
882     public FilterHolder newFilterHolder(Class<? extends Filter> filter)
883     {
884         return new FilterHolder(filter);
885     }
886     
887     /* ------------------------------------------------------------ */
888     /** 
889      * @see #newFilterHolder(Class)
890      */
891     public FilterHolder newFilterHolder()
892     {
893         return new FilterHolder();
894     }
895 
896 
897     /* ------------------------------------------------------------ */
898     public FilterHolder getFilter(String name)
899     {
900         return (FilterHolder)_filterNameMap.get(name);
901     }
902 
903     
904     /* ------------------------------------------------------------ */
905     /** Convenience method to add a filter.
906      * @param filter  class of filter to create
907      * @param pathSpec filter mappings for filter
908      * @param dispatches see {@link FilterMapping#setDispatches(int)}
909      * @return The filter holder.
910      */
911     public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,EnumSet<DispatcherType> dispatches)
912     {
913         FilterHolder holder = newFilterHolder();
914         holder.setHeldClass(filter);
915         addFilterWithMapping(holder,pathSpec,dispatches);
916         
917         return holder;
918     }
919     
920     /* ------------------------------------------------------------ */
921     /** Convenience method to add a filter.
922      * @param className of filter
923      * @param pathSpec filter mappings for filter
924      * @param dispatches see {@link FilterMapping#setDispatches(int)}
925      * @return The filter holder.
926      */
927     public FilterHolder addFilterWithMapping (String className,String pathSpec,EnumSet<DispatcherType> dispatches)
928     {
929         FilterHolder holder = newFilterHolder();
930         holder.setName(className+"-"+_filters.length);
931         holder.setClassName(className);
932         
933         addFilterWithMapping(holder,pathSpec,dispatches);
934         return holder;
935     }
936     
937     /* ------------------------------------------------------------ */
938     /** Convenience method to add a filter.
939      * @param holder filter holder to add
940      * @param pathSpec filter mappings for filter
941      * @param dispatches see {@link FilterMapping#setDispatches(int)}
942      */
943     public void addFilterWithMapping (FilterHolder holder,String pathSpec,EnumSet<DispatcherType> dispatches)
944     {
945         FilterHolder[] holders = getFilters();
946         if (holders!=null)
947             holders = (FilterHolder[])holders.clone();
948         
949         try
950         {
951             setFilters((FilterHolder[])LazyList.addToArray(holders, holder, FilterHolder.class));
952             
953             FilterMapping mapping = new FilterMapping();
954             mapping.setFilterName(holder.getName());
955             mapping.setPathSpec(pathSpec);
956             mapping.setDispatcherTypes(dispatches);
957             setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class));
958         }
959         catch (RuntimeException e)
960         {
961             setFilters(holders);
962             throw e;
963         }
964         catch (Error e)
965         {
966             setFilters(holders);
967             throw e;
968         }
969             
970     }
971     
972     
973     /* ------------------------------------------------------------ */
974     /** Convenience method to add a filter.
975      * @param filter  class of filter to create
976      * @param pathSpec filter mappings for filter
977      * @param dispatches see {@link FilterMapping#setDispatches(int)}
978      * @return The filter holder.
979      */
980     public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,int dispatches)
981     {
982         FilterHolder holder = newFilterHolder(filter);
983         addFilterWithMapping(holder,pathSpec,dispatches);
984         
985         return holder;
986     }
987     
988     /* ------------------------------------------------------------ */
989     /** Convenience method to add a filter.
990      * @param className of filter
991      * @param pathSpec filter mappings for filter
992      * @param dispatches see {@link FilterMapping#setDispatches(int)}
993      * @return The filter holder.
994      */
995     public FilterHolder addFilterWithMapping (String className,String pathSpec,int dispatches)
996     {
997         FilterHolder holder = newFilterHolder(null);
998         holder.setName(className+"-"+_filters.length);
999         holder.setClassName(className);
1000         
1001         addFilterWithMapping(holder,pathSpec,dispatches);
1002         return holder;
1003     }
1004     
1005     /* ------------------------------------------------------------ */
1006     /** Convenience method to add a filter.
1007      * @param holder filter holder to add
1008      * @param pathSpec filter mappings for filter
1009      * @param dispatches see {@link FilterMapping#setDispatches(int)}
1010      */
1011     public void addFilterWithMapping (FilterHolder holder,String pathSpec,int dispatches)
1012     {
1013         FilterHolder[] holders = getFilters();
1014         if (holders!=null)
1015             holders = (FilterHolder[])holders.clone();
1016         
1017         try
1018         {
1019             setFilters((FilterHolder[])LazyList.addToArray(holders, holder, FilterHolder.class));
1020             
1021             FilterMapping mapping = new FilterMapping();
1022             mapping.setFilterName(holder.getName());
1023             mapping.setPathSpec(pathSpec);
1024             mapping.setDispatches(dispatches);
1025             setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class));
1026         }
1027         catch (RuntimeException e)
1028         {
1029             setFilters(holders);
1030             throw e;
1031         }
1032         catch (Error e)
1033         {
1034             setFilters(holders);
1035             throw e;
1036         }
1037             
1038     }
1039     
1040     
1041     /* ------------------------------------------------------------ */
1042     /** Convenience method to add a filter with a mapping
1043      * @param className
1044      * @param pathSpec
1045      * @param dispatches
1046      * @return the filter holder created
1047      * @deprecated use {@link #addFilterWithMapping(Class, String, int)} instead
1048      */
1049     public FilterHolder addFilter (String className,String pathSpec,EnumSet<DispatcherType> dispatches)
1050     {
1051         return addFilterWithMapping(className, pathSpec, dispatches);
1052     }
1053     
1054     /* ------------------------------------------------------------ */
1055     /**
1056      * convenience method to add a filter and mapping
1057      * @param filter
1058      * @param filterMapping
1059      */
1060     public void addFilter (FilterHolder filter, FilterMapping filterMapping)
1061     {
1062         if (filter != null)
1063             setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class));
1064         if (filterMapping != null)
1065             setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), filterMapping, FilterMapping.class));
1066     }
1067     
1068     /* ------------------------------------------------------------ */  
1069     /** Convenience method to add a preconstructed FilterHolder
1070      * @param filter
1071      */
1072     public void addFilter (FilterHolder filter)
1073     {
1074         if (filter != null)
1075             setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class));
1076     }
1077     
1078     /* ------------------------------------------------------------ */
1079     /** Convenience method to add a preconstructed FilterMapping
1080      * @param mapping
1081      */
1082     public void addFilterMapping (FilterMapping mapping)
1083     {
1084         if (mapping != null)
1085             setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class));
1086     }
1087     
1088     /* ------------------------------------------------------------ */
1089     /** Convenience method to add a preconstructed FilterMapping
1090      * @param mapping
1091      */
1092     public void prependFilterMapping (FilterMapping mapping)
1093     {
1094         if (mapping != null)
1095         {
1096             FilterMapping[] mappings =getFilterMappings();
1097             if (mappings==null || mappings.length==0)
1098                 setFilterMappings(new FilterMapping[] {mapping});
1099             else
1100             {
1101 
1102                 FilterMapping[] new_mappings=new FilterMapping[mappings.length+1];
1103                 System.arraycopy(mappings,0,new_mappings,1,mappings.length);
1104                 new_mappings[0]=mapping;
1105                 setFilterMappings(new_mappings);
1106             }
1107         }
1108     }
1109 
1110     /* ------------------------------------------------------------ */
1111     protected synchronized void updateNameMappings()
1112     {   
1113         // update filter name map
1114         _filterNameMap.clear();
1115         if (_filters!=null)
1116         {   
1117             for (int i=0;i<_filters.length;i++)
1118             {
1119                 _filterNameMap.put(_filters[i].getName(),_filters[i]);
1120                 _filters[i].setServletHandler(this);
1121             }
1122         }
1123 
1124         // Map servlet names to holders
1125         _servletNameMap.clear();
1126         if (_servlets!=null)
1127         {   
1128             // update the maps
1129             for (int i=0;i<_servlets.length;i++)
1130             {
1131                 _servletNameMap.put(_servlets[i].getName(),_servlets[i]);
1132                 _servlets[i].setServletHandler(this);
1133             }
1134         }
1135     }
1136     
1137     /* ------------------------------------------------------------ */
1138     protected synchronized void updateMappings()
1139     {   
1140         // update filter mappings
1141         if (_filterMappings==null)
1142         {
1143             _filterPathMappings=null;
1144             _filterNameMappings=null;
1145         }
1146         else 
1147         {
1148             _filterPathMappings=new ArrayList();
1149             _filterNameMappings=new MultiMap();
1150             for (int i=0;i<_filterMappings.length;i++)
1151             {
1152                 FilterHolder filter_holder = (FilterHolder)_filterNameMap.get(_filterMappings[i].getFilterName());
1153                 if (filter_holder==null)
1154                     throw new IllegalStateException("No filter named "+_filterMappings[i].getFilterName());
1155                 _filterMappings[i].setFilterHolder(filter_holder);    
1156                 if (_filterMappings[i].getPathSpecs()!=null)
1157                     _filterPathMappings.add(_filterMappings[i]);
1158                 
1159                 if (_filterMappings[i].getServletNames()!=null)
1160                 {
1161                     String[] names=_filterMappings[i].getServletNames();
1162                     for (int j=0;j<names.length;j++)
1163                     {
1164                         if (names[j]!=null)
1165                             _filterNameMappings.add(names[j], _filterMappings[i]);  
1166                     }
1167                 }
1168             }
1169         }
1170 
1171         // Map servlet paths to holders
1172         if (_servletMappings==null || _servletNameMap==null)
1173         {
1174             _servletPathMap=null;
1175         }
1176         else
1177         {
1178             PathMap pm = new PathMap();
1179             
1180             // update the maps
1181             for (int i=0;i<_servletMappings.length;i++)
1182             {
1183                 ServletHolder servlet_holder = (ServletHolder)_servletNameMap.get(_servletMappings[i].getServletName());
1184                 if (servlet_holder==null)
1185                     throw new IllegalStateException("No such servlet: "+_servletMappings[i].getServletName());
1186                 else if (_servletMappings[i].getPathSpecs()!=null)
1187                 {
1188                     String[] pathSpecs = _servletMappings[i].getPathSpecs();
1189                     for (int j=0;j<pathSpecs.length;j++)
1190                         if (pathSpecs[j]!=null)
1191                             pm.put(pathSpecs[j],servlet_holder);
1192                 }
1193             }
1194             
1195             _servletPathMap=pm;
1196         }
1197         
1198         // flush filter chain cache
1199         if (_chainCache!=null)
1200         {
1201             for (int i=_chainCache.length;i-->0;)
1202             {
1203                 if (_chainCache[i]!=null)
1204                     _chainCache[i].clear();
1205             }
1206         }
1207 
1208         if (LOG.isDebugEnabled()) 
1209         {
1210             LOG.debug("filterNameMap="+_filterNameMap);
1211             LOG.debug("pathFilters="+_filterPathMappings);
1212             LOG.debug("servletFilterMap="+_filterNameMappings);
1213             LOG.debug("servletPathMap="+_servletPathMap);
1214             LOG.debug("servletNameMap="+_servletNameMap);
1215         }
1216         
1217         try
1218         {
1219             if (isStarted())
1220                 initialize();
1221         }
1222         catch (Exception e)
1223         {
1224             throw new RuntimeException(e);
1225         }
1226     }
1227 
1228     /* ------------------------------------------------------------ */
1229     protected void notFound(HttpServletRequest request,
1230                   HttpServletResponse response)
1231         throws IOException
1232     {
1233         if(LOG.isDebugEnabled())
1234             LOG.debug("Not Found "+request.getRequestURI());
1235         response.sendError(HttpServletResponse.SC_NOT_FOUND);
1236     }
1237     
1238     /* ------------------------------------------------------------ */
1239     /**
1240      * @param filterChainsCached The filterChainsCached to set.
1241      */
1242     public void setFilterChainsCached(boolean filterChainsCached)
1243     {
1244         _filterChainsCached = filterChainsCached;
1245     }
1246     
1247     /* ------------------------------------------------------------ */
1248     /**
1249      * @param filterMappings The filterMappings to set.
1250      */
1251     public void setFilterMappings(FilterMapping[] filterMappings)
1252     {
1253         if (getServer()!=null)
1254             getServer().getContainer().update(this,_filterMappings,filterMappings,"filterMapping",true);
1255         _filterMappings = filterMappings;
1256         updateMappings();
1257         invalidateChainsCache();
1258     }
1259     
1260     /* ------------------------------------------------------------ */
1261     public synchronized void setFilters(FilterHolder[] holders)
1262     {
1263         if (getServer()!=null)
1264             getServer().getContainer().update(this,_filters,holders,"filter",true);
1265         _filters=holders;
1266         updateNameMappings();
1267         invalidateChainsCache();
1268     }
1269     
1270     /* ------------------------------------------------------------ */
1271     /**
1272      * @param servletMappings The servletMappings to set.
1273      */
1274     public void setServletMappings(ServletMapping[] servletMappings)
1275     {
1276         if (getServer()!=null)
1277             getServer().getContainer().update(this,_servletMappings,servletMappings,"servletMapping",true);
1278         _servletMappings = servletMappings;
1279         updateMappings();
1280         invalidateChainsCache();
1281     }
1282     
1283     /* ------------------------------------------------------------ */
1284     /** Set Servlets.
1285      * @param holders Array of servletsto define
1286      */
1287     public synchronized void setServlets(ServletHolder[] holders)
1288     {
1289         if (getServer()!=null)
1290             getServer().getContainer().update(this,_servlets,holders,"servlet",true);
1291         _servlets=holders;
1292         updateNameMappings();
1293         invalidateChainsCache();
1294     }
1295 
1296 
1297     /* ------------------------------------------------------------ */
1298     /* ------------------------------------------------------------ */
1299     private class CachedChain implements FilterChain
1300     {
1301         FilterHolder _filterHolder;
1302         CachedChain _next;
1303         ServletHolder _servletHolder;
1304 
1305         /* ------------------------------------------------------------ */
1306         CachedChain(Object filters, ServletHolder servletHolder)
1307         {
1308             if (LazyList.size(filters)>0)
1309             {
1310                 _filterHolder=(FilterHolder)LazyList.get(filters, 0);
1311                 filters=LazyList.remove(filters,0);
1312                 _next=new CachedChain(filters,servletHolder);
1313             }
1314             else
1315                 _servletHolder=servletHolder;
1316         }
1317 
1318         /* ------------------------------------------------------------ */
1319         public void doFilter(ServletRequest request, ServletResponse response) 
1320             throws IOException, ServletException
1321         {
1322             // pass to next filter
1323             if (_filterHolder!=null)
1324             {
1325                 if (LOG.isDebugEnabled())
1326                     LOG.debug("call filter " + _filterHolder);
1327                 Filter filter= _filterHolder.getFilter();
1328                 if (_filterHolder.isAsyncSupported())
1329                     filter.doFilter(request, response, _next);
1330                 else
1331                 {
1332                     final Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
1333                     final boolean suspendable=baseRequest.isAsyncSupported();
1334                     if (suspendable)
1335                     {
1336                         try
1337                         {
1338                             baseRequest.setAsyncSupported(false);
1339                             filter.doFilter(request, response, _next);
1340                         }
1341                         finally
1342                         {
1343                             baseRequest.setAsyncSupported(true);
1344                         }
1345                     }
1346                     else
1347                         filter.doFilter(request, response, _next);
1348                 }
1349                 return;
1350             }
1351 
1352             // Call servlet
1353             if (_servletHolder != null)
1354             {
1355                 if (LOG.isDebugEnabled())
1356                     LOG.debug("call servlet " + _servletHolder);
1357                 final Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
1358                 _servletHolder.handle(baseRequest,request, response);
1359             }
1360             else // Not found
1361                 notFound((HttpServletRequest)request, (HttpServletResponse)response);
1362         }
1363         
1364         public String toString()
1365         {
1366             if (_filterHolder!=null)
1367                 return _filterHolder+"->"+_next.toString();
1368             if (_servletHolder!=null)
1369                 return _servletHolder.toString();
1370             return "null";
1371         }
1372     }  
1373     
1374     /* ------------------------------------------------------------ */
1375     /* ------------------------------------------------------------ */
1376     private class Chain implements FilterChain
1377     {
1378         final Request _baseRequest;
1379         final Object _chain;
1380         final ServletHolder _servletHolder;
1381         int _filter= 0;
1382 
1383         /* ------------------------------------------------------------ */
1384         Chain(Request baseRequest, Object filters, ServletHolder servletHolder)
1385         {
1386             _baseRequest=baseRequest;
1387             _chain= filters;
1388             _servletHolder= servletHolder;
1389         }
1390 
1391         /* ------------------------------------------------------------ */
1392         public void doFilter(ServletRequest request, ServletResponse response)
1393             throws IOException, ServletException
1394         {
1395             if (LOG.isDebugEnabled()) 
1396                 LOG.debug("doFilter " + _filter);
1397 
1398             // pass to next filter
1399             if (_filter < LazyList.size(_chain))
1400             {
1401                 FilterHolder holder= (FilterHolder)LazyList.get(_chain, _filter++);
1402                 if (LOG.isDebugEnabled()) 
1403                     LOG.debug("call filter " + holder);
1404                 Filter filter= holder.getFilter();
1405                 
1406                 if (holder.isAsyncSupported() || !_baseRequest.isAsyncSupported())
1407                 {
1408                     filter.doFilter(request, response, this);
1409                 }
1410                 else
1411                 {
1412                     try
1413                     {
1414                         _baseRequest.setAsyncSupported(false);
1415                         filter.doFilter(request, response, this);
1416                     }
1417                     finally
1418                     {
1419                         _baseRequest.setAsyncSupported(true);
1420                     }
1421                 }
1422                     
1423                 return;
1424             }
1425 
1426             // Call servlet
1427             if (_servletHolder != null)
1428             {
1429                 if (LOG.isDebugEnabled()) 
1430                     LOG.debug("call servlet " + _servletHolder);
1431                 _servletHolder.handle(_baseRequest,request, response);
1432             }
1433             else // Not found
1434                 notFound((HttpServletRequest)request, (HttpServletResponse)response);
1435         }
1436 
1437         /* ------------------------------------------------------------ */
1438         public String toString()
1439         {
1440             StringBuilder b = new StringBuilder();
1441             for (int i=0; i<LazyList.size(_chain);i++)
1442             {
1443                 Object o=LazyList.get(_chain, i);
1444                 b.append(o.toString());
1445                 b.append("->");
1446             }
1447             b.append(_servletHolder);
1448             return b.toString();
1449         }
1450     }
1451 
1452     /* ------------------------------------------------------------ */
1453     /**
1454      * @return The maximum entries in a filter chain cache.
1455      */
1456     public int getMaxFilterChainsCacheSize()
1457     {
1458         return _maxFilterChainsCacheSize;
1459     }
1460 
1461     /* ------------------------------------------------------------ */
1462     /** Set the maximum filter chain cache size.
1463      * Filter chains are cached if {@link #isFilterChainsCached()} is true. If the max cache size
1464      * is greater than zero, then the cache is flushed whenever it grows to be this size.
1465      * 
1466      * @param maxFilterChainsCacheSize  the maximum number of entries in a filter chain cache.
1467      */
1468     public void setMaxFilterChainsCacheSize(int maxFilterChainsCacheSize)
1469     {
1470         _maxFilterChainsCacheSize = maxFilterChainsCacheSize;
1471     }
1472     
1473     /* ------------------------------------------------------------ */
1474     void destroyServlet(Servlet servlet)
1475     {
1476         if (_contextHandler!=null)
1477             _contextHandler.destroyServlet(servlet);
1478     }
1479 
1480     /* ------------------------------------------------------------ */
1481     void destroyFilter(Filter filter)
1482     {
1483         if (_contextHandler!=null)
1484             _contextHandler.destroyFilter(filter);
1485     }
1486     
1487     /* ------------------------------------------------------------ */
1488     @Override
1489     public void dump(Appendable out,String indent) throws IOException
1490     {
1491         super.dumpThis(out);
1492         dump(out,indent,
1493                 TypeUtil.asList(getHandlers()),
1494                 getBeans(),
1495                 TypeUtil.asList(getFilterMappings()),
1496                 TypeUtil.asList(getFilters()),
1497                 TypeUtil.asList(getServletMappings()),
1498                 TypeUtil.asList(getServlets()));
1499     }
1500 }