View Javadoc

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