View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.servlet;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  import java.util.Stack;
32  
33  import javax.servlet.MultipartConfigElement;
34  import javax.servlet.Servlet;
35  import javax.servlet.ServletConfig;
36  import javax.servlet.ServletContext;
37  import javax.servlet.ServletException;
38  import javax.servlet.ServletRegistration;
39  import javax.servlet.ServletRequest;
40  import javax.servlet.ServletResponse;
41  import javax.servlet.ServletSecurityElement;
42  import javax.servlet.SingleThreadModel;
43  import javax.servlet.UnavailableException;
44  
45  import org.eclipse.jetty.security.IdentityService;
46  import org.eclipse.jetty.security.RunAsToken;
47  import org.eclipse.jetty.server.Request;
48  import org.eclipse.jetty.server.UserIdentity;
49  import org.eclipse.jetty.server.handler.ContextHandler;
50  import org.eclipse.jetty.util.Loader;
51  import org.eclipse.jetty.util.annotation.ManagedAttribute;
52  import org.eclipse.jetty.util.annotation.ManagedObject;
53  import org.eclipse.jetty.util.log.Log;
54  import org.eclipse.jetty.util.log.Logger;
55  
56  
57  
58  
59  /* --------------------------------------------------------------------- */
60  /** Servlet Instance and Context Holder.
61   * Holds the name, params and some state of a javax.servlet.Servlet
62   * instance. It implements the ServletConfig interface.
63   * This class will organise the loading of the servlet when needed or
64   * requested.
65   *
66   *
67   */
68  @ManagedObject("Servlet Holder")
69  public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope, Comparable<ServletHolder>
70  {
71      private static final Logger LOG = Log.getLogger(ServletHolder.class);
72  
73      /* ---------------------------------------------------------------- */
74      private int _initOrder;
75      private boolean _initOnStartup=false;
76      private Map<String, String> _roleMap;
77      private String _forcedPath;
78      private String _runAsRole;
79      private RunAsToken _runAsToken;
80      private IdentityService _identityService;
81      private ServletRegistration.Dynamic _registration;
82  
83  
84      private transient Servlet _servlet;
85      private transient Config _config;
86      private transient long _unavailable;
87      private transient boolean _enabled = true;
88      private transient UnavailableException _unavailableEx;
89      public static final Map<String,String> NO_MAPPED_ROLES = Collections.emptyMap();
90  
91      /* ---------------------------------------------------------------- */
92      /** Constructor .
93       */
94      public ServletHolder()
95      {
96          super (Source.EMBEDDED);
97      }
98  
99      /* ---------------------------------------------------------------- */
100     /** Constructor .
101      */
102     public ServletHolder(Holder.Source creator)
103     {
104         super (creator);
105     }
106 
107     /* ---------------------------------------------------------------- */
108     /** Constructor for existing servlet.
109      */
110     public ServletHolder(Servlet servlet)
111     {
112         super (Source.EMBEDDED);
113         setServlet(servlet);
114     }
115 
116     /* ---------------------------------------------------------------- */
117     /** Constructor for servlet class.
118      */
119     public ServletHolder(String name, Class<? extends Servlet> servlet)
120     {
121         super (Source.EMBEDDED);
122         setName(name);
123         setHeldClass(servlet);
124     }
125 
126     /* ---------------------------------------------------------------- */
127     /** Constructor for servlet class.
128      */
129     public ServletHolder(String name, Servlet servlet)
130     {
131         super (Source.EMBEDDED);
132         setName(name);
133         setServlet(servlet);
134     }
135 
136     /* ---------------------------------------------------------------- */
137     /** Constructor for servlet class.
138      */
139     public ServletHolder(Class<? extends Servlet> servlet)
140     {
141         super (Source.EMBEDDED);
142         setHeldClass(servlet);
143     }
144 
145     /* ---------------------------------------------------------------- */
146     /**
147      * @return The unavailable exception or null if not unavailable
148      */
149     public UnavailableException getUnavailableException()
150     {
151         return _unavailableEx;
152     }
153 
154     /* ------------------------------------------------------------ */
155     public synchronized void setServlet(Servlet servlet)
156     {
157         if (servlet==null || servlet instanceof SingleThreadModel)
158             throw new IllegalArgumentException();
159 
160         _extInstance=true;
161         _servlet=servlet;
162         setHeldClass(servlet.getClass());
163         if (getName()==null)
164             setName(servlet.getClass().getName()+"-"+super.hashCode());
165     }
166 
167     /* ------------------------------------------------------------ */
168     @ManagedAttribute(value="initialization order", readonly=true)
169     public int getInitOrder()
170     {
171         return _initOrder;
172     }
173 
174     /* ------------------------------------------------------------ */
175     /** Set the initialize order.
176      * Holders with order<0, are initialized on use. Those with
177      * order>=0 are initialized in increasing order when the handler
178      * is started.
179      */
180     public void setInitOrder(int order)
181     {
182         _initOnStartup=order>0;
183         _initOrder = order;
184     }
185 
186     /* ------------------------------------------------------------ */
187     /** Comparitor by init order.
188      */
189     @Override
190     public int compareTo(ServletHolder sh)
191     {
192         if (sh==this)
193             return 0;
194         if (sh._initOrder<_initOrder)
195             return 1;
196         if (sh._initOrder>_initOrder)
197             return -1;
198 
199         int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0;
200         if (c==0)
201             c=_name.compareTo(sh._name);
202         if (c==0)
203             c=this.hashCode()>sh.hashCode()?1:-1;
204             return c;
205     }
206 
207     /* ------------------------------------------------------------ */
208     public boolean equals(Object o)
209     {
210         return o instanceof ServletHolder && compareTo((ServletHolder)o)==0;
211     }
212 
213     /* ------------------------------------------------------------ */
214     public int hashCode()
215     {
216         return _name==null?System.identityHashCode(this):_name.hashCode();
217     }
218 
219     /* ------------------------------------------------------------ */
220     /** Link a user role.
221      * Translate the role name used by a servlet, to the link name
222      * used by the container.
223      * @param name The role name as used by the servlet
224      * @param link The role name as used by the container.
225      */
226     public synchronized void setUserRoleLink(String name,String link)
227     {
228         if (_roleMap==null)
229             _roleMap=new HashMap<String, String>();
230         _roleMap.put(name,link);
231     }
232 
233     /* ------------------------------------------------------------ */
234     /** get a user role link.
235      * @param name The name of the role
236      * @return The name as translated by the link. If no link exists,
237      * the name is returned.
238      */
239     public String getUserRoleLink(String name)
240     {
241         if (_roleMap==null)
242             return name;
243         String link= _roleMap.get(name);
244         return (link==null)?name:link;
245     }
246 
247     /* ------------------------------------------------------------ */
248     public Map<String, String> getRoleMap()
249     {
250         return _roleMap == null? NO_MAPPED_ROLES : _roleMap;
251     }
252 
253     /* ------------------------------------------------------------ */
254     /**
255      * @return Returns the forcedPath.
256      */
257     @ManagedAttribute(value="forced servlet path", readonly=true)
258     public String getForcedPath()
259     {
260         return _forcedPath;
261     }
262 
263     /* ------------------------------------------------------------ */
264     /**
265      * @param forcedPath The forcedPath to set.
266      */
267     public void setForcedPath(String forcedPath)
268     {
269         _forcedPath = forcedPath;
270     }
271 
272     public boolean isEnabled()
273     {
274         return _enabled;
275     }
276 
277 
278     public void setEnabled(boolean enabled)
279     {
280         _enabled = enabled;
281     }
282 
283 
284     /* ------------------------------------------------------------ */
285     public void doStart()
286         throws Exception
287     {
288         _unavailable=0;
289         if (!_enabled)
290             return;
291         //check servlet has a class (ie is not a preliminary registration). If preliminary, fail startup.
292         try
293         {
294             super.doStart();
295         }
296         catch (UnavailableException ue)
297         {
298             makeUnavailable(ue);
299             throw ue;
300         }
301 
302         try
303         {
304             checkServletType();
305         }
306         catch (UnavailableException ue)
307         {
308             makeUnavailable(ue);
309             if (!_servletHandler.isStartWithUnavailable())
310                 throw ue; //servlet is not an instance of javax.servlet.Servlet
311         }
312 
313 
314         _identityService = _servletHandler.getIdentityService();
315         if (_identityService!=null && _runAsRole!=null)
316             _runAsToken=_identityService.newRunAsToken(_runAsRole);
317 
318         _config=new Config();
319 
320         if (_class!=null && javax.servlet.SingleThreadModel.class.isAssignableFrom(_class))
321             _servlet = new SingleThreadedWrapper();
322 
323         if (_extInstance || _initOnStartup)
324         {
325             try
326             {
327                 initServlet();
328             }
329             catch(Exception e)
330             {
331                 if (_servletHandler.isStartWithUnavailable())
332                     LOG.ignore(e);
333                 else
334                     throw e;
335             }
336         }
337     }
338 
339     /* ------------------------------------------------------------ */
340     public void doStop()
341         throws Exception
342     {
343         Object old_run_as = null;
344         if (_servlet!=null)
345         {
346             try
347             {
348                 if (_identityService!=null)
349                     old_run_as=_identityService.setRunAs(_identityService.getSystemUserIdentity(),_runAsToken);
350 
351                 destroyInstance(_servlet);
352             }
353             catch (Exception e)
354             {
355                 LOG.warn(e);
356             }
357             finally
358             {
359                 if (_identityService!=null)
360                     _identityService.unsetRunAs(old_run_as);
361             }
362         }
363 
364         if (!_extInstance)
365             _servlet=null;
366 
367         _config=null;
368     }
369 
370     /* ------------------------------------------------------------ */
371     @Override
372     public void destroyInstance (Object o)
373     throws Exception
374     {
375         if (o==null)
376             return;
377         Servlet servlet =  ((Servlet)o);
378         servlet.destroy();
379         getServletHandler().destroyServlet(servlet);
380     }
381 
382     /* ------------------------------------------------------------ */
383     /** Get the servlet.
384      * @return The servlet
385      */
386     public synchronized Servlet getServlet()
387         throws ServletException
388     {
389         // Handle previous unavailability
390         if (_unavailable!=0)
391         {
392             if (_unavailable<0 || _unavailable>0 && System.currentTimeMillis()<_unavailable)
393                 throw _unavailableEx;
394             _unavailable=0;
395             _unavailableEx=null;
396         }
397 
398         if (_servlet==null)
399             initServlet();
400         return _servlet;
401     }
402 
403     /* ------------------------------------------------------------ */
404     /** Get the servlet instance (no initialization done).
405      * @return The servlet or null
406      */
407     public Servlet getServletInstance()
408     {
409         return _servlet;
410     }
411 
412     /* ------------------------------------------------------------ */
413     /**
414      * Check to ensure class of servlet is acceptable.
415      * @throws UnavailableException
416      */
417     public void checkServletType ()
418         throws UnavailableException
419     {
420         if (_class==null || !javax.servlet.Servlet.class.isAssignableFrom(_class))
421         {
422             throw new UnavailableException("Servlet "+_class+" is not a javax.servlet.Servlet");
423         }
424     }
425 
426     /* ------------------------------------------------------------ */
427     /**
428      * @return true if the holder is started and is not unavailable
429      */
430     public boolean isAvailable()
431     {
432         if (isStarted()&& _unavailable==0)
433             return true;
434         try
435         {
436             getServlet();
437         }
438         catch(Exception e)
439         {
440             LOG.ignore(e);
441         }
442 
443         return isStarted()&& _unavailable==0;
444     }
445 
446     /* ------------------------------------------------------------ */
447     private void makeUnavailable(UnavailableException e)
448     {
449         if (_unavailableEx==e && _unavailable!=0)
450             return;
451 
452         _servletHandler.getServletContext().log("unavailable",e);
453 
454         _unavailableEx=e;
455         _unavailable=-1;
456         if (e.isPermanent())
457             _unavailable=-1;
458         else
459         {
460             if (_unavailableEx.getUnavailableSeconds()>0)
461                 _unavailable=System.currentTimeMillis()+1000*_unavailableEx.getUnavailableSeconds();
462             else
463                 _unavailable=System.currentTimeMillis()+5000; // TODO configure
464         }
465     }
466 
467     /* ------------------------------------------------------------ */
468 
469     private void makeUnavailable(final Throwable e)
470     {
471         if (e instanceof UnavailableException)
472             makeUnavailable((UnavailableException)e);
473         else
474         {
475             ServletContext ctx = _servletHandler.getServletContext();
476             if (ctx==null)
477                 LOG.info("unavailable",e);
478             else
479                 ctx.log("unavailable",e);
480             _unavailableEx=new UnavailableException(String.valueOf(e),-1)
481             {
482                 {
483                     initCause(e);
484                 }
485             };
486             _unavailable=-1;
487         }
488     }
489 
490     /* ------------------------------------------------------------ */
491     private void initServlet()
492     	throws ServletException
493     {
494         Object old_run_as = null;
495         try
496         {
497             if (_servlet==null)
498                 _servlet=newInstance();
499             if (_config==null)
500                 _config=new Config();
501 
502             // Handle run as
503             if (_identityService!=null)
504             {
505                 old_run_as=_identityService.setRunAs(_identityService.getSystemUserIdentity(),_runAsToken);
506             }
507 
508             // Handle configuring servlets that implement org.apache.jasper.servlet.JspServlet
509             if (isJspServlet())
510             {
511                 initJspServlet();
512             }
513 
514             initMultiPart();
515 
516             LOG.debug("Filter.init {}",_servlet);
517             _servlet.init(_config);
518         }
519         catch (UnavailableException e)
520         {
521             makeUnavailable(e);
522             _servlet=null;
523             _config=null;
524             throw e;
525         }
526         catch (ServletException e)
527         {
528             makeUnavailable(e.getCause()==null?e:e.getCause());
529             _servlet=null;
530             _config=null;
531             throw e;
532         }
533         catch (Exception e)
534         {
535             makeUnavailable(e);
536             _servlet=null;
537             _config=null;
538             throw new ServletException(this.toString(),e);
539         }
540         finally
541         {
542             // pop run-as role
543             if (_identityService!=null)
544                 _identityService.unsetRunAs(old_run_as);
545         }
546     }
547 
548 
549     /* ------------------------------------------------------------ */
550     /**
551      * @throws Exception
552      */
553     protected void initJspServlet () throws Exception
554     {
555         ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext());
556             
557         /* Set the webapp's classpath for Jasper */
558         ch.setAttribute("org.apache.catalina.jsp_classpath", ch.getClassPath());
559 
560         /* Set the system classpath for Jasper */
561         setInitParameter("com.sun.appserv.jsp.classpath", Loader.getClassPath(ch.getClassLoader().getParent()));
562 
563         /* Set up other classpath attribute */
564         if ("?".equals(getInitParameter("classpath")))
565         {
566             String classpath = ch.getClassPath();
567             LOG.debug("classpath=" + classpath);
568             if (classpath != null)
569                 setInitParameter("classpath", classpath);
570         }
571     }
572 
573     /* ------------------------------------------------------------ */
574     /**
575      * Register a ServletRequestListener that will ensure tmp multipart
576      * files are deleted when the request goes out of scope.
577      * 
578      * @throws Exception
579      */
580     protected void initMultiPart () throws Exception
581     {
582         //if this servlet can handle multipart requests, ensure tmp files will be
583         //cleaned up correctly
584         if (((Registration)getRegistration()).getMultipartConfig() != null)
585         {
586             //Register a listener to delete tmp files that are created as a result of this
587             //servlet calling Request.getPart() or Request.getParts()
588 
589             ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext());
590             ch.addEventListener(new Request.MultiPartCleanerListener());
591         }
592     }
593 
594     /* ------------------------------------------------------------ */
595     /**
596      * @see org.eclipse.jetty.server.UserIdentity.Scope#getContextPath()
597      */
598     @Override
599     public String getContextPath()
600     {
601         return _config.getServletContext().getContextPath();
602     }
603 
604     /* ------------------------------------------------------------ */
605     /**
606      * @see org.eclipse.jetty.server.UserIdentity.Scope#getRoleRefMap()
607      */
608     @Override
609     public Map<String, String> getRoleRefMap()
610     {
611         return _roleMap;
612     }
613 
614     /* ------------------------------------------------------------ */
615     @ManagedAttribute(value="role to run servlet as", readonly=true)
616     public String getRunAsRole()
617     {
618         return _runAsRole;
619     }
620 
621     /* ------------------------------------------------------------ */
622     public void setRunAsRole(String role)
623     {
624         _runAsRole = role;
625     }
626 
627     /* ------------------------------------------------------------ */
628     /** Service a request with this servlet.
629      */
630     public void handle(Request baseRequest,
631                        ServletRequest request,
632                        ServletResponse response)
633         throws ServletException,
634                UnavailableException,
635                IOException
636     {
637         if (_class==null)
638             throw new UnavailableException("Servlet Not Initialized");
639 
640         Servlet servlet=_servlet;
641         synchronized(this)
642         {
643             if (_unavailable!=0 || !_initOnStartup)
644                 servlet=getServlet();
645             if (servlet==null)
646                 throw new UnavailableException("Could not instantiate "+_class);
647         }
648 
649         // Service the request
650         boolean servlet_error=true;
651         Object old_run_as = null;
652         boolean suspendable = baseRequest.isAsyncSupported();
653         try
654         {
655             // Handle aliased path
656             if (_forcedPath!=null)
657                 // TODO complain about poor naming to the Jasper folks
658                 request.setAttribute("org.apache.catalina.jsp_file",_forcedPath);
659 
660             // Handle run as
661             if (_identityService!=null)
662                 old_run_as=_identityService.setRunAs(baseRequest.getResolvedUserIdentity(),_runAsToken);
663 
664             if (!isAsyncSupported())
665                 baseRequest.setAsyncSupported(false);
666 
667             MultipartConfigElement mpce = ((Registration)getRegistration()).getMultipartConfig();
668             if (mpce != null)
669                 request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
670 
671             servlet.service(request,response);
672             servlet_error=false;
673         }
674         catch(UnavailableException e)
675         {
676             makeUnavailable(e);
677             throw _unavailableEx;
678         }
679         finally
680         {
681             baseRequest.setAsyncSupported(suspendable);
682 
683             // pop run-as role
684             if (_identityService!=null)
685                 _identityService.unsetRunAs(old_run_as);
686 
687             // Handle error params.
688             if (servlet_error)
689                 request.setAttribute("javax.servlet.error.servlet_name",getName());
690         }
691     }
692 
693 
694     /* ------------------------------------------------------------ */
695     private boolean isJspServlet ()
696     {
697         if (_servlet == null)
698             return false;
699 
700         Class c = _servlet.getClass();
701 
702         boolean result = false;
703         while (c != null && !result)
704         {
705             result = isJspServlet(c.getName());
706             c = c.getSuperclass();
707         }
708 
709         return result;
710     }
711 
712 
713     /* ------------------------------------------------------------ */
714     private boolean isJspServlet (String classname)
715     {
716         if (classname == null)
717             return false;
718         return ("org.apache.jasper.servlet.JspServlet".equals(classname));
719     }
720 
721 
722     /* ------------------------------------------------------------ */
723     /* ------------------------------------------------------------ */
724     /* ------------------------------------------------------------ */
725     protected class Config extends HolderConfig implements ServletConfig
726     {
727         /* -------------------------------------------------------- */
728         @Override
729         public String getServletName()
730         {
731             return getName();
732         }
733 
734     }
735 
736     /* -------------------------------------------------------- */
737     /* -------------------------------------------------------- */
738     /* -------------------------------------------------------- */
739     public class Registration extends HolderRegistration implements ServletRegistration.Dynamic
740     {
741         protected MultipartConfigElement _multipartConfig;
742 
743         @Override
744         public Set<String> addMapping(String... urlPatterns)
745         {
746             illegalStateIfContextStarted();
747             Set<String> clash=null;
748             for (String pattern : urlPatterns)
749             {
750                 ServletMapping mapping = _servletHandler.getServletMapping(pattern);
751                 if (mapping!=null)
752                 {
753                     //if the servlet mapping was from a default descriptor, then allow it to be overridden
754                     if (!mapping.isDefault())
755                     {
756                         if (clash==null)
757                             clash=new HashSet<String>();
758                         clash.add(pattern);
759                     }
760                 }
761             }
762 
763             //if there were any clashes amongst the urls, return them
764             if (clash!=null)
765                 return clash;
766 
767             //otherwise apply all of them
768             ServletMapping mapping = new ServletMapping();
769             mapping.setServletName(ServletHolder.this.getName());
770             mapping.setPathSpecs(urlPatterns);
771             _servletHandler.addServletMapping(mapping);
772 
773             return Collections.emptySet();
774         }
775 
776         @Override
777         public Collection<String> getMappings()
778         {
779             ServletMapping[] mappings =_servletHandler.getServletMappings();
780             List<String> patterns=new ArrayList<String>();
781             if (mappings!=null)
782             {
783                 for (ServletMapping mapping : mappings)
784                 {
785                     if (!mapping.getServletName().equals(getName()))
786                         continue;
787                     String[] specs=mapping.getPathSpecs();
788                     if (specs!=null && specs.length>0)
789                         patterns.addAll(Arrays.asList(specs));
790                 }
791             }
792             return patterns;
793         }
794 
795         @Override
796         public String getRunAsRole()
797         {
798             return _runAsRole;
799         }
800 
801         @Override
802         public void setLoadOnStartup(int loadOnStartup)
803         {
804             illegalStateIfContextStarted();
805             ServletHolder.this.setInitOrder(loadOnStartup);
806         }
807 
808         public int getInitOrder()
809         {
810             return ServletHolder.this.getInitOrder();
811         }
812 
813         @Override
814         public void setMultipartConfig(MultipartConfigElement element)
815         {
816             _multipartConfig = element;
817         }
818 
819         public MultipartConfigElement getMultipartConfig()
820         {
821             return _multipartConfig;
822         }
823 
824         @Override
825         public void setRunAsRole(String role)
826         {
827             _runAsRole = role;
828         }
829 
830         @Override
831         public Set<String> setServletSecurity(ServletSecurityElement securityElement)
832         {
833             return _servletHandler.setServletSecurity(this, securityElement);
834         }
835     }
836 
837     public ServletRegistration.Dynamic getRegistration()
838     {
839         if (_registration == null)
840             _registration = new Registration();
841         return _registration;
842     }
843 
844     /* -------------------------------------------------------- */
845     /* -------------------------------------------------------- */
846     /* -------------------------------------------------------- */
847     private class SingleThreadedWrapper implements Servlet
848     {
849         Stack<Servlet> _stack=new Stack<Servlet>();
850 
851         @Override
852         public void destroy()
853         {
854             synchronized(this)
855             {
856                 while(_stack.size()>0)
857                     try { (_stack.pop()).destroy(); } catch (Exception e) { LOG.warn(e); }
858             }
859         }
860 
861         @Override
862         public ServletConfig getServletConfig()
863         {
864             return _config;
865         }
866 
867         @Override
868         public String getServletInfo()
869         {
870             return null;
871         }
872 
873         @Override
874         public void init(ServletConfig config) throws ServletException
875         {
876             synchronized(this)
877             {
878                 if(_stack.size()==0)
879                 {
880                     try
881                     {
882                         Servlet s = newInstance();
883                         s.init(config);
884                         _stack.push(s);
885                     }
886                     catch (ServletException e)
887                     {
888                         throw e;
889                     }
890                     catch (Exception e)
891                     {
892                         throw new ServletException(e);
893                     }
894                 }
895             }
896         }
897 
898         @Override
899         public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
900         {
901             Servlet s;
902             synchronized(this)
903             {
904                 if(_stack.size()>0)
905                     s=(Servlet)_stack.pop();
906                 else
907                 {
908                     try
909                     {
910                         s = newInstance();
911                         s.init(_config);
912                     }
913                     catch (ServletException e)
914                     {
915                         throw e;
916                     }
917                     catch (Exception e)
918                     {
919                         throw new ServletException(e);
920                     }
921                 }
922             }
923 
924             try
925             {
926                 s.service(req,res);
927             }
928             finally
929             {
930                 synchronized(this)
931                 {
932                     _stack.push(s);
933                 }
934             }
935         }
936     }
937 
938     /* ------------------------------------------------------------ */
939     /**
940      * @return the newly created Servlet instance
941      * @throws ServletException
942      * @throws IllegalAccessException
943      * @throws InstantiationException
944      */
945     protected Servlet newInstance() throws ServletException, IllegalAccessException, InstantiationException
946     {
947         try
948         {
949             ServletContext ctx = getServletHandler().getServletContext();
950             if (ctx instanceof ServletContextHandler.Context)
951                 return ((ServletContextHandler.Context)ctx).createServlet(getHeldClass());
952             return getHeldClass().newInstance();
953         }
954         catch (ServletException se)
955         {
956             Throwable cause = se.getRootCause();
957             if (cause instanceof InstantiationException)
958                 throw (InstantiationException)cause;
959             if (cause instanceof IllegalAccessException)
960                 throw (IllegalAccessException)cause;
961             throw se;
962         }
963     }
964     
965 
966     /* ------------------------------------------------------------ */
967     @Override
968     public String toString()
969     {
970         return String.format("%s@%x==%s,%d,%b",_name,hashCode(),_className,_initOrder,_servlet!=null);
971     }
972 }