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