View Javadoc

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