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