View Javadoc

1   // ========================================================================
2   // Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.servlet;
15  
16  import java.io.IOException;
17  import java.util.ArrayList;
18  import java.util.Arrays;
19  import java.util.Collection;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  import java.util.Stack;
27  
28  
29  import javax.servlet.Servlet;
30  import javax.servlet.ServletConfig;
31  import javax.servlet.ServletException;
32  import org.eclipse.jetty.servlet.api.ServletRegistration;
33  
34  import javax.servlet.ServletContext;
35  import javax.servlet.ServletRequest;
36  import javax.servlet.ServletResponse;
37  import javax.servlet.SingleThreadModel;
38  import javax.servlet.UnavailableException;
39  import org.eclipse.jetty.security.IdentityService;
40  import org.eclipse.jetty.security.RunAsToken;
41  import org.eclipse.jetty.server.Request;
42  import org.eclipse.jetty.server.UserIdentity;
43  import org.eclipse.jetty.util.log.Log;
44  import org.eclipse.jetty.util.log.Logger;
45  
46  
47  
48  
49  /* --------------------------------------------------------------------- */
50  /** Servlet Instance and Context Holder.
51   * Holds the name, params and some state of a javax.servlet.Servlet
52   * instance. It implements the ServletConfig interface.
53   * This class will organise the loading of the servlet when needed or
54   * requested.
55   *
56   * 
57   */
58  public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope, Comparable
59  {
60      private static final Logger LOG = Log.getLogger(ServletHolder.class);
61  
62      /* ---------------------------------------------------------------- */
63      private int _initOrder;
64      private boolean _initOnStartup=false;
65      private Map<String, String> _roleMap;
66      private String _forcedPath;
67      private String _runAsRole;
68      private RunAsToken _runAsToken;
69      private IdentityService _identityService;
70      private ServletRegistration.Dynamic _registration;
71      
72      
73      private transient Servlet _servlet;
74      private transient Config _config;
75      private transient long _unavailable;
76      private transient UnavailableException _unavailableEx;
77      public static final Map<String,String> NO_MAPPED_ROLES = Collections.emptyMap();
78  
79      /* ---------------------------------------------------------------- */
80      /** Constructor .
81       */
82      public ServletHolder()
83      {
84      }
85      
86      
87      /* ---------------------------------------------------------------- */
88      /** Constructor for existing servlet.
89       */
90      public ServletHolder(Servlet servlet)
91      {
92          setServlet(servlet);
93      }
94  
95      /* ---------------------------------------------------------------- */
96      /** Constructor for existing servlet.
97       */
98      public ServletHolder(Class<? extends Servlet> servlet)
99      {
100         setHeldClass(servlet);
101     }
102 
103     /* ---------------------------------------------------------------- */
104     /**
105      * @return The unavailable exception or null if not unavailable
106      */
107     public UnavailableException getUnavailableException()
108     {
109         return _unavailableEx;
110     }
111     
112     /* ------------------------------------------------------------ */
113     public synchronized void setServlet(Servlet servlet)
114     {
115         if (servlet==null || servlet instanceof SingleThreadModel)
116             throw new IllegalArgumentException();
117 
118         _extInstance=true;
119         _servlet=servlet;
120         setHeldClass(servlet.getClass());
121         if (getName()==null)
122             setName(servlet.getClass().getName()+"-"+super.hashCode());
123     }
124     
125     /* ------------------------------------------------------------ */
126     public int getInitOrder()
127     {
128         return _initOrder;
129     }
130 
131     /* ------------------------------------------------------------ */
132     /** Set the initialize order.
133      * Holders with order<0, are initialized on use. Those with
134      * order>=0 are initialized in increasing order when the handler
135      * is started.
136      */
137     public void setInitOrder(int order)
138     {
139         _initOnStartup=true;
140         _initOrder = order;
141     }
142     
143     public boolean isSetInitOrder()
144     {
145         return _initOnStartup;
146     }
147 
148     /* ------------------------------------------------------------ */
149     /** Comparitor by init order.
150      */
151     public int compareTo(Object o)
152     {
153         if (o instanceof ServletHolder)
154         {
155             ServletHolder sh= (ServletHolder)o;
156             if (sh==this)
157                 return 0;
158             if (sh._initOrder<_initOrder)
159                 return 1;
160             if (sh._initOrder>_initOrder)
161                 return -1;
162             
163             int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0;
164             if (c==0)
165                 c=_name.compareTo(sh._name);
166             if (c==0)
167                 c=this.hashCode()>o.hashCode()?1:-1;
168             return c;
169         }
170         return 1;
171     }
172 
173     /* ------------------------------------------------------------ */
174     public boolean equals(Object o)
175     {
176         return compareTo(o)==0;
177     }
178 
179     /* ------------------------------------------------------------ */
180     public int hashCode()
181     {
182         return _name==null?System.identityHashCode(this):_name.hashCode();
183     }
184 
185     /* ------------------------------------------------------------ */
186     /** Link a user role.
187      * Translate the role name used by a servlet, to the link name
188      * used by the container.
189      * @param name The role name as used by the servlet
190      * @param link The role name as used by the container.
191      */
192     public synchronized void setUserRoleLink(String name,String link)
193     {
194         if (_roleMap==null)
195             _roleMap=new HashMap<String, String>();
196         _roleMap.put(name,link);
197     }
198     
199     /* ------------------------------------------------------------ */
200     /** get a user role link.
201      * @param name The name of the role
202      * @return The name as translated by the link. If no link exists,
203      * the name is returned.
204      */
205     public String getUserRoleLink(String name)
206     {
207         if (_roleMap==null)
208             return name;
209         String link= _roleMap.get(name);
210         return (link==null)?name:link;
211     }
212 
213     /* ------------------------------------------------------------ */
214     public Map<String, String> getRoleMap()
215     {
216         return _roleMap == null? NO_MAPPED_ROLES : _roleMap;
217     }
218     
219     /* ------------------------------------------------------------ */
220     /**
221      * @return Returns the forcedPath.
222      */
223     public String getForcedPath()
224     {
225         return _forcedPath;
226     }
227     
228     /* ------------------------------------------------------------ */
229     /**
230      * @param forcedPath The forcedPath to set.
231      */
232     public void setForcedPath(String forcedPath)
233     {
234         _forcedPath = forcedPath;
235     }
236     
237     /* ------------------------------------------------------------ */
238     public void doStart()
239         throws Exception
240     {
241         _unavailable=0;
242         try
243         {
244             super.doStart();
245             checkServletType();
246         }
247         catch (UnavailableException ue)
248         {
249             makeUnavailable(ue);
250         }
251 
252         _identityService = _servletHandler.getIdentityService();
253         if (_identityService!=null && _runAsRole!=null)
254             _runAsToken=_identityService.newRunAsToken(_runAsRole);
255         
256         _config=new Config();
257 
258         if (_class!=null && javax.servlet.SingleThreadModel.class.isAssignableFrom(_class))
259             _servlet = new SingleThreadedWrapper();
260 
261         if (_extInstance || _initOnStartup)
262         {
263             try
264             {
265                 initServlet();
266             }
267             catch(Exception e)
268             {
269                 if (_servletHandler.isStartWithUnavailable())
270                     LOG.ignore(e);
271                 else
272                     throw e;
273             }
274         }  
275     }
276 
277     /* ------------------------------------------------------------ */
278     public void doStop()
279         throws Exception
280     {
281         Object old_run_as = null;
282         if (_servlet!=null)
283         {       
284             try
285             {
286                 if (_identityService!=null)
287                     old_run_as=_identityService.setRunAs(_identityService.getSystemUserIdentity(),_runAsToken);
288 
289                 destroyInstance(_servlet);
290             }
291             catch (Exception e)
292             {
293                 LOG.warn(e);
294             }
295             finally
296             {
297                 if (_identityService!=null)
298                     _identityService.unsetRunAs(old_run_as);
299             }
300         }
301 
302         if (!_extInstance)
303             _servlet=null;
304 
305         _config=null;
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().destroyServlet(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         if (_servlet==null)
336             initServlet();
337         return _servlet;
338     }
339 
340     /* ------------------------------------------------------------ */
341     /** Get the servlet instance (no initialization done).
342      * @return The servlet or null
343      */
344     public Servlet getServletInstance()
345     {
346         return _servlet;
347     }
348         
349     /* ------------------------------------------------------------ */
350     /**
351      * Check to ensure class of servlet is acceptable.
352      * @throws UnavailableException
353      */
354     public void checkServletType ()
355         throws UnavailableException
356     {
357         if (_class==null || !javax.servlet.Servlet.class.isAssignableFrom(_class))
358         {
359             throw new UnavailableException("Servlet "+_class+" is not a javax.servlet.Servlet");
360         }
361     }
362 
363     /* ------------------------------------------------------------ */
364     /** 
365      * @return true if the holder is started and is not unavailable
366      */
367     public boolean isAvailable()
368     {
369         if (isStarted()&& _unavailable==0)
370             return true;
371         try 
372         {
373             getServlet();
374         }
375         catch(Exception e)
376         {
377             LOG.ignore(e);
378         }
379 
380         return isStarted()&& _unavailable==0;
381     }
382     
383     /* ------------------------------------------------------------ */
384     private void makeUnavailable(UnavailableException e)
385     {
386         if (_unavailableEx==e && _unavailable!=0)
387             return;
388 
389         _servletHandler.getServletContext().log("unavailable",e);
390 
391         _unavailableEx=e;
392         _unavailable=-1;
393         if (e.isPermanent())   
394             _unavailable=-1;
395         else
396         {
397             if (_unavailableEx.getUnavailableSeconds()>0)
398                 _unavailable=System.currentTimeMillis()+1000*_unavailableEx.getUnavailableSeconds();
399             else
400                 _unavailable=System.currentTimeMillis()+5000; // TODO configure
401         }
402     }
403 
404     /* ------------------------------------------------------------ */
405 
406     private void makeUnavailable(final Throwable e)
407     {
408         if (e instanceof UnavailableException)
409             makeUnavailable((UnavailableException)e);
410         else
411         {
412             ServletContext ctx = _servletHandler.getServletContext();
413             if (ctx==null)
414                 LOG.info("unavailable",e);
415             else
416                 ctx.log("unavailable",e);
417             _unavailableEx=new UnavailableException(String.valueOf(e),-1)
418             {
419                 {
420                     initCause(e);
421                 }
422             };
423             _unavailable=-1;
424         }
425     }
426 
427     /* ------------------------------------------------------------ */
428     private void initServlet()
429     	throws ServletException
430     {
431         Object old_run_as = null;
432         try
433         {
434             if (_servlet==null)
435                 _servlet=newInstance();
436             if (_config==null)
437                 _config=new Config();
438             
439             // Handle run as
440             if (_identityService!=null)
441             {
442                 old_run_as=_identityService.setRunAs(_identityService.getSystemUserIdentity(),_runAsToken);
443             }
444 
445             _servlet.init(_config);
446         }
447         catch (UnavailableException e)
448         {
449             makeUnavailable(e);
450             _servlet=null;
451             _config=null;
452             throw e;
453         }
454         catch (ServletException e)
455         {
456             makeUnavailable(e.getCause()==null?e:e.getCause());
457             _servlet=null;
458             _config=null;
459             throw e;
460         }
461         catch (Exception e)
462         {
463             makeUnavailable(e);
464             _servlet=null;
465             _config=null;
466             throw new ServletException(this.toString(),e);
467         }
468         finally
469         {
470             // pop run-as role
471             if (_identityService!=null)
472                 _identityService.unsetRunAs(old_run_as);
473         }
474     }
475     
476     
477     /* ------------------------------------------------------------ */
478     /**
479      * @see org.eclipse.jetty.server.UserIdentity.Scope#getContextPath()
480      */
481     public String getContextPath()
482     {
483         return _config.getServletContext().getContextPath();
484     }
485 
486     /* ------------------------------------------------------------ */
487     /**
488      * @see org.eclipse.jetty.server.UserIdentity.Scope#getRoleRefMap()
489      */
490     public Map<String, String> getRoleRefMap()
491     {
492         return _roleMap;
493     }
494 
495     /* ------------------------------------------------------------ */
496     public String getRunAsRole() 
497     {
498         return _runAsRole;
499     }
500     
501     /* ------------------------------------------------------------ */
502     public void setRunAsRole(String role) 
503     {
504         _runAsRole = role;
505     }
506     
507     /* ------------------------------------------------------------ */
508     /** Service a request with this servlet.
509      */
510     public void handle(Request baseRequest,
511                        ServletRequest request,
512                        ServletResponse response)
513         throws ServletException,
514                UnavailableException,
515                IOException
516     {
517         if (_class==null)
518             throw new UnavailableException("Servlet Not Initialized");
519         
520         Servlet servlet=_servlet;
521         synchronized(this)
522         {
523             if (_unavailable!=0 || !_initOnStartup)
524                 servlet=getServlet();
525             if (servlet==null)
526                 throw new UnavailableException("Could not instantiate "+_class);
527         }
528         
529         // Service the request
530         boolean servlet_error=true;
531         Object old_run_as = null;
532         boolean suspendable = baseRequest.isAsyncSupported();
533         try
534         {
535             // Handle aliased path
536             if (_forcedPath!=null)
537                 // TODO complain about poor naming to the Jasper folks
538                 request.setAttribute("org.apache.catalina.jsp_file",_forcedPath);
539 
540             // Handle run as
541             if (_identityService!=null)
542                 old_run_as=_identityService.setRunAs(baseRequest.getResolvedUserIdentity(),_runAsToken);
543             
544             if (!isAsyncSupported())
545                 baseRequest.setAsyncSupported(false);
546             
547             servlet.service(request,response);
548             servlet_error=false;
549         }
550         catch(UnavailableException e)
551         {
552             makeUnavailable(e);
553             throw _unavailableEx;
554         }
555         finally
556         {
557             baseRequest.setAsyncSupported(suspendable);
558             
559             // pop run-as role
560             if (_identityService!=null)
561                 _identityService.unsetRunAs(old_run_as);
562 
563             // Handle error params.
564             if (servlet_error)
565                 request.setAttribute("javax.servlet.error.servlet_name",getName());
566         }
567     }
568 
569  
570     /* ------------------------------------------------------------ */
571     /* ------------------------------------------------------------ */
572     /* ------------------------------------------------------------ */
573     protected class Config extends HolderConfig implements ServletConfig
574     {   
575         /* -------------------------------------------------------- */
576         public String getServletName()
577         {
578             return getName();
579         }
580         
581     }
582 
583     /* -------------------------------------------------------- */
584     /* -------------------------------------------------------- */
585     /* -------------------------------------------------------- */
586     public class Registration extends HolderRegistration implements ServletRegistration.Dynamic
587     {         
588         public Set<String> addMapping(String... urlPatterns)
589         {
590             illegalStateIfContextStarted();
591             Set<String> clash=null;
592             for (String pattern : urlPatterns)
593             {
594                 if (_servletHandler.getServletMapping(pattern)!=null)
595                 {
596                     if (clash==null)
597                         clash=new HashSet<String>();
598                     clash.add(pattern);
599                 }
600             }
601             
602             if (clash!=null)
603                 return clash;
604             
605             ServletMapping mapping = new ServletMapping();
606             mapping.setServletName(ServletHolder.this.getName());
607             mapping.setPathSpecs(urlPatterns);
608             _servletHandler.addServletMapping(mapping);
609             
610             return Collections.emptySet();
611         }
612 
613         public Collection<String> getMappings()
614         {
615             ServletMapping[] mappings =_servletHandler.getServletMappings();
616             List<String> patterns=new ArrayList<String>();
617             for (ServletMapping mapping : mappings)
618             {
619                 if (!mapping.getServletName().equals(getName()))
620                     continue;
621                 String[] specs=mapping.getPathSpecs();
622                 if (specs!=null && specs.length>0)
623                     patterns.addAll(Arrays.asList(specs));
624             }
625             return patterns;
626         }
627 
628         public String getRunAsRole() 
629         {
630             return _runAsRole;
631         }
632 
633         public void setLoadOnStartup(int loadOnStartup)
634         {
635             illegalStateIfContextStarted();
636             ServletHolder.this.setInitOrder(loadOnStartup);
637         }
638         
639         public int getInitOrder()
640         {
641             return ServletHolder.this.getInitOrder();
642         }
643  
644         public void setRunAsRole(String role) 
645         {
646             _runAsRole = role;
647         }
648     }
649     
650     public ServletRegistration.Dynamic getRegistration()
651     {
652         if (_registration == null)
653             _registration =  new Registration();
654         return _registration;
655     }
656     
657     /* -------------------------------------------------------- */
658     /* -------------------------------------------------------- */
659     /* -------------------------------------------------------- */
660     private class SingleThreadedWrapper implements Servlet
661     {
662         Stack<Servlet> _stack=new Stack<Servlet>();
663         
664         public void destroy()
665         {
666             synchronized(this)
667             {
668                 while(_stack.size()>0)
669                     try { (_stack.pop()).destroy(); } catch (Exception e) { LOG.warn(e); }
670             }
671         }
672 
673         public ServletConfig getServletConfig()
674         {
675             return _config;
676         }
677 
678         public String getServletInfo()
679         {
680             return null;
681         }
682 
683         public void init(ServletConfig config) throws ServletException
684         {
685             synchronized(this)
686             {
687                 if(_stack.size()==0)
688                 {
689                     try
690                     {
691                         Servlet s = newInstance();
692                         s.init(config);
693                         _stack.push(s);
694                     }
695                     catch (ServletException e)
696                     {
697                         throw e;
698                     }
699                     catch (Exception e)
700                     {
701                         throw new ServletException(e);
702                     }
703                 }
704             }
705         }
706 
707         public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
708         {
709             Servlet s;
710             synchronized(this)
711             {
712                 if(_stack.size()>0)
713                     s=(Servlet)_stack.pop();
714                 else
715                 {
716                     try
717                     {
718                         s = newInstance();
719                         s.init(_config);
720                     }
721                     catch (ServletException e)
722                     {
723                         throw e;
724                     }
725                     catch (Exception e)
726                     {
727                         throw new ServletException(e);
728                     }
729                 }
730             }
731             
732             try
733             {
734                 s.service(req,res);
735             }
736             finally
737             {
738                 synchronized(this)
739                 {
740                     _stack.push(s);
741                 }
742             }
743         }
744     }
745     
746     /* ------------------------------------------------------------ */
747     /**
748      * @return the newly created Servlet instance
749      * @throws ServletException
750      * @throws IllegalAccessException
751      * @throws InstantiationException
752      */
753     protected Servlet newInstance() throws ServletException, IllegalAccessException, InstantiationException
754     {
755         try
756         {
757             ServletContext ctx = getServletHandler().getServletContext();
758             if (ctx==null)
759                 return getHeldClass().newInstance();
760             return ((ServletContextHandler.Context)ctx).createServlet(getHeldClass());
761         }
762         catch (ServletException se)
763         {
764             Throwable cause = se.getRootCause();
765             if (cause instanceof InstantiationException)
766                 throw (InstantiationException)cause;
767             if (cause instanceof IllegalAccessException)
768                 throw (IllegalAccessException)cause;
769             throw se;
770         }
771     }
772 }
773 
774 
775 
776 
777