View Javadoc

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