1   // ========================================================================
2   // Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.jetty.servlet;
16  
17  
18  import java.io.IOException;
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.concurrent.ConcurrentHashMap;
25  
26  import javax.servlet.Filter;
27  import javax.servlet.FilterChain;
28  import javax.servlet.RequestDispatcher;
29  import javax.servlet.Servlet;
30  import javax.servlet.ServletContext;
31  import javax.servlet.ServletException;
32  import javax.servlet.ServletRequest;
33  import javax.servlet.ServletResponse;
34  import javax.servlet.UnavailableException;
35  import javax.servlet.http.HttpServletRequest;
36  import javax.servlet.http.HttpServletResponse;
37  
38  import org.mortbay.jetty.Dispatcher;
39  import org.mortbay.jetty.EofException;
40  import org.mortbay.jetty.HttpConnection;
41  import org.mortbay.jetty.HttpException;
42  import org.mortbay.jetty.Request;
43  import org.mortbay.jetty.RetryRequest;
44  import org.mortbay.jetty.Server;
45  import org.mortbay.jetty.handler.AbstractHandler;
46  import org.mortbay.jetty.handler.ContextHandler;
47  import org.mortbay.log.Log;
48  import org.mortbay.util.LazyList;
49  import org.mortbay.util.MultiException;
50  import org.mortbay.util.MultiMap;
51  import org.mortbay.util.URIUtil;
52  
53  
54  /* --------------------------------------------------------------------- */
55  /** Servlet HttpHandler.
56   * This handler maps requests to servlets that implement the
57   * javax.servlet.http.HttpServlet API.
58   * <P>
59   * This handler does not implement the full J2EE features and is intended to
60   * be used when a full web application is not required.  Specifically filters
61   * and request wrapping are not supported.
62   * 
63   * Unless run as part of a {@link Context} or derivative, the {@link #initialize()}
64   * method must be called manually after start().
65   * 
66   * @see org.mortbay.jetty.webapp.WebAppContext
67   * @author Greg Wilkins
68   */
69  public class ServletHandler extends AbstractHandler
70  {
71      /* ------------------------------------------------------------ */
72      public static final String __DEFAULT_SERVLET="default";
73      public static final String __J_S_CONTEXT_TEMPDIR="javax.servlet.context.tempdir";
74      public static final String __J_S_ERROR_EXCEPTION="javax.servlet.error.exception";
75      public static final String __J_S_ERROR_EXCEPTION_TYPE="javax.servlet.error.exception_type";
76      public static final String __J_S_ERROR_MESSAGE="javax.servlet.error.message";
77      public static final String __J_S_ERROR_REQUEST_URI="javax.servlet.error.request_uri";
78      public static final String __J_S_ERROR_SERVLET_NAME="javax.servlet.error.servlet_name";
79      public static final String __J_S_ERROR_STATUS_CODE="javax.servlet.error.status_code";
80          
81      /* ------------------------------------------------------------ */
82      private ContextHandler _contextHandler;
83      private ContextHandler.SContext _servletContext;
84      private FilterHolder[] _filters;
85      private FilterMapping[] _filterMappings;
86      private boolean _filterChainsCached=true;
87      private int _maxFilterChainsCacheSize=1000;
88      private boolean _startWithUnavailable=true;
89      
90      private ServletHolder[] _servlets;
91      private ServletMapping[] _servletMappings;
92      
93      private transient Map _filterNameMap= new HashMap();
94      private transient List _filterPathMappings;
95      private transient MultiMap _filterNameMappings;
96      
97      private transient Map _servletNameMap=new HashMap();
98      private transient PathMap _servletPathMap;
99      
100     protected transient ConcurrentHashMap _chainCache[];
101 
102 
103     /* ------------------------------------------------------------ */
104     /** Constructor. 
105      */
106     public ServletHandler()
107     {
108     }
109 
110     /* ------------------------------------------------------------ */
111     /* 
112      * @see org.mortbay.jetty.handler.AbstractHandler#setServer(org.mortbay.jetty.Server)
113      */
114     public void setServer(Server server)
115     {
116         if (getServer()!=null && getServer()!=server)
117         {
118             getServer().getContainer().update(this, _filters, null, "filter",true);
119             getServer().getContainer().update(this, _filterMappings, null, "filterMapping",true);
120             getServer().getContainer().update(this, _servlets, null, "servlet",true);
121             getServer().getContainer().update(this, _servletMappings, null, "servletMapping",true);
122         }
123         if (server!=null && getServer()!=server)
124         {
125             server.getContainer().update(this, null, _filters, "filter",true);
126             server.getContainer().update(this, null, _filterMappings, "filterMapping",true);
127             server.getContainer().update(this, null, _servlets, "servlet",true);
128             server.getContainer().update(this, null, _servletMappings, "servletMapping",true);
129         }
130         super.setServer(server);
131         
132     }
133 
134     /* ----------------------------------------------------------------- */
135     protected synchronized void doStart()
136         throws Exception
137     {
138         _servletContext=ContextHandler.getCurrentContext();
139         _contextHandler=_servletContext==null?null:_servletContext.getContextHandler();
140 
141         updateNameMappings();
142         updateMappings();
143         
144         if(_filterChainsCached)
145             _chainCache=     new ConcurrentHashMap[]{null,new ConcurrentHashMap(),new ConcurrentHashMap(),null,new ConcurrentHashMap(),null,null,null,new ConcurrentHashMap()};
146 
147         super.doStart();
148         
149         if (_contextHandler==null || !(_contextHandler instanceof Context))
150             initialize();
151     }   
152     
153     /* ----------------------------------------------------------------- */
154     protected synchronized void doStop()
155         throws Exception
156     {
157         super.doStop();
158         
159         // Stop filters
160         if (_filters!=null)
161         {
162             for (int i=_filters.length; i-->0;)
163             {
164                 try { _filters[i].stop(); }catch(Exception e){Log.warn(Log.EXCEPTION,e);}
165             }
166         }
167         
168         // Stop servlets
169         if (_servlets!=null)
170         {
171             for (int i=_servlets.length; i-->0;)
172             {
173                 try { _servlets[i].stop(); }catch(Exception e){Log.warn(Log.EXCEPTION,e);}
174             }
175         }
176 
177         _filterPathMappings=null;
178         _filterNameMappings=null;
179         
180         _servletPathMap=null;
181         _chainCache=null;
182     }
183 
184     
185     /* ------------------------------------------------------------ */
186     /**
187      * @return Returns the contextLog.
188      */
189     public Object getContextLog()
190     {
191         return null;
192     }
193     /* ------------------------------------------------------------ */
194     /**
195      * @return Returns the filterMappings.
196      */
197     public FilterMapping[] getFilterMappings()
198     {
199         return _filterMappings;
200     }
201     
202     /* ------------------------------------------------------------ */
203     /** Get Filters.
204      * @return Array of defined servlets
205      */
206     public FilterHolder[] getFilters()
207     {
208         return _filters;
209     }
210     
211     /* ------------------------------------------------------------ */
212     /** ServletHolder matching path.
213      * @param pathInContext Path within _context.
214      * @return PathMap Entries pathspec to ServletHolder
215      */
216     public PathMap.Entry getHolderEntry(String pathInContext)
217     {
218         if (_servletPathMap==null)
219             return null;
220         return _servletPathMap.getMatch(pathInContext);
221     }
222     
223     /* ------------------------------------------------------------ */
224     /**
225      * @return A {@link RequestDispatcher dispatcher} wrapping the resource at <code>uriInContext</code>,
226      *  or <code>null</code> if the specified uri cannot be dispatched to.
227      */
228     public RequestDispatcher getRequestDispatcher(String uriInContext)
229     {
230         if (uriInContext == null)
231             return null;
232 
233         if (!uriInContext.startsWith("/"))
234             return null;
235         
236         try
237         {
238             String query=null;
239             int q=0;
240             if ((q=uriInContext.indexOf('?'))>0)
241             {
242                 query=uriInContext.substring(q+1);
243                 uriInContext=uriInContext.substring(0,q);
244             }
245             if ((q=uriInContext.indexOf(';'))>0)
246                 uriInContext=uriInContext.substring(0,q);
247 
248             String pathInContext=URIUtil.canonicalPath(URIUtil.decodePath(uriInContext));
249             String uri=URIUtil.addPaths(_contextHandler.getContextPath(), uriInContext);
250             return new Dispatcher(_contextHandler, uri, pathInContext, query);
251         }
252         catch(Exception e)
253         {
254             Log.ignore(e);
255         }
256         return null;
257     }
258 
259     /* ------------------------------------------------------------ */
260     public ServletContext getServletContext()
261     {
262         return _servletContext;
263     }
264     /* ------------------------------------------------------------ */
265     /**
266      * @return Returns the servletMappings.
267      */
268     public ServletMapping[] getServletMappings()
269     {
270         return _servletMappings;
271     }
272         
273     /* ------------------------------------------------------------ */
274     /** Get Servlets.
275      * @return Array of defined servlets
276      */
277     public ServletHolder[] getServlets()
278     {
279         return _servlets;
280     }
281 
282     /* ------------------------------------------------------------ */
283     public ServletHolder getServlet(String name)
284     {
285         return (ServletHolder)_servletNameMap.get(name);
286     }
287     
288     /* ------------------------------------------------------------ */
289     /* 
290      * @see org.mortbay.jetty.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
291      */
292     public void handle(String target, HttpServletRequest request,HttpServletResponse response, int type)
293          throws IOException, ServletException
294     {
295         if (!isStarted())
296             return;
297         
298         // Get the base requests
299         final Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest();
300         final String old_servlet_name=base_request.getServletName();
301         final String old_servlet_path=base_request.getServletPath();
302         final String old_path_info=base_request.getPathInfo();
303         final Map old_role_map=base_request.getRoleMap();
304         
305         try
306         {
307             ServletHolder servlet_holder=null;
308             FilterChain chain=null;
309             
310             // find the servlet
311             if (target.startsWith("/"))
312             {
313                 // Look for the servlet by path
314                 PathMap.Entry entry=getHolderEntry(target);
315                 if (entry!=null)
316                 {
317                     servlet_holder=(ServletHolder)entry.getValue();
318                     base_request.setServletName(servlet_holder.getName());
319                     base_request.setRoleMap(servlet_holder.getRoleMap());
320                     if(Log.isDebugEnabled())Log.debug("servlet="+servlet_holder);
321                     
322                     String servlet_path_spec=(String)entry.getKey(); 
323                     String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target);
324                     String path_info=PathMap.pathInfo(servlet_path_spec,target);
325                     
326                     if (type==INCLUDE)
327                     {
328                         base_request.setAttribute(Dispatcher.__INCLUDE_SERVLET_PATH,servlet_path);
329                         base_request.setAttribute(Dispatcher.__INCLUDE_PATH_INFO, path_info);
330                     }
331                     else
332                     {
333                         base_request.setServletPath(servlet_path);
334                         base_request.setPathInfo(path_info);
335                     }
336                     
337                     if (servlet_holder!=null && _filterMappings!=null && _filterMappings.length>0)
338                         chain=getFilterChain(type, target, servlet_holder,request.isInitial());
339                 }      
340             }
341             else
342             {
343                 // look for a servlet by name!
344                 servlet_holder=(ServletHolder)_servletNameMap.get(target);
345                 if (servlet_holder!=null && _filterMappings!=null && _filterMappings.length>0)
346                 {
347                     base_request.setServletName(servlet_holder.getName());
348                     chain=getFilterChain(type, null,servlet_holder,request.isInitial());
349                 }
350             }
351 
352             if (Log.isDebugEnabled()) 
353             {
354                 Log.debug("chain="+chain);
355                 Log.debug("servlet holder="+servlet_holder);
356             }
357 
358             // Do the filter/handling thang
359             if (servlet_holder!=null)
360             {
361                 base_request.setHandled(true);
362                 if (chain!=null)
363                     chain.doFilter(request, response);
364                 else 
365                     servlet_holder.handle(request,response);
366             }
367             else
368                 notFound(request, response);
369         }
370         catch(RetryRequest e)
371         {
372             throw e;
373         }
374         catch(EofException e)
375         {
376             throw e;
377         }
378         catch(Exception e)
379         {
380             if (type!=REQUEST)
381             {
382                 if (e instanceof IOException)
383                     throw (IOException)e;
384                 if (e instanceof RuntimeException)
385                     throw (RuntimeException)e;
386                 if (e instanceof ServletException)
387                     throw (ServletException)e;
388             }
389             
390             
391             // unwrap cause
392             Throwable th=e;
393             if (th instanceof UnavailableException)
394             {
395                 Log.debug(th); 
396             }
397             else if (th instanceof ServletException)
398             {
399                 Log.debug(th);
400                 Throwable cause=((ServletException)th).getRootCause();
401                 if (cause!=th && cause!=null)
402                     th=cause;
403             }
404             
405             // hnndle or log exception
406             if (th instanceof RetryRequest)
407             {
408                 base_request.setHandled(false);
409                 throw (RetryRequest)th;  
410             }
411             else if (th instanceof HttpException)
412                 throw (HttpException)th;
413             else if (Log.isDebugEnabled())
414             {
415                 Log.warn(request.getRequestURI(), th); 
416                 Log.debug(request.toString()); 
417             }
418             else if (th instanceof IOException || th instanceof UnavailableException)
419             {
420                 Log.warn(request.getRequestURI()+": "+th);
421             }
422             else
423             {
424                 Log.warn(request.getRequestURI(),th);
425             }
426             
427             // TODO httpResponse.getHttpConnection().forceClose();
428             if (!response.isCommitted())
429             {
430                 request.setAttribute(ServletHandler.__J_S_ERROR_EXCEPTION_TYPE,th.getClass());
431                 request.setAttribute(ServletHandler.__J_S_ERROR_EXCEPTION,th);
432                 if (th instanceof UnavailableException)
433                 {
434                     UnavailableException ue = (UnavailableException)th;
435                     if (ue.isPermanent())
436                         response.sendError(HttpServletResponse.SC_NOT_FOUND,th.getMessage());
437                     else
438                         response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,th.getMessage());
439                 }
440                 else
441                     response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,th.getMessage());
442             }
443             else
444                 if(Log.isDebugEnabled())Log.debug("Response already committed for handling "+th);
445         }
446         catch(Error e)
447         {   
448             if (type!=REQUEST)
449                 throw e;
450             Log.warn("Error for "+request.getRequestURI(),e);
451             if(Log.isDebugEnabled())Log.debug(request.toString());
452             
453             // TODO httpResponse.getHttpConnection().forceClose();
454             if (!response.isCommitted())
455             {
456                 request.setAttribute(ServletHandler.__J_S_ERROR_EXCEPTION_TYPE,e.getClass());
457                 request.setAttribute(ServletHandler.__J_S_ERROR_EXCEPTION,e);
458                 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,e.getMessage());
459             }
460             else
461                 if(Log.isDebugEnabled())Log.debug("Response already committed for handling ",e);
462         }
463         finally
464         {
465             base_request.setServletName(old_servlet_name);
466             base_request.setRoleMap(old_role_map);
467             if (type!=INCLUDE)
468             {
469                 base_request.setServletPath(old_servlet_path);
470                 base_request.setPathInfo(old_path_info); 
471             }
472         }
473         return;
474     }
475 
476     /* ------------------------------------------------------------ */
477     private FilterChain getFilterChain(int requestType, String pathInContext, ServletHolder servletHolder, boolean initial) 
478     {
479         String key=pathInContext==null?servletHolder.getName():pathInContext;
480         if (!initial)
481             key="!"+key;
482             
483         
484         if (_filterChainsCached && _chainCache!=null)
485         {
486             FilterChain chain = (FilterChain)_chainCache[requestType].get(key);
487             if (chain!=null)
488                 return chain;
489         }
490         
491         // Build list of filters
492         Object filters= null;
493         
494         // Path filters
495         if (pathInContext!=null && _filterPathMappings!=null)
496         {
497             for (int i= 0; i < _filterPathMappings.size(); i++)
498             {
499                 FilterMapping mapping = (FilterMapping)_filterPathMappings.get(i);
500                 if (mapping.appliesTo(pathInContext, requestType,initial))
501                     filters= LazyList.add(filters, mapping.getFilterHolder());
502             }
503         }
504 
505         // Servlet name filters
506         if (servletHolder != null && _filterNameMappings!=null && _filterNameMappings.size() > 0)
507         {
508             // Servlet name filters
509             if (_filterNameMappings.size() > 0)
510             {
511                 Object o= _filterNameMappings.get(servletHolder.getName());
512                 for (int i=0; i<LazyList.size(o);i++)
513                 {
514                     FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
515                     if (mapping.appliesTo(requestType,initial))
516                         filters=LazyList.add(filters,mapping.getFilterHolder());
517                 }
518                 
519                 o= _filterNameMappings.get("*");
520                 for (int i=0; i<LazyList.size(o);i++)
521                 {
522                     FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
523                     if (mapping.appliesTo(requestType,initial))
524                         filters=LazyList.add(filters,mapping.getFilterHolder());
525                 }
526             }
527         }
528         
529         if (filters==null)
530             return null;
531         
532         FilterChain chain = null;
533         if (_filterChainsCached)
534         {
535             if (LazyList.size(filters) > 0)
536                 chain= new CachedChain(filters, servletHolder);
537 	    if (_maxFilterChainsCacheSize>0 && _chainCache[requestType].size()>_maxFilterChainsCacheSize)
538 		_chainCache[requestType].clear();
539 	    _chainCache[requestType].put(key,chain);
540         }
541         else if (LazyList.size(filters) > 0)
542             chain = new Chain(filters, servletHolder);
543     
544         return chain;
545     }
546 
547     /* ------------------------------------------------------------ */
548     /**
549      * @return Returns the initializeAtStart.
550      * @deprecated
551      */
552     public boolean isInitializeAtStart()
553     {
554         return false;
555     }
556     
557     /* ------------------------------------------------------------ */
558     /**
559      * @param initializeAtStart The initializeAtStart to set.
560      * @deprecated
561      */
562     public void setInitializeAtStart(boolean initializeAtStart)
563     {
564     }
565 
566     /* ------------------------------------------------------------ */
567     /**
568      * @return true if the handler is started and there are no unavailable servlets 
569      */
570     public boolean isAvailable()
571     {
572         if (!isStarted())
573             return false;
574         ServletHolder[] holders = getServlets();
575         for (int i=0;i<holders.length;i++)
576         {
577             ServletHolder holder = holders[i];
578             if (holder!=null && !holder.isAvailable())
579                 return false;
580         }
581         return true;
582     }
583     
584     /* ------------------------------------------------------------ */
585     /**
586      * @param start True if this handler will start with unavailable servlets
587      */
588     public void setStartWithUnavailable(boolean start)
589     {
590         _startWithUnavailable=start;
591     }
592     
593     /* ------------------------------------------------------------ */
594     /**
595      * @return True if this handler will start with unavailable servlets
596      */
597     public boolean isStartWithUnavailable()
598     {
599         return _startWithUnavailable;
600     }
601     
602     
603     
604     /* ------------------------------------------------------------ */
605     /** Initialize filters and load-on-startup servlets.
606      * Called automatically from start if autoInitializeServlet is true.
607      */
608     public void initialize()
609         throws Exception
610     {
611         MultiException mx = new MultiException();
612 
613         // Start filters
614         if (_filters!=null)
615         {
616             for (int i=0;i<_filters.length; i++)
617                 _filters[i].start();
618         }
619         
620         if (_servlets!=null)
621         {
622             // Sort and Initialize servlets
623             ServletHolder[] servlets = (ServletHolder[])_servlets.clone();
624             Arrays.sort(servlets);
625             for (int i=0; i<servlets.length; i++)
626             {
627                 try
628                 {
629                     if (servlets[i].getClassName()==null && servlets[i].getForcedPath()!=null)
630                     {
631                         ServletHolder forced_holder = (ServletHolder)_servletPathMap.match(servlets[i].getForcedPath());
632                         if (forced_holder==null || forced_holder.getClassName()==null)
633                         {    
634                             mx.add(new IllegalStateException("No forced path servlet for "+servlets[i].getForcedPath()));
635                             continue;
636                         }
637                         servlets[i].setClassName(forced_holder.getClassName());
638                     }
639                     
640                     servlets[i].start();
641                 }
642                 catch(Throwable e)
643                 {
644                     Log.debug(Log.EXCEPTION,e);
645                     mx.add(e);
646                 }
647             } 
648             mx.ifExceptionThrow();  
649         }
650     }
651     
652     /* ------------------------------------------------------------ */
653     /**
654      * @return Returns the filterChainsCached.
655      */
656     public boolean isFilterChainsCached()
657     {
658         return _filterChainsCached;
659     }
660 
661     /* ------------------------------------------------------------ */
662     /**
663      * @see also newServletHolder(Class)
664      */
665     public ServletHolder newServletHolder()
666     {
667         return new ServletHolder();
668     }
669     
670     /* ------------------------------------------------------------ */
671     public ServletHolder newServletHolder(Class servlet)
672     {
673         return new ServletHolder(servlet);
674     }
675     
676     /* ------------------------------------------------------------ */
677     /** conveniance method to add a servlet.
678      * @return The servlet holder.
679      */
680     public ServletHolder addServletWithMapping (String className,String pathSpec)
681     {
682         ServletHolder holder = newServletHolder(null);
683         holder.setName(className+"-"+holder.hashCode());
684         holder.setClassName(className);
685         
686         addServletWithMapping(holder,pathSpec);
687         
688         return holder;
689     }   
690     
691     /* ------------------------------------------------------------ */
692     /** conveniance method to add a servlet.
693      * @return The servlet holder.
694      */
695     public ServletHolder addServletWithMapping (Class servlet,String pathSpec)
696     {
697         ServletHolder holder = newServletHolder(servlet);
698         setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class));
699         
700         addServletWithMapping(holder,pathSpec);
701         
702         return holder;
703     }   
704     
705     /* ------------------------------------------------------------ */
706     /** conveniance method to add a servlet.
707      * @param name
708      * @param className
709      * @param pathSpec
710      * @return The servlet holder.
711      */
712     public void addServletWithMapping (ServletHolder servlet,String pathSpec)
713     {
714         ServletHolder[] holders=getServlets();
715         if (holders!=null)
716             holders = (ServletHolder[])holders.clone();
717         
718         try
719         {
720             setServlets((ServletHolder[])LazyList.addToArray(holders, servlet, ServletHolder.class));
721             
722             ServletMapping mapping = new ServletMapping();
723             mapping.setServletName(servlet.getName());
724             mapping.setPathSpec(pathSpec);
725             setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class));
726         }
727         catch (Exception e)
728         {
729             setServlets(holders);
730             if (e instanceof RuntimeException)
731                 throw (RuntimeException)e;
732             throw new RuntimeException(e);
733         }
734     }
735 
736     /* ------------------------------------------------------------ */
737     /** Convenience method to add a servlet with a servlet mapping.
738      * @param className
739      * @param pathSpec
740      * @return
741      * @deprecated
742      */
743     public ServletHolder addServlet (String className, String pathSpec)
744     {
745         return addServletWithMapping (className, pathSpec);
746     }
747 
748     
749     /* ------------------------------------------------------------ */    
750     /**Convenience method to add a pre-constructed ServletHolder.
751      * @param holder
752      */
753     public void addServlet(ServletHolder holder)
754     {
755         setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class));
756     }
757     
758     /* ------------------------------------------------------------ */    
759     /** Convenience method to add a pre-constructed ServletMapping.
760      * @param mapping
761      */
762     public void addServletMapping (ServletMapping mapping)
763     {
764         setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class));
765     }
766     
767     /* ------------------------------------------------------------ */
768     public FilterHolder newFilterHolder(Class filter)
769     {
770         return new FilterHolder(filter);
771     }
772     
773     /* ------------------------------------------------------------ */
774     /** 
775      * @see {@link #newFilterHolder(Class)}
776      */
777     public FilterHolder newFilterHolder()
778     {
779         return new FilterHolder();
780     }
781 
782     /* ------------------------------------------------------------ */
783     public FilterHolder getFilter(String name)
784     {
785         return (FilterHolder)_filterNameMap.get(name);
786     }
787     
788     /* ------------------------------------------------------------ */
789     /** conveniance method to add a filter.
790      * @param name
791      * @param className
792      * @param pathSpec
793      * @param dispatches see {@link FilterMapping#setDispatches(int)}
794      * @return The filter holder.
795      */
796     public FilterHolder addFilterWithMapping (Class filter,String pathSpec,int dispatches)
797     {
798         FilterHolder holder = newFilterHolder(filter);
799         addFilterWithMapping(holder,pathSpec,dispatches);
800         
801         return holder;
802     }
803     
804     /* ------------------------------------------------------------ */
805     /** conveniance method to add a filter.
806      * @param name
807      * @param className
808      * @param pathSpec
809      * @param dispatches see {@link FilterMapping#setDispatches(int)}
810      * @return The filter holder.
811      */
812     public FilterHolder addFilterWithMapping (String className,String pathSpec,int dispatches)
813     {
814         FilterHolder holder = newFilterHolder(null);
815         holder.setName(className+"-"+holder.hashCode());
816         holder.setClassName(className);
817         
818         addFilterWithMapping(holder,pathSpec,dispatches);
819         return holder;
820     }
821     
822     /* ------------------------------------------------------------ */
823     /** conveniance method to add a filter.
824      * @param name
825      * @param className
826      * @param pathSpec
827      * @param dispatches see {@link FilterMapping#setDispatches(int)}
828      * @return The filter holder.
829      */
830     public void addFilterWithMapping (FilterHolder holder,String pathSpec,int dispatches)
831     {
832         FilterHolder[] holders = getFilters();
833         if (holders!=null)
834             holders = (FilterHolder[])holders.clone();
835         
836         try
837         {
838             setFilters((FilterHolder[])LazyList.addToArray(holders, holder, FilterHolder.class));
839             
840             FilterMapping mapping = new FilterMapping();
841             mapping.setFilterName(holder.getName());
842             mapping.setPathSpec(pathSpec);
843             mapping.setDispatches(dispatches);
844             setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class));
845         }
846         catch (RuntimeException e)
847         {
848             setFilters(holders);
849             throw e;
850         }
851         catch (Error e)
852         {
853             setFilters(holders);
854             throw e;
855         }
856             
857     }
858     
859     /* ------------------------------------------------------------ */
860     /** Convenience method to add a filter with a mapping
861      * @param className
862      * @param pathSpec
863      * @param dispatches
864      * @return
865      * @deprecated
866      */
867     public FilterHolder addFilter (String className,String pathSpec,int dispatches)
868     {
869         return addFilterWithMapping(className, pathSpec, dispatches);
870     }
871     
872     /* ------------------------------------------------------------ */
873     /**
874      * convenience method to add a filter and mapping
875      * @param filter
876      * @param filterMapping
877      */
878     public void addFilter (FilterHolder filter, FilterMapping filterMapping)
879     {
880         if (filter != null)
881             setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class));
882         if (filterMapping != null)
883             setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), filterMapping, FilterMapping.class));
884     }
885     
886     /* ------------------------------------------------------------ */  
887     /** Convenience method to add a preconstructed FilterHolder
888      * @param filter
889      */
890     public void addFilter (FilterHolder filter)
891     {
892         if (filter != null)
893             setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class));
894     }
895     
896     /* ------------------------------------------------------------ */
897     /** Convenience method to add a preconstructed FilterMapping
898      * @param mapping
899      */
900     public void addFilterMapping (FilterMapping mapping)
901     {
902         if (mapping != null)
903             setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class));
904     }
905 
906     /* ------------------------------------------------------------ */
907     protected synchronized void updateNameMappings()
908     {   
909         // update filter name map
910         _filterNameMap.clear();
911         if (_filters!=null)
912         {   
913             for (int i=0;i<_filters.length;i++)
914             {
915                 _filterNameMap.put(_filters[i].getName(),_filters[i]);
916                 _filters[i].setServletHandler(this);
917             }
918         }
919 
920         // Map servlet names to holders
921         _servletNameMap.clear();
922         if (_servlets!=null)
923         {   
924             // update the maps
925             for (int i=0;i<_servlets.length;i++)
926             {
927                 _servletNameMap.put(_servlets[i].getName(),_servlets[i]);
928                 _servlets[i].setServletHandler(this);
929             }
930         }
931     }
932     
933     /* ------------------------------------------------------------ */
934     protected synchronized void updateMappings()
935     {   
936         // update filter mappings
937         if (_filterMappings==null)
938         {
939             _filterPathMappings=null;
940             _filterNameMappings=null;
941         }
942         else 
943         {
944             _filterPathMappings=new ArrayList();
945             _filterNameMappings=new MultiMap();
946             for (int i=0;i<_filterMappings.length;i++)
947             {
948                 FilterHolder filter_holder = (FilterHolder)_filterNameMap.get(_filterMappings[i].getFilterName());
949                 if (filter_holder==null)
950                     throw new IllegalStateException("No filter named "+_filterMappings[i].getFilterName());
951                 _filterMappings[i].setFilterHolder(filter_holder);    
952                 if (_filterMappings[i].getPathSpecs()!=null)
953                     _filterPathMappings.add(_filterMappings[i]);
954                 
955                 if (_filterMappings[i].getServletNames()!=null)
956                 {
957                     String[] names=_filterMappings[i].getServletNames();
958                     for (int j=0;j<names.length;j++)
959                     {
960                         if (names[j]!=null)
961                             _filterNameMappings.add(names[j], _filterMappings[i]);  
962                     }
963                 }
964             }
965         }
966 
967         // Map servlet paths to holders
968         if (_servletMappings==null || _servletNameMap==null)
969         {
970             _servletPathMap=null;
971         }
972         else
973         {
974             PathMap pm = new PathMap();
975             
976             // update the maps
977             for (int i=0;i<_servletMappings.length;i++)
978             {
979                 ServletHolder servlet_holder = (ServletHolder)_servletNameMap.get(_servletMappings[i].getServletName());
980                 if (servlet_holder==null)
981                     throw new IllegalStateException("No such servlet: "+_servletMappings[i].getServletName());
982                 else if (_servletMappings[i].getPathSpecs()!=null)
983                 {
984                     String[] pathSpecs = _servletMappings[i].getPathSpecs();
985                     for (int j=0;j<pathSpecs.length;j++)
986                         if (pathSpecs[j]!=null)
987                             pm.put(pathSpecs[j],servlet_holder);
988                 }
989             }
990             
991             _servletPathMap=pm;
992         }
993         
994         
995 
996         if (Log.isDebugEnabled()) 
997         {
998             Log.debug("filterNameMap="+_filterNameMap);
999             Log.debug("pathFilters="+_filterPathMappings);
1000             Log.debug("servletFilterMap="+_filterNameMappings);
1001             Log.debug("servletPathMap="+_servletPathMap);
1002             Log.debug("servletNameMap="+_servletNameMap);
1003         }
1004         
1005         try
1006         {
1007             if (isStarted())
1008                 initialize();
1009         }
1010         catch (Exception e)
1011         {
1012             throw new RuntimeException(e);
1013         }
1014     }
1015 
1016 
1017     /* ------------------------------------------------------------ */
1018     protected void notFound(HttpServletRequest request,
1019                   HttpServletResponse response)
1020         throws IOException
1021     {
1022         if(Log.isDebugEnabled())Log.debug("Not Found "+request.getRequestURI());
1023         response.sendError(HttpServletResponse.SC_NOT_FOUND);
1024     }
1025     
1026     /* ------------------------------------------------------------ */
1027     /**
1028      * @param filterChainsCached The filterChainsCached to set.
1029      */
1030     public void setFilterChainsCached(boolean filterChainsCached)
1031     {
1032         _filterChainsCached = filterChainsCached;
1033     }
1034     
1035     /* ------------------------------------------------------------ */
1036     /**
1037      * @param filterMappings The filterMappings to set.
1038      */
1039     public void setFilterMappings(FilterMapping[] filterMappings)
1040     {
1041         if (getServer()!=null)
1042             getServer().getContainer().update(this,_filterMappings,filterMappings,"filterMapping",true);
1043         _filterMappings = filterMappings;
1044         updateMappings();
1045     }
1046     
1047     /* ------------------------------------------------------------ */
1048     public synchronized void setFilters(FilterHolder[] holders)
1049     {
1050         if (getServer()!=null)
1051             getServer().getContainer().update(this,_filters,holders,"filter",true);
1052         _filters=holders;
1053         updateNameMappings();
1054     }
1055     
1056     /* ------------------------------------------------------------ */
1057     /**
1058      * @param servletMappings The servletMappings to set.
1059      */
1060     public void setServletMappings(ServletMapping[] servletMappings)
1061     {
1062         if (getServer()!=null)
1063             getServer().getContainer().update(this,_servletMappings,servletMappings,"servletMapping",true);
1064         _servletMappings = servletMappings;
1065         updateMappings();
1066     }
1067     
1068     /* ------------------------------------------------------------ */
1069     /** Set Servlets.
1070      * @param holders Array of servletsto define
1071      */
1072     public synchronized void setServlets(ServletHolder[] holders)
1073     {
1074         if (getServer()!=null)
1075             getServer().getContainer().update(this,_servlets,holders,"servlet",true);
1076         _servlets=holders;
1077         updateNameMappings();
1078     }
1079 
1080 
1081     /* ------------------------------------------------------------ */
1082     /* ------------------------------------------------------------ */
1083     private class CachedChain implements FilterChain
1084     {
1085         FilterHolder _filterHolder;
1086         CachedChain _next;
1087         ServletHolder _servletHolder;
1088 
1089         /* ------------------------------------------------------------ */
1090         CachedChain(Object filters, ServletHolder servletHolder)
1091         {
1092             if (LazyList.size(filters)>0)
1093             {
1094                 _filterHolder=(FilterHolder)LazyList.get(filters, 0);
1095                 filters=LazyList.remove(filters,0);
1096                 _next=new CachedChain(filters,servletHolder);
1097             }
1098             else
1099                 _servletHolder=servletHolder;
1100         }
1101 
1102         /* ------------------------------------------------------------ */
1103         public void doFilter(ServletRequest request, ServletResponse response) 
1104             throws IOException, ServletException
1105         {
1106             // pass to next filter
1107             if (_filterHolder!=null)
1108             {
1109                 if (Log.isDebugEnabled())
1110                     Log.debug("call filter " + _filterHolder);
1111                 Filter filter= _filterHolder.getFilter();
1112                 filter.doFilter(request, response, _next);
1113                 return;
1114             }
1115 
1116             // Call servlet
1117             if (_servletHolder != null)
1118             {
1119                 if (Log.isDebugEnabled())
1120                     Log.debug("call servlet " + _servletHolder);
1121                 _servletHolder.handle(request, response);
1122             }
1123             else // Not found
1124                 notFound((HttpServletRequest)request, (HttpServletResponse)response);
1125         }
1126         
1127         public String toString()
1128         {
1129             if (_filterHolder!=null)
1130                 return _filterHolder+"->"+_next.toString();
1131             if (_servletHolder!=null)
1132                 return _servletHolder.toString();
1133             return "null";
1134         }
1135     }  
1136     
1137     /* ------------------------------------------------------------ */
1138     /* ------------------------------------------------------------ */
1139     private class Chain implements FilterChain
1140     {
1141         int _filter= 0;
1142         Object _chain;
1143         ServletHolder _servletHolder;
1144 
1145         /* ------------------------------------------------------------ */
1146         Chain(Object filters, ServletHolder servletHolder)
1147         {
1148             _chain= filters;
1149             _servletHolder= servletHolder;
1150         }
1151 
1152         /* ------------------------------------------------------------ */
1153         public void doFilter(ServletRequest request, ServletResponse response)
1154             throws IOException, ServletException
1155         {
1156             if (Log.isDebugEnabled()) Log.debug("doFilter " + _filter);
1157 
1158             // pass to next filter
1159             if (_filter < LazyList.size(_chain))
1160             {
1161                 FilterHolder holder= (FilterHolder)LazyList.get(_chain, _filter++);
1162                 if (Log.isDebugEnabled()) Log.debug("call filter " + holder);
1163                 Filter filter= holder.getFilter();
1164                 filter.doFilter(request, response, this);
1165                 return;
1166             }
1167 
1168             // Call servlet
1169             if (_servletHolder != null)
1170             {
1171                 if (Log.isDebugEnabled()) Log.debug("call servlet " + _servletHolder);
1172                 _servletHolder.handle(request, response);
1173             }
1174             else // Not found
1175                 notFound((HttpServletRequest)request, (HttpServletResponse)response);
1176         }
1177 
1178         /* ------------------------------------------------------------ */
1179         public String toString()
1180         {
1181             StringBuilder b = new StringBuilder();
1182             for (int i=0; i<LazyList.size(_chain);i++)
1183             {
1184                 Object o=LazyList.get(_chain, i);
1185                 b.append(o.toString());
1186                 b.append("->");
1187             }
1188             b.append(_servletHolder);
1189             return b.toString();
1190         }
1191     }
1192 
1193     /* ------------------------------------------------------------ */
1194     /**
1195      * @return The maximum entries in a filter chain cache.
1196      */
1197     public int getMaxFilterChainsCacheSize()
1198     {
1199         return _maxFilterChainsCacheSize;
1200     }
1201 
1202     /* ------------------------------------------------------------ */
1203     /** Set the maximum filter chain cache size.
1204      * Filter chains are cached if {@link #isFilterChainsCached()} is true. If the max cache size
1205      * is greater than zero, then the cache is flushed whenever it grows to be this size.
1206      * 
1207      * @param maxFilterChainsCacheSize  the maximum number of entries in a filter chain cache.
1208      */
1209     public void setMaxFilterChainsCacheSize(int maxFilterChainsCacheSize)
1210     {
1211         _maxFilterChainsCacheSize = maxFilterChainsCacheSize;
1212     }
1213     
1214     /**
1215      * Customize a servlet.
1216      * 
1217      * Called before the servlet goes into service.
1218      * Subclasses of ServletHandler should override
1219      * this method.
1220      * 
1221      * @param servlet
1222      * @return
1223      * @throws Exception
1224      */
1225     public Servlet customizeServlet (Servlet servlet)
1226     throws Exception
1227     {
1228         return servlet;
1229     }
1230     
1231     
1232     public Servlet customizeServletDestroy (Servlet servlet)
1233     throws Exception
1234     {
1235         return servlet;
1236     }
1237     
1238     
1239     /**
1240      * Customize a Filter.
1241      * 
1242      * Called before the Filter goes into service.
1243      * Subclasses of ServletHandler should override
1244      * this method.
1245      * 
1246      * @param filter
1247      * @return
1248      * @throws Exception
1249      */
1250     public Filter customizeFilter (Filter filter)
1251     throws Exception
1252     {
1253         return filter;
1254     }
1255     
1256     
1257     public Filter customizeFilterDestroy (Filter filter)
1258     throws Exception
1259     {
1260         return filter;
1261     }
1262 }