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  import java.io.IOException;
18  import java.security.Principal;
19  import java.util.Enumeration;
20  import java.util.HashMap;
21  import java.util.Map;
22  import java.util.Stack;
23  
24  import javax.servlet.Servlet;
25  import javax.servlet.ServletConfig;
26  import javax.servlet.ServletContext;
27  import javax.servlet.ServletException;
28  import javax.servlet.ServletRequest;
29  import javax.servlet.ServletResponse;
30  import javax.servlet.SingleThreadModel;
31  import javax.servlet.UnavailableException;
32  
33  import org.mortbay.jetty.HttpConnection;
34  import org.mortbay.jetty.Request;
35  import org.mortbay.jetty.handler.ContextHandler;
36  import org.mortbay.jetty.security.SecurityHandler;
37  import org.mortbay.jetty.security.UserRealm;
38  import org.mortbay.log.Log;
39  
40  
41  
42  /* --------------------------------------------------------------------- */
43  /** Servlet Instance and Context Holder.
44   * Holds the name, params and some state of a javax.servlet.Servlet
45   * instance. It implements the ServletConfig interface.
46   * This class will organise the loading of the servlet when needed or
47   * requested.
48   *
49   * @author Greg Wilkins
50   */
51  public class ServletHolder extends Holder
52      implements Comparable
53  {
54      /* ---------------------------------------------------------------- */
55      private int _initOrder;
56      private boolean _initOnStartup=false;
57      private Map _roleMap; 
58      private String _forcedPath;
59      private String _runAs;
60      private UserRealm _realm;    
61      
62      private transient Servlet _servlet;
63      private transient Config _config;
64      private transient long _unavailable;
65      private transient UnavailableException _unavailableEx;
66  
67      
68      /* ---------------------------------------------------------------- */
69      /** Constructor .
70       */
71      public ServletHolder()
72      {}
73      
74      /* ---------------------------------------------------------------- */
75      /** Constructor for existing servlet.
76       */
77      public ServletHolder(Servlet servlet)
78      {
79          setServlet(servlet);
80      }
81  
82      /* ---------------------------------------------------------------- */
83      /** Constructor for existing servlet.
84       */
85      public ServletHolder(Class servlet)
86      {
87          super(servlet);
88      }
89  
90      /* ---------------------------------------------------------------- */
91      /**
92       * @return The unavailable exception or null if not unavailable
93       */
94      public UnavailableException getUnavailableException()
95      {
96          return _unavailableEx;
97      }
98      
99      /* ------------------------------------------------------------ */
100     public synchronized void setServlet(Servlet servlet)
101     {
102         if (servlet==null || servlet instanceof SingleThreadModel)
103             throw new IllegalArgumentException();
104 
105         _extInstance=true;
106         _servlet=servlet;
107         setHeldClass(servlet.getClass());
108         if (getName()==null)
109             setName(servlet.getClass().getName()+"-"+super.hashCode());
110     }
111     
112     /* ------------------------------------------------------------ */
113     public int getInitOrder()
114     {
115         return _initOrder;
116     }
117 
118     /* ------------------------------------------------------------ */
119     /** Set the initialize order.
120      * Holders with order<0, are initialized on use. Those with
121      * order>=0 are initialized in increasing order when the handler
122      * is started.
123      */
124     public void setInitOrder(int order)
125     {
126         _initOnStartup=true;
127         _initOrder = order;
128     }
129 
130     /* ------------------------------------------------------------ */
131     /** Comparitor by init order.
132      */
133     public int compareTo(Object o)
134     {
135         if (o instanceof ServletHolder)
136         {
137             ServletHolder sh= (ServletHolder)o;
138             if (sh==this)
139                 return 0;
140             if (sh._initOrder<_initOrder)
141                 return 1;
142             if (sh._initOrder>_initOrder)
143                 return -1;
144             
145             int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0;
146             if (c==0)
147                 c=_name.compareTo(sh._name);
148             if (c==0)
149                 c=this.hashCode()>o.hashCode()?1:-1;
150             return c;
151         }
152         return 1;
153     }
154 
155     /* ------------------------------------------------------------ */
156     public boolean equals(Object o)
157     {
158         return compareTo(o)==0;
159     }
160 
161     /* ------------------------------------------------------------ */
162     public int hashCode()
163     {
164         return _name==null?System.identityHashCode(this):_name.hashCode();
165     }
166 
167     /* ------------------------------------------------------------ */
168     /** Link a user role.
169      * Translate the role name used by a servlet, to the link name
170      * used by the container.
171      * @param name The role name as used by the servlet
172      * @param link The role name as used by the container.
173      */
174     public synchronized void setUserRoleLink(String name,String link)
175     {
176         if (_roleMap==null)
177             _roleMap=new HashMap();
178         _roleMap.put(name,link);
179     }
180     
181     /* ------------------------------------------------------------ */
182     /** get a user role link.
183      * @param name The name of the role
184      * @return The name as translated by the link. If no link exists,
185      * the name is returned.
186      */
187     public String getUserRoleLink(String name)
188     {
189         if (_roleMap==null)
190             return name;
191         String link=(String)_roleMap.get(name);
192         return (link==null)?name:link;
193     }
194 
195     /* ------------------------------------------------------------ */
196     public Map getRoleMap()
197     {
198         return _roleMap;
199     }
200     
201     /* ------------------------------------------------------------ */
202     /** 
203      * @param role Role name that is added to UserPrincipal when this servlet
204      * is called. 
205      */
206     public void setRunAs(String role)
207     {
208         _runAs=role;
209     }
210     
211     /* ------------------------------------------------------------ */
212     public String getRunAs()
213     {
214         return _runAs;
215     }
216     
217     /* ------------------------------------------------------------ */
218     /**
219      * @return Returns the forcedPath.
220      */
221     public String getForcedPath()
222     {
223         return _forcedPath;
224     }
225     
226     /* ------------------------------------------------------------ */
227     /**
228      * @param forcedPath The forcedPath to set.
229      */
230     public void setForcedPath(String forcedPath)
231     {
232         _forcedPath = forcedPath;
233     }
234     
235     /* ------------------------------------------------------------ */
236     public void doStart()
237     throws Exception
238     {
239         _unavailable=0;
240         try
241         {
242             super.doStart();
243             checkServletType();
244         }
245         catch (UnavailableException ue)
246         {
247             makeUnavailable(ue);
248         }
249 
250         _config=new Config();
251 
252         if (_runAs!=null)
253             _realm=((SecurityHandler)(ContextHandler.getCurrentContext()
254                     .getContextHandler().getChildHandlerByClass(SecurityHandler.class))).getUserRealm();
255 
256         if (javax.servlet.SingleThreadModel.class.isAssignableFrom(_class))
257             _servlet = new SingleThreadedWrapper();
258 
259         if (_extInstance || _initOnStartup)
260         {
261             try
262             {
263                 initServlet();
264             }
265             catch(Exception e)
266             {
267                 if (_servletHandler.isStartWithUnavailable())
268                     Log.ignore(e);
269                 else
270                     throw e;
271             }
272         }  
273     }
274 
275     /* ------------------------------------------------------------ */
276     public void doStop()
277     {
278         Principal user=null;
279         try
280         {
281             // Handle run as
282             if (_runAs!=null && _realm!=null)
283                 user=_realm.pushRole(null,_runAs);
284                 
285             if (_servlet!=null)
286             {                  
287                 try
288                 {
289                     destroyInstance(_servlet);
290                 }
291                 catch (Exception e)
292                 {
293                     Log.warn(e);
294                 }
295             }
296             
297             if (!_extInstance)
298                 _servlet=null;
299            
300             _config=null;
301         }
302         finally
303         {
304             super.doStop();
305             // pop run-as role
306             if (_runAs!=null && _realm!=null && user!=null)
307                 _realm.popRole(user); 
308         }
309     }
310 
311     /* ------------------------------------------------------------ */
312     public void destroyInstance (Object o)
313     throws Exception
314     {
315         if (o==null)
316             return;
317         Servlet servlet =  ((Servlet)o);
318         servlet.destroy();
319         getServletHandler().customizeServletDestroy(servlet);
320     }
321 
322     /* ------------------------------------------------------------ */
323     /** Get the servlet.
324      * @return The servlet
325      */
326     public synchronized Servlet getServlet()
327         throws ServletException
328     {
329         // Handle previous unavailability
330         if (_unavailable!=0)
331         {
332             if (_unavailable<0 || _unavailable>0 && System.currentTimeMillis()<_unavailable)
333                 throw _unavailableEx;
334             _unavailable=0;
335             _unavailableEx=null;
336         }
337 
338         if (_servlet==null)
339             initServlet();
340         return _servlet;
341     }
342 
343     /* ------------------------------------------------------------ */
344     /** Get the servlet instance (no initialization done).
345      * @return The servlet or null
346      */
347     public Servlet getServletInstance()
348     {
349         return _servlet;
350     }
351     
352     /* ------------------------------------------------------------ */
353     /**
354      * Check to ensure class of servlet is acceptable.
355      * @throws UnavailableException
356      */
357     public void checkServletType ()
358         throws UnavailableException
359     {
360         if (!javax.servlet.Servlet.class.isAssignableFrom(_class))
361         {
362             throw new UnavailableException("Servlet "+_class+" is not a javax.servlet.Servlet");
363         }
364     }
365 
366     /* ------------------------------------------------------------ */
367     /** 
368      * @return true if the holder is started and is not unavailable
369      */
370     public boolean isAvailable()
371     {
372         if (isStarted()&& _unavailable==0)
373             return true;
374         try 
375         {
376             getServlet();
377         }
378         catch(Exception e)
379         {
380             Log.ignore(e);
381         }
382 
383         return isStarted()&& _unavailable==0;
384     }
385     
386     /* ------------------------------------------------------------ */
387     private void makeUnavailable(UnavailableException e) 
388     {
389         if (_unavailableEx==e && _unavailable!=0)
390             return;
391 
392         _servletHandler.getServletContext().log("Unavailable "+e);
393         
394         _unavailableEx=e;
395         _unavailable=-1;
396         if (e.isPermanent())   
397             _unavailable=-1;
398         else
399         {
400             if (_unavailableEx.getUnavailableSeconds()>0)
401                 _unavailable=System.currentTimeMillis()+1000*_unavailableEx.getUnavailableSeconds();
402             else
403                 _unavailable=System.currentTimeMillis()+5000; // TODO configure
404         }
405     }
406     
407     /* ------------------------------------------------------------ */
408     private void makeUnavailable(Throwable e) 
409     {
410         if (e instanceof UnavailableException)
411             makeUnavailable((UnavailableException)e);
412         else
413         {
414             _servletHandler.getServletContext().log("unavailable",e);
415             _unavailableEx=new UnavailableException(e.toString(),-1);
416             _unavailable=-1;
417         }
418     }
419 
420     /* ------------------------------------------------------------ */
421     private void initServlet() 
422     	throws ServletException
423     {
424         Principal user=null;
425         try
426         {
427             if (_servlet==null)
428                 _servlet=(Servlet)newInstance();
429             if (_config==null)
430                 _config=new Config();
431             
432             //handle any cusomizations of the servlet, such as @postConstruct
433             if (!(_servlet instanceof SingleThreadedWrapper))
434                 _servlet = getServletHandler().customizeServlet(_servlet);
435             
436             // Handle run as
437             if (_runAs!=null && _realm!=null)
438                 user=_realm.pushRole(null,_runAs);
439             
440             _servlet.init(_config);
441         }
442         catch (UnavailableException e)
443         {
444             makeUnavailable(e);
445             _servlet=null;
446             _config=null;
447             throw e;
448         }
449         catch (ServletException e)
450         {
451             makeUnavailable(e.getCause()==null?e:e.getCause());
452             _servlet=null;
453             _config=null;
454             throw e;
455         }
456         catch (Exception e)
457         {
458             makeUnavailable(e);
459             _servlet=null;
460             _config=null;
461             throw new ServletException(e);
462         }
463         finally
464         {
465             // pop run-as role
466             if (_runAs!=null && _realm!=null && user!=null)
467                 _realm.popRole(user);
468         }
469     }
470     
471     /* ------------------------------------------------------------ */
472     /** Service a request with this servlet.
473      */
474     public void handle(ServletRequest request,
475                        ServletResponse response)
476         throws ServletException,
477                UnavailableException,
478                IOException
479     {
480         if (_class==null)
481             throw new UnavailableException("Servlet Not Initialized");
482         
483         Servlet servlet=_servlet;
484         synchronized(this)
485         {
486             if (_unavailable!=0 || !_initOnStartup)
487                 servlet=getServlet();
488             if (servlet==null)
489                 throw new UnavailableException("Could not instantiate "+_class);
490         }
491         
492         // Service the request
493         boolean servlet_error=true;
494         Principal user=null;
495         Request base_request=null;
496         try
497         {
498             // Handle aliased path
499             if (_forcedPath!=null)
500                 // TODO complain about poor naming to the Jasper folks
501                 request.setAttribute("org.apache.catalina.jsp_file",_forcedPath);
502 
503             // Handle run as
504             if (_runAs!=null && _realm!=null)
505             {
506                 base_request=HttpConnection.getCurrentConnection().getRequest();
507                 user=_realm.pushRole(base_request.getUserPrincipal(),_runAs);
508                 base_request.setUserPrincipal(user);
509             }
510             
511             servlet.service(request,response);
512             servlet_error=false;
513         }
514         catch(UnavailableException e)
515         {
516             makeUnavailable(e);
517             throw _unavailableEx;
518         }
519         finally
520         {
521             // pop run-as role
522             if (_runAs!=null && _realm!=null && user!=null && base_request!=null)
523             {
524                 user=_realm.popRole(user);
525                 base_request.setUserPrincipal(user);
526             }
527 
528             // Handle error params.
529             if (servlet_error)
530                 request.setAttribute("javax.servlet.error.servlet_name",getName());
531         }
532     }
533 
534  
535     /* ------------------------------------------------------------ */
536     /* ------------------------------------------------------------ */
537     /* ------------------------------------------------------------ */
538     class Config implements ServletConfig
539     {   
540         /* -------------------------------------------------------- */
541         public String getServletName()
542         {
543             return getName();
544         }
545         
546         /* -------------------------------------------------------- */
547         public ServletContext getServletContext()
548         {
549             return _servletHandler.getServletContext();
550         }
551 
552         /* -------------------------------------------------------- */
553         public String getInitParameter(String param)
554         {
555             return ServletHolder.this.getInitParameter(param);
556         }
557     
558         /* -------------------------------------------------------- */
559         public Enumeration getInitParameterNames()
560         {
561             return ServletHolder.this.getInitParameterNames();
562         }
563     }
564 
565     /* -------------------------------------------------------- */
566     /* -------------------------------------------------------- */
567     /* -------------------------------------------------------- */
568     private class SingleThreadedWrapper implements Servlet
569     {
570         Stack _stack=new Stack();
571         
572         public void destroy()
573         {
574             synchronized(this)
575             {
576                 while(_stack.size()>0)
577                     try { ((Servlet)_stack.pop()).destroy(); } catch (Exception e) { Log.warn(e); }
578             }
579         }
580 
581         public ServletConfig getServletConfig()
582         {
583             return _config;
584         }
585 
586         public String getServletInfo()
587         {
588             return null;
589         }
590 
591         public void init(ServletConfig config) throws ServletException
592         {
593             synchronized(this)
594             {
595                 if(_stack.size()==0)
596                 {
597                     try
598                     {
599                         Servlet s = (Servlet) newInstance();
600                         s = getServletHandler().customizeServlet(s);
601                         s.init(config);
602                         _stack.push(s);
603                     }
604                     catch (ServletException e)
605                     {
606                         throw e;
607                     }
608                     catch (Exception e)
609                     {
610                         throw new ServletException(e);
611                     }
612                 }
613             }
614         }
615 
616         public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
617         {
618             Servlet s;
619             synchronized(this)
620             {
621                 if(_stack.size()>0)
622                     s=(Servlet)_stack.pop();
623                 else
624                 {
625                     try
626                     {
627                         s = (Servlet) newInstance();
628                         s = getServletHandler().customizeServlet(s);
629                         s.init(_config);
630                     }
631                     catch (ServletException e)
632                     {
633                         throw e;
634                     }
635                     catch (IOException e)
636                     {
637                         throw e;
638                     }
639                     catch (Exception e)
640                     {
641                         throw new ServletException(e);
642                     }
643                 }
644             }
645             
646             try
647             {
648                 s.service(req,res);
649             }
650             finally
651             {
652                 synchronized(this)
653                 {
654                     _stack.push(s);
655                 }
656             }
657         }
658         
659     }
660 }
661 
662 
663 
664 
665