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