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