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