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