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