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