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