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