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.HashSet;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Queue;
25  import java.util.concurrent.ConcurrentHashMap;
26  import java.util.concurrent.ConcurrentLinkedQueue;
27  import java.util.concurrent.ConcurrentMap;
28  
29  import org.eclipse.jetty.server.DispatcherType;
30  import javax.servlet.Filter;
31  import javax.servlet.FilterChain;
32  import javax.servlet.RequestDispatcher;
33  import javax.servlet.Servlet;
34  import javax.servlet.ServletContext;
35  import javax.servlet.ServletException;
36  import javax.servlet.ServletRequest;
37  import javax.servlet.ServletResponse;
38  import javax.servlet.UnavailableException;
39  import javax.servlet.http.HttpServletRequest;
40  import javax.servlet.http.HttpServletResponse;
41  
42  import org.eclipse.jetty.continuation.ContinuationThrowable;
43  import org.eclipse.jetty.http.HttpException;
44  import org.eclipse.jetty.http.PathMap;
45  import org.eclipse.jetty.io.EofException;
46  import org.eclipse.jetty.io.RuntimeIOException;
47  import org.eclipse.jetty.security.IdentityService;
48  import org.eclipse.jetty.security.SecurityHandler;
49  import org.eclipse.jetty.server.Dispatcher;
50  import org.eclipse.jetty.server.HttpConnection;
51  import org.eclipse.jetty.server.Request;
52  import org.eclipse.jetty.server.Server;
53  import org.eclipse.jetty.server.ServletRequestHttpWrapper;
54  import org.eclipse.jetty.server.ServletResponseHttpWrapper;
55  import org.eclipse.jetty.server.UserIdentity;
56  import org.eclipse.jetty.server.handler.ContextHandler;
57  import org.eclipse.jetty.server.handler.ScopedHandler;
58  import org.eclipse.jetty.util.LazyList;
59  import org.eclipse.jetty.util.MultiException;
60  import org.eclipse.jetty.util.MultiMap;
61  import org.eclipse.jetty.util.TypeUtil;
62  import org.eclipse.jetty.util.URIUtil;
63  import org.eclipse.jetty.util.log.Log;
64  import org.eclipse.jetty.util.log.Logger;
65  
66  /* --------------------------------------------------------------------- */
67  /** Servlet HttpHandler.
68   * This handler maps requests to servlets that implement the
69   * javax.servlet.http.HttpServlet API.
70   * <P>
71   * This handler does not implement the full J2EE features and is intended to
72   * be used directly when a full web application is not required.  If a Web application is required,
73   * then this handler should be used as part of a <code>org.eclipse.jetty.webapp.WebAppContext</code>.
74   * <p>
75   * Unless run as part of a {@link ServletContextHandler} or derivative, the {@link #initialize()}
76   * method must be called manually after start().
77   */
78  public class ServletHandler extends ScopedHandler
79  {
80      private static final Logger LOG = Log.getLogger(ServletHandler.class);
81  
82      /* ------------------------------------------------------------ */
83      public static final String __DEFAULT_SERVLET="default";
84          
85      /* ------------------------------------------------------------ */
86      private ServletContextHandler _contextHandler;
87      private ContextHandler.Context _servletContext;
88      private FilterHolder[] _filters=new FilterHolder[0];
89      private FilterMapping[] _filterMappings;
90      private boolean _filterChainsCached=true;
91      private int _maxFilterChainsCacheSize=512;
92      private boolean _startWithUnavailable=true;
93      private IdentityService _identityService;
94      
95      private ServletHolder[] _servlets=new ServletHolder[0];
96      private ServletMapping[] _servletMappings;
97      
98      private final Map<String,FilterHolder> _filterNameMap= new HashMap<String,FilterHolder>();
99      private List<FilterMapping> _filterPathMappings;
100     private MultiMap<String> _filterNameMappings;
101     
102     private final Map<String,ServletHolder> _servletNameMap=new HashMap<String,ServletHolder>();
103     private PathMap _servletPathMap;
104     
105     protected final ConcurrentMap<String,FilterChain> _chainCache[] = new ConcurrentMap[FilterMapping.ALL];
106     protected final Queue<String>[] _chainLRU = new Queue[FilterMapping.ALL];
107 
108 
109     /* ------------------------------------------------------------ */
110     /** Constructor. 
111      */
112     public ServletHandler()
113     {
114     }
115 
116     /* ------------------------------------------------------------ */
117     /* 
118      * @see org.eclipse.jetty.server.handler.AbstractHandler#setServer(org.eclipse.jetty.server.Server)
119      */
120     public void setServer(Server server)
121     {
122         Server old=getServer();
123         if (old!=null && old!=server)
124         {
125             getServer().getContainer().update(this, _filters, null, "filter",true);
126             getServer().getContainer().update(this, _filterMappings, null, "filterMapping",true);
127             getServer().getContainer().update(this, _servlets, null, "servlet",true);
128             getServer().getContainer().update(this, _servletMappings, null, "servletMapping",true);
129         }
130 
131         super.setServer(server);
132         
133         if (server!=null && old!=server)
134         {
135             server.getContainer().update(this, null, _filters, "filter",true);
136             server.getContainer().update(this, null, _filterMappings, "filterMapping",true);
137             server.getContainer().update(this, null, _servlets, "servlet",true);
138             server.getContainer().update(this, null, _servletMappings, "servletMapping",true);
139         }
140     }
141 
142     /* ----------------------------------------------------------------- */
143     @Override
144     protected synchronized void doStart()
145         throws Exception
146     {
147         _servletContext=ContextHandler.getCurrentContext();
148         _contextHandler=(ServletContextHandler)(_servletContext==null?null:_servletContext.getContextHandler());
149 
150         if (_contextHandler!=null)
151         {
152             SecurityHandler security_handler = (SecurityHandler)_contextHandler.getChildHandlerByClass(SecurityHandler.class);
153             if (security_handler!=null)
154                 _identityService=security_handler.getIdentityService();
155         }
156         
157         updateNameMappings();
158         updateMappings();
159         
160         if(_filterChainsCached)
161         {
162             _chainCache[FilterMapping.REQUEST]=new ConcurrentHashMap<String,FilterChain>();
163             _chainCache[FilterMapping.FORWARD]=new ConcurrentHashMap<String,FilterChain>();
164             _chainCache[FilterMapping.INCLUDE]=new ConcurrentHashMap<String,FilterChain>();
165             _chainCache[FilterMapping.ERROR]=new ConcurrentHashMap<String,FilterChain>();
166             _chainCache[FilterMapping.ASYNC]=new ConcurrentHashMap<String,FilterChain>();
167             
168             _chainLRU[FilterMapping.REQUEST]=new ConcurrentLinkedQueue<String>();
169             _chainLRU[FilterMapping.FORWARD]=new ConcurrentLinkedQueue<String>();
170             _chainLRU[FilterMapping.INCLUDE]=new ConcurrentLinkedQueue<String>();
171             _chainLRU[FilterMapping.ERROR]=new ConcurrentLinkedQueue<String>();
172             _chainLRU[FilterMapping.ASYNC]=new ConcurrentLinkedQueue<String>();
173         }
174 
175         super.doStart();
176         
177         if (_contextHandler==null || !(_contextHandler instanceof ServletContextHandler))
178             initialize();
179     }   
180     
181     /* ----------------------------------------------------------------- */
182     @Override
183     protected synchronized void doStop()
184         throws Exception
185     {
186         super.doStop();
187         
188         // Stop filters
189         if (_filters!=null)
190         {
191             for (int i=_filters.length; i-->0;)
192             {
193                 try { _filters[i].stop(); }catch(Exception e){LOG.warn(Log.EXCEPTION,e);}
194             }
195         }
196         
197         // Stop servlets
198         if (_servlets!=null)
199         {
200             for (int i=_servlets.length; i-->0;)
201             {
202                 try { _servlets[i].stop(); }catch(Exception e){LOG.warn(Log.EXCEPTION,e);}
203             }
204         }
205 
206         _filterPathMappings=null;
207         _filterNameMappings=null;
208         
209         _servletPathMap=null;
210     }
211 
212     /* ------------------------------------------------------------ */
213     IdentityService getIdentityService()
214     {
215         return _identityService;
216     }
217     
218     /* ------------------------------------------------------------ */
219     /**
220      * @return Returns the contextLog.
221      */
222     public Object getContextLog()
223     {
224         return null;
225     }
226     
227     /* ------------------------------------------------------------ */
228     /**
229      * @return Returns the filterMappings.
230      */
231     public FilterMapping[] getFilterMappings()
232     {
233         return _filterMappings;
234     }
235     
236     /* ------------------------------------------------------------ */
237     /** Get Filters.
238      * @return Array of defined servlets
239      */
240     public FilterHolder[] getFilters()
241     {
242         return _filters;
243     }
244     
245     /* ------------------------------------------------------------ */
246     /** ServletHolder matching path.
247      * @param pathInContext Path within _context.
248      * @return PathMap Entries pathspec to ServletHolder
249      */
250     public PathMap.Entry getHolderEntry(String pathInContext)
251     {
252         if (_servletPathMap==null)
253             return null;
254         return _servletPathMap.getMatch(pathInContext);
255     }
256 
257     /* ------------------------------------------------------------ */
258     /**
259      * @param uriInContext uri to get dispatcher for
260      * @return A {@link RequestDispatcher dispatcher} wrapping the resource at <code>uriInContext</code>,
261      *  or <code>null</code> if the specified uri cannot be dispatched to.
262      */
263     public RequestDispatcher getRequestDispatcher(String uriInContext)
264     {
265         if (uriInContext == null || _contextHandler==null)
266             return null;
267 
268         if (!uriInContext.startsWith("/"))
269             return null;
270         
271         try
272         {
273             String query=null;
274             int q;
275             if ((q=uriInContext.indexOf('?'))>0)
276             {
277                 query=uriInContext.substring(q+1);
278                 uriInContext=uriInContext.substring(0,q);
279             }
280             if ((q=uriInContext.indexOf(';'))>0)
281                 uriInContext=uriInContext.substring(0,q);
282 
283             String pathInContext=URIUtil.canonicalPath(URIUtil.decodePath(uriInContext));
284             String uri=URIUtil.addPaths(_contextHandler.getContextPath(), uriInContext);
285             return new Dispatcher(_contextHandler, uri, pathInContext, query);
286         }
287         catch(Exception e)
288         {
289             LOG.ignore(e);
290         }
291         return null;
292     }
293 
294     /* ------------------------------------------------------------ */
295     public ServletContext getServletContext()
296     {
297         return _servletContext;
298     }
299     
300     /* ------------------------------------------------------------ */
301     /**
302      * @return Returns the servletMappings.
303      */
304     public ServletMapping[] getServletMappings()
305     {
306         return _servletMappings;
307     }
308     
309     /* ------------------------------------------------------------ */
310     /**
311      * @return Returns the servletMappings.
312      */
313     public ServletMapping getServletMapping(String pattern)
314     {
315         if (_servletMappings!=null)
316         {
317             for (ServletMapping m:_servletMappings)
318             {
319                 String[] paths=m.getPathSpecs();
320                 if (paths!=null)
321                 {
322                     for (String path:paths)
323                     {
324                         if (pattern.equals(path))
325                             return m;
326                     }
327                 }
328             }
329         }
330         return null;
331     }
332         
333     /* ------------------------------------------------------------ */
334     /** Get Servlets.
335      * @return Array of defined servlets
336      */
337     public ServletHolder[] getServlets()
338     {
339         return _servlets;
340     }
341 
342     /* ------------------------------------------------------------ */
343     public ServletHolder getServlet(String name)
344     {
345         return (ServletHolder)_servletNameMap.get(name);
346     }
347 
348     /* ------------------------------------------------------------ */
349     @Override
350     public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
351     {
352         // Get the base requests
353         final String old_servlet_path=baseRequest.getServletPath();
354         final String old_path_info=baseRequest.getPathInfo();
355 
356         DispatcherType type = baseRequest.getDispatcherType();
357        
358         ServletHolder servlet_holder=null;
359         UserIdentity.Scope old_scope=null;
360 
361         // find the servlet
362         if (target.startsWith("/"))
363         {
364             // Look for the servlet by path
365             PathMap.Entry entry=getHolderEntry(target);
366             if (entry!=null)
367             {
368                 servlet_holder=(ServletHolder)entry.getValue();
369 
370                 String servlet_path_spec=(String)entry.getKey(); 
371                 String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target);
372                 String path_info=PathMap.pathInfo(servlet_path_spec,target);
373 
374                 if (DispatcherType.INCLUDE.equals(type))
375                 {
376                     baseRequest.setAttribute(Dispatcher.INCLUDE_SERVLET_PATH,servlet_path);
377                     baseRequest.setAttribute(Dispatcher.INCLUDE_PATH_INFO, path_info);
378                 }
379                 else
380                 {
381                     baseRequest.setServletPath(servlet_path);
382                     baseRequest.setPathInfo(path_info);
383                 }
384             }      
385         }
386         else
387         {
388             // look for a servlet by name!
389             servlet_holder=(ServletHolder)_servletNameMap.get(target);
390         }
391 
392         if (LOG.isDebugEnabled())
393             LOG.debug("servlet {}|{}|{} -> {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),servlet_holder);
394 
395         try
396         {
397             // Do the filter/handling thang
398             if (servlet_holder!=null)
399             {
400                 old_scope=baseRequest.getUserIdentityScope();
401                 baseRequest.setUserIdentityScope(servlet_holder);
402 
403                 // start manual inline of nextScope(target,baseRequest,request,response);
404                 if (never())
405                     nextScope(target,baseRequest,request,response);
406                 else if (_nextScope!=null)
407                     _nextScope.doScope(target,baseRequest,request, response);
408                 else if (_outerScope!=null)
409                     _outerScope.doHandle(target,baseRequest,request, response);
410                 else 
411                     doHandle(target,baseRequest,request, response);
412                 // end manual inline (pathentic attempt to reduce stack depth)
413             }
414         }
415         finally
416         {
417             if (old_scope!=null)
418                 baseRequest.setUserIdentityScope(old_scope);
419 
420             if (!(DispatcherType.INCLUDE.equals(type)))
421             {
422                 baseRequest.setServletPath(old_servlet_path);
423                 baseRequest.setPathInfo(old_path_info); 
424             }
425         }
426     }
427     
428     /* ------------------------------------------------------------ */
429     /* 
430      * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
431      */
432     @Override
433     public void doHandle(String target, Request baseRequest,HttpServletRequest request, HttpServletResponse response)
434         throws IOException, ServletException
435     {
436         DispatcherType type = baseRequest.getDispatcherType();
437         
438         ServletHolder servlet_holder=(ServletHolder) baseRequest.getUserIdentityScope();
439         FilterChain chain=null;
440 
441         // find the servlet
442         if (target.startsWith("/"))
443         {
444             if (servlet_holder!=null && _filterMappings!=null && _filterMappings.length>0)
445                 chain=getFilterChain(baseRequest, target, servlet_holder);
446         }
447         else
448         {
449             if (servlet_holder!=null)
450             {
451                 if (_filterMappings!=null && _filterMappings.length>0)
452                 {
453                     chain=getFilterChain(baseRequest, null,servlet_holder);
454                 }
455             }
456         }
457 
458         LOG.debug("chain={}",chain);
459         
460         try
461         {
462             if (servlet_holder==null)
463             {
464                 notFound(request, response);
465             }
466             else
467             {
468                 // unwrap any tunnelling of base Servlet request/responses
469                 ServletRequest req = request;
470                 if (req instanceof ServletRequestHttpWrapper)
471                     req = ((ServletRequestHttpWrapper)req).getRequest();
472                 ServletResponse res = response;
473                 if (res instanceof ServletResponseHttpWrapper)
474                     res = ((ServletResponseHttpWrapper)res).getResponse();
475 
476                 // Do the filter/handling thang
477                 if (chain!=null)
478                     chain.doFilter(req, res);
479                 else 
480                     servlet_holder.handle(baseRequest,req,res);
481             }
482         }
483         catch(EofException e)
484         {
485             throw e;
486         }
487         catch(RuntimeIOException e)
488         {
489             throw e;
490         }
491         catch(ContinuationThrowable e)
492         {   
493             throw e;
494         }
495         catch(Exception e)
496         {
497             if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
498             {
499                 if (e instanceof IOException)
500                     throw (IOException)e;
501                 if (e instanceof RuntimeException)
502                     throw (RuntimeException)e;
503                 if (e instanceof ServletException)
504                     throw (ServletException)e;
505             }
506 
507             // unwrap cause
508             Throwable th=e;
509             if (th instanceof UnavailableException)
510             {
511                 LOG.debug(th); 
512             }
513             else if (th instanceof ServletException)
514             {
515                 LOG.debug(th);
516                 Throwable cause=((ServletException)th).getRootCause();
517                 if (cause!=null)
518                     th=cause;
519             }
520             else if (th instanceof RuntimeIOException)
521             {
522                 LOG.debug(th);
523                 Throwable cause=(IOException)((RuntimeIOException)th).getCause();
524                 if (cause!=null)
525                     th=cause;
526             }
527 
528             // handle or log exception
529             if (th instanceof HttpException)
530                 throw (HttpException)th;
531             else if (th instanceof RuntimeIOException)
532                 throw (RuntimeIOException)th;
533             else if (th instanceof EofException)
534                 throw (EofException)th;
535 
536             else if (LOG.isDebugEnabled())
537             {
538                 LOG.warn(request.getRequestURI(), th); 
539                 LOG.debug(request.toString()); 
540             }
541             else if (th instanceof IOException || th instanceof UnavailableException)
542             {
543                 LOG.debug(request.getRequestURI(),th);
544             }
545             else
546             {
547                 LOG.warn(request.getRequestURI(),th);
548             }
549 
550             if (!response.isCommitted())
551             {
552                 request.setAttribute(Dispatcher.ERROR_EXCEPTION_TYPE,th.getClass());
553                 request.setAttribute(Dispatcher.ERROR_EXCEPTION,th);
554                 if (th instanceof UnavailableException)
555                 {
556                     UnavailableException ue = (UnavailableException)th;
557                     if (ue.isPermanent())
558                         response.sendError(HttpServletResponse.SC_NOT_FOUND,th.getMessage());
559                     else
560                         response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,th.getMessage());
561                 }
562                 else
563                     response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,th.getMessage());
564             }
565             else
566                 LOG.debug("Response already committed for handling "+th);
567         }
568         catch(Error e)
569         {   
570             if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
571                 throw e;
572             LOG.warn("Error for "+request.getRequestURI(),e);
573             if(LOG.isDebugEnabled())LOG.debug(request.toString());
574 
575             // TODO httpResponse.getHttpConnection().forceClose();
576             if (!response.isCommitted())
577             {
578                 request.setAttribute(Dispatcher.ERROR_EXCEPTION_TYPE,e.getClass());
579                 request.setAttribute(Dispatcher.ERROR_EXCEPTION,e);
580                 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,e.getMessage());
581             }
582             else
583                 LOG.debug("Response already committed for handling ",e);
584         }
585         finally
586         {
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         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             // pass to next filter
1324             if (_filterHolder!=null)
1325             {
1326                 if (LOG.isDebugEnabled())
1327                     LOG.debug("call filter " + _filterHolder);
1328                 Filter filter= _filterHolder.getFilter();
1329                 if (_filterHolder.isAsyncSupported())
1330                     filter.doFilter(request, response, _next);
1331                 else
1332                 {
1333                     final Request baseRequest=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest();
1334                     final boolean suspendable=baseRequest.isAsyncSupported();
1335                     if (suspendable)
1336                     {
1337                         try
1338                         {
1339                             baseRequest.setAsyncSupported(false);
1340                             filter.doFilter(request, response, _next);
1341                         }
1342                         finally
1343                         {
1344                             baseRequest.setAsyncSupported(true);
1345                         }
1346                     }
1347                     else
1348                         filter.doFilter(request, response, _next);
1349                 }
1350                 return;
1351             }
1352 
1353             // Call servlet
1354             if (_servletHolder != null)
1355             {
1356                 if (LOG.isDebugEnabled())
1357                     LOG.debug("call servlet " + _servletHolder);
1358                 final Request baseRequest=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest();
1359                 _servletHolder.handle(baseRequest,request, response);
1360             }
1361             else // Not found
1362                 notFound((HttpServletRequest)request, (HttpServletResponse)response);
1363         }
1364         
1365         public String toString()
1366         {
1367             if (_filterHolder!=null)
1368                 return _filterHolder+"->"+_next.toString();
1369             if (_servletHolder!=null)
1370                 return _servletHolder.toString();
1371             return "null";
1372         }
1373     }  
1374     
1375     /* ------------------------------------------------------------ */
1376     /* ------------------------------------------------------------ */
1377     private class Chain implements FilterChain
1378     {
1379         final Request _baseRequest;
1380         final Object _chain;
1381         final ServletHolder _servletHolder;
1382         int _filter= 0;
1383 
1384         /* ------------------------------------------------------------ */
1385         Chain(Request baseRequest, Object filters, ServletHolder servletHolder)
1386         {
1387             _baseRequest=baseRequest;
1388             _chain= filters;
1389             _servletHolder= servletHolder;
1390         }
1391 
1392         /* ------------------------------------------------------------ */
1393         public void doFilter(ServletRequest request, ServletResponse response)
1394             throws IOException, ServletException
1395         {
1396             if (LOG.isDebugEnabled()) 
1397                 LOG.debug("doFilter " + _filter);
1398 
1399             // pass to next filter
1400             if (_filter < LazyList.size(_chain))
1401             {
1402                 FilterHolder holder= (FilterHolder)LazyList.get(_chain, _filter++);
1403                 if (LOG.isDebugEnabled()) 
1404                     LOG.debug("call filter " + holder);
1405                 Filter filter= holder.getFilter();
1406                 
1407                 if (holder.isAsyncSupported() || !_baseRequest.isAsyncSupported())
1408                 {
1409                     filter.doFilter(request, response, this);
1410                 }
1411                 else
1412                 {
1413                     try
1414                     {
1415                         _baseRequest.setAsyncSupported(false);
1416                         filter.doFilter(request, response, this);
1417                     }
1418                     finally
1419                     {
1420                         _baseRequest.setAsyncSupported(true);
1421                     }
1422                 }
1423                     
1424                 return;
1425             }
1426 
1427             // Call servlet
1428             if (_servletHolder != null)
1429             {
1430                 if (LOG.isDebugEnabled()) 
1431                     LOG.debug("call servlet " + _servletHolder);
1432                 _servletHolder.handle(_baseRequest,request, response);
1433             }
1434             else // Not found
1435                 notFound((HttpServletRequest)request, (HttpServletResponse)response);
1436         }
1437 
1438         /* ------------------------------------------------------------ */
1439         public String toString()
1440         {
1441             StringBuilder b = new StringBuilder();
1442             for (int i=0; i<LazyList.size(_chain);i++)
1443             {
1444                 Object o=LazyList.get(_chain, i);
1445                 b.append(o.toString());
1446                 b.append("->");
1447             }
1448             b.append(_servletHolder);
1449             return b.toString();
1450         }
1451     }
1452 
1453     /* ------------------------------------------------------------ */
1454     /**
1455      * @return The maximum entries in a filter chain cache.
1456      */
1457     public int getMaxFilterChainsCacheSize()
1458     {
1459         return _maxFilterChainsCacheSize;
1460     }
1461 
1462     /* ------------------------------------------------------------ */
1463     /** Set the maximum filter chain cache size.
1464      * Filter chains are cached if {@link #isFilterChainsCached()} is true. If the max cache size
1465      * is greater than zero, then the cache is flushed whenever it grows to be this size.
1466      * 
1467      * @param maxFilterChainsCacheSize  the maximum number of entries in a filter chain cache.
1468      */
1469     public void setMaxFilterChainsCacheSize(int maxFilterChainsCacheSize)
1470     {
1471         _maxFilterChainsCacheSize = maxFilterChainsCacheSize;
1472     }
1473     
1474     /* ------------------------------------------------------------ */
1475     void destroyServlet(Servlet servlet)
1476     {
1477         if (_contextHandler!=null)
1478             _contextHandler.destroyServlet(servlet);
1479     }
1480 
1481     /* ------------------------------------------------------------ */
1482     void destroyFilter(Filter filter)
1483     {
1484         if (_contextHandler!=null)
1485             _contextHandler.destroyFilter(filter);
1486     }
1487     
1488     /* ------------------------------------------------------------ */
1489     @Override
1490     public void dump(Appendable out,String indent) throws IOException
1491     {
1492         super.dumpThis(out);
1493         dump(out,indent,
1494                 TypeUtil.asList(getHandlers()),
1495                 getBeans(),
1496                 TypeUtil.asList(getFilterMappings()),
1497                 TypeUtil.asList(getFilters()),
1498                 TypeUtil.asList(getServletMappings()),
1499                 TypeUtil.asList(getServlets()));
1500     }
1501 }