1   // ========================================================================
2   // Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.jetty;
16  
17  import java.io.IOException;
18  import java.lang.reflect.Method;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Enumeration;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.ListIterator;
25  
26  import javax.servlet.ServletException;
27  
28  import org.mortbay.component.Container;
29  import org.mortbay.component.LifeCycle;
30  import org.mortbay.jetty.bio.SocketConnector;
31  import org.mortbay.jetty.handler.HandlerCollection;
32  import org.mortbay.jetty.handler.HandlerWrapper;
33  import org.mortbay.jetty.nio.SelectChannelConnector;
34  import org.mortbay.jetty.security.UserRealm;
35  import org.mortbay.log.Log;
36  import org.mortbay.thread.QueuedThreadPool;
37  import org.mortbay.thread.ThreadPool;
38  import org.mortbay.util.Attributes;
39  import org.mortbay.util.AttributesMap;
40  import org.mortbay.util.LazyList;
41  import org.mortbay.util.MultiException;
42  
43  /* ------------------------------------------------------------ */
44  /** Jetty HTTP Servlet Server.
45   * This class is the main class for the Jetty HTTP Servlet server.
46   * It aggregates Connectors (HTTP request receivers) and request Handlers.
47   * The server is itself a handler and a ThreadPool.  Connectors use the ThreadPool methods
48   * to run jobs that will eventually call the handle method.
49   *
50   *  @org.apache.xbean.XBean  description="Creates an embedded Jetty web server"
51   */
52  public class Server extends HandlerWrapper implements Attributes
53  {
54      private static ShutdownHookThread hookThread = new ShutdownHookThread();
55      private static String _version = (Server.class.getPackage()!=null && Server.class.getPackage().getImplementationVersion()!=null)
56          ?Server.class.getPackage().getImplementationVersion()
57          :"7.0.x";
58  
59      private ThreadPool _threadPool;
60      private Connector[] _connectors;
61      private UserRealm[] _realms;
62      private Container _container=new Container();
63      private SessionIdManager _sessionIdManager;
64      private boolean _sendServerVersion = true; //send Server: header
65      private boolean _sendDateHeader = false; //send Date: header 
66      private AttributesMap _attributes = new AttributesMap();
67      private List _dependentLifeCycles=new ArrayList();
68      private int _graceful=0;
69      
70      /* ------------------------------------------------------------ */
71      public Server()
72      {
73          setServer(this); 
74      }
75      
76      /* ------------------------------------------------------------ */
77      /** Convenience constructor
78       * Creates server and a {@link SocketConnector} at the passed port.
79       */
80      public Server(int port)
81      {
82          setServer(this);
83  
84          Connector connector=new SelectChannelConnector();
85          connector.setPort(port);
86          setConnectors(new Connector[]{connector});
87      }
88  
89  
90      /* ------------------------------------------------------------ */
91      public static String getVersion()
92      {
93          return _version;
94      }
95      
96      /* ------------------------------------------------------------ */
97      /**
98       * @return Returns the container.
99       */
100     public Container getContainer()
101     {
102         return _container;
103     }
104 
105     /* ------------------------------------------------------------ */
106     public boolean getStopAtShutdown()
107     {
108         return hookThread.contains(this);
109     }
110     
111     /* ------------------------------------------------------------ */
112     public void setStopAtShutdown(boolean stop)
113     {
114         if (stop)
115             hookThread.add(this);
116         else
117             hookThread.remove(this);
118     }
119     
120     /* ------------------------------------------------------------ */
121     /**
122      * @return Returns the connectors.
123      */
124     public Connector[] getConnectors()
125     {
126         return _connectors;
127     }
128     
129 
130     /* ------------------------------------------------------------ */
131     public void addConnector(Connector connector)
132     {
133         setConnectors((Connector[])LazyList.addToArray(getConnectors(), connector, Connector.class));
134     }
135 
136     /* ------------------------------------------------------------ */
137     /**
138      * Conveniance method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to 
139      * remove a connector.
140      * @param connector The connector to remove.
141      */
142     public void removeConnector(Connector connector) {
143         setConnectors((Connector[])LazyList.removeFromArray (getConnectors(), connector));
144     }
145 
146     /* ------------------------------------------------------------ */
147     /** Set the connectors for this server.
148      * Each connector has this server set as it's ThreadPool and its Handler.
149      * @param connectors The connectors to set.
150      */
151     public void setConnectors(Connector[] connectors)
152     {
153         if (connectors!=null)
154         {
155             for (int i=0;i<connectors.length;i++)
156                 connectors[i].setServer(this);
157         }
158         
159         _container.update(this, _connectors, connectors, "connector");
160         _connectors = connectors;
161     }
162 
163     /* ------------------------------------------------------------ */
164     /**
165      * @return Returns the threadPool.
166      */
167     public ThreadPool getThreadPool()
168     {
169         return _threadPool;
170     }
171     
172     /* ------------------------------------------------------------ */
173     /**
174      * @param threadPool The threadPool to set.
175      */
176     public void setThreadPool(ThreadPool threadPool)
177     {
178         _container.update(this,_threadPool,threadPool, "threadpool",true);
179         _threadPool = threadPool;
180     }
181 
182     /* ------------------------------------------------------------ */
183     protected void doStart() throws Exception
184     {
185         Log.info("jetty-"+_version);
186         HttpGenerator.setServerVersion(_version);
187         MultiException mex=new MultiException();
188       
189         for (int i=0;_realms !=null && i<_realms.length; i++)
190         {
191             if (_realms[i] instanceof LifeCycle)
192                 ((LifeCycle)_realms[i]).start();
193         }
194 
195         Iterator itor = _dependentLifeCycles.iterator();
196         while (itor.hasNext())
197         {   
198             try
199             {
200                 ((LifeCycle)itor.next()).start(); 
201             }
202             catch (Throwable e) {mex.add(e);}
203         }
204         
205         if (_threadPool==null)
206         {
207             QueuedThreadPool tp=new QueuedThreadPool();
208             setThreadPool(tp);
209         }
210         
211         if (_sessionIdManager!=null)
212             _sessionIdManager.start();
213         
214         try
215         {
216             if (_threadPool instanceof LifeCycle)
217                 ((LifeCycle)_threadPool).start();
218         } 
219         catch(Throwable e) { mex.add(e);}
220         
221         try 
222         { 
223             super.doStart(); 
224         } 
225         catch(Throwable e) 
226         { 
227             Log.warn("Error starting handlers",e);
228         }
229         
230         if (_connectors!=null)
231         {
232             for (int i=0;i<_connectors.length;i++)
233             {
234                 try{_connectors[i].start();}
235                 catch(Throwable e)
236                 {
237                     mex.add(e);
238                 }
239             }
240         }
241         mex.ifExceptionThrow();
242     }
243 
244     /* ------------------------------------------------------------ */
245     protected void doStop() throws Exception
246     {
247         MultiException mex=new MultiException();
248         
249         for (int i=0;_realms !=null && i<_realms.length; i++)
250         {
251             if (_realms[i] instanceof LifeCycle)
252                 ((LifeCycle)_realms[i]).stop();
253         }
254         
255         if (_graceful>0)
256         {
257             if (_connectors!=null)
258             {
259                 for (int i=_connectors.length;i-->0;)
260                 {
261                     Log.info("Graceful shutdown {}",_connectors[i]);
262                     try{_connectors[i].close();}catch(Throwable e){mex.add(e);}
263                 }
264             }
265             
266             Handler[] contexts = getChildHandlersByClass(Graceful.class);
267             for (int c=0;c<contexts.length;c++)
268             {
269                 Graceful context=(Graceful)contexts[c];
270                 Log.info("Graceful shutdown {}",context);
271                 context.setShutdown(true);
272             }
273             Thread.sleep(_graceful);
274         }
275         
276         if (_connectors!=null)
277         {
278             for (int i=_connectors.length;i-->0;)
279                 try{_connectors[i].stop();}catch(Throwable e){mex.add(e);}
280         }
281 
282         try {super.doStop(); } catch(Throwable e) { mex.add(e);}
283         
284         if (_sessionIdManager!=null)
285             _sessionIdManager.stop();
286         
287         try
288         {
289             if (_threadPool instanceof LifeCycle)
290                 ((LifeCycle)_threadPool).stop();
291         }
292         catch(Throwable e){mex.add(e);}
293         
294         if (!_dependentLifeCycles.isEmpty())
295         {
296             ListIterator itor = _dependentLifeCycles.listIterator(_dependentLifeCycles.size());
297             while (itor.hasPrevious())
298             {
299                 try
300                 {
301                     ((LifeCycle)itor.previous()).stop(); 
302                 }
303                 catch (Throwable e) {mex.add(e);}
304             }
305         }
306        
307         mex.ifExceptionThrow();
308     }
309 
310     /* ------------------------------------------------------------ */
311     /* Handle a request from a connection.
312      * Called to handle a request on the connection when either the header has been received,
313      * or after the entire request has been received (for short requests of known length).
314      */
315     public void handle(HttpConnection connection) throws IOException, ServletException
316     {
317         String target=connection.getRequest().getPathInfo();
318         if (Log.isDebugEnabled())
319         {
320             Log.debug("REQUEST "+target+" on "+connection);
321             handle(target, connection.getRequest(), connection.getResponse(), Handler.REQUEST);
322             Log.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus());
323         }
324         else
325             handle(target, connection.getRequest(), connection.getResponse(), Handler.REQUEST);
326     }
327 
328     /* ------------------------------------------------------------ */
329     public void join() throws InterruptedException 
330     {
331         getThreadPool().join();
332     }
333 
334     /* ------------------------------------------------------------ */
335     /**
336      * @return Map of realm name to UserRealm instances.
337      */
338     public UserRealm[] getUserRealms()
339     {
340         return _realms;
341     }
342     
343     /* ------------------------------------------------------------ */
344     /**
345      * @param realms Map of realm name to UserRealm instances.
346      */
347     public void setUserRealms(UserRealm[] realms)
348     {
349         _container.update(this,_realms,realms, "realm",true);
350         _realms=realms;
351     }
352     
353     /* ------------------------------------------------------------ */
354     public void addUserRealm(UserRealm realm)
355     {
356         setUserRealms((UserRealm[])LazyList.addToArray(getUserRealms(), realm, UserRealm.class));
357     }
358     
359     /* ------------------------------------------------------------ */
360     public void removeUserRealm(UserRealm realm)
361     {
362         setUserRealms((UserRealm[])LazyList.removeFromArray(getUserRealms(), realm));
363     }
364 
365     /* ------------------------------------------------------------ */
366     /* ------------------------------------------------------------ */
367     /**
368      * @return Returns the sessionIdManager.
369      */
370     public SessionIdManager getSessionIdManager()
371     {
372         return _sessionIdManager;
373     }
374 
375     /* ------------------------------------------------------------ */
376     /* ------------------------------------------------------------ */
377     /**
378      * @param sessionIdManager The sessionIdManager to set.
379      */
380     public void setSessionIdManager(SessionIdManager sessionIdManager)
381     {
382         _container.update(this,_sessionIdManager,sessionIdManager, "sessionIdManager",true);
383         _sessionIdManager = sessionIdManager;
384     }
385 
386     /* ------------------------------------------------------------ */
387     public void setSendServerVersion (boolean sendServerVersion)
388     {
389         _sendServerVersion = sendServerVersion;
390     }
391 
392     /* ------------------------------------------------------------ */
393     public boolean getSendServerVersion()
394     {
395         return _sendServerVersion;
396     }
397 
398     /* ------------------------------------------------------------ */
399     /**
400      * @param sendDateHeader
401      */
402     public void setSendDateHeader(boolean sendDateHeader)
403     {
404         _sendDateHeader = sendDateHeader;
405     }
406 
407     /* ------------------------------------------------------------ */
408     public boolean getSendDateHeader()
409     {
410         return _sendDateHeader;
411     }
412     
413     
414     /**
415      * Add a LifeCycle object to be started/stopped
416      * along with the Server.
417      * @param c
418      */
419     public void addLifeCycle (LifeCycle c)
420     {
421         if (c == null)
422             return;
423         if (!_dependentLifeCycles.contains(c)) 
424         {
425             _dependentLifeCycles.add(c);
426             _container.addBean(c);
427         }
428         try
429         {
430             if (isStarted())
431                 ((LifeCycle)c).start();
432         }
433         catch (Exception e)
434         {
435             throw new RuntimeException (e);
436         }
437     }
438     
439     /**
440      * Remove a LifeCycle object to be started/stopped 
441      * along with the Server
442      * @param c
443      */
444     public void removeLifeCycle (LifeCycle c)
445     {
446         if (c == null)
447             return;
448         _dependentLifeCycles.remove(c);
449         _container.removeBean(c);
450     }
451     
452  
453     
454     /* ------------------------------------------------------------ */
455     /* ------------------------------------------------------------ */
456     /* ------------------------------------------------------------ */
457     /**
458      * ShutdownHook thread for stopping all servers.
459      * 
460      * Thread is hooked first time list of servers is changed.
461      */
462     private static class ShutdownHookThread extends Thread
463     {
464         private boolean hooked = false;
465         private ArrayList servers = new ArrayList();
466 
467         /**
468          * Hooks this thread for shutdown.
469          * 
470          * @see java.lang.Runtime#addShutdownHook(java.lang.Thread)
471          */
472         private void createShutdownHook()
473         {
474             if (!Boolean.getBoolean("JETTY_NO_SHUTDOWN_HOOK") && !hooked)
475             {
476                 try
477                 {
478                     Method shutdownHook = java.lang.Runtime.class.getMethod("addShutdownHook", new Class[]
479                     { java.lang.Thread.class});
480                     shutdownHook.invoke(Runtime.getRuntime(), new Object[]
481                     { this});
482                     this.hooked = true;
483                 }
484                 catch (Exception e)
485                 {
486                     if (Log.isDebugEnabled())
487                         Log.debug("No shutdown hook in JVM ", e);
488                 }
489             }
490         }
491 
492         /**
493          * Add Server to servers list.
494          */
495         public boolean add(Server server)
496         {
497             createShutdownHook();
498             return this.servers.add(server);
499         }
500 
501         /**
502          * Contains Server in servers list?
503          */
504         public boolean contains(Server server)
505         {
506             return this.servers.contains(server);
507         }
508 
509         /**
510          * Append all Servers from Collection
511          */
512         public boolean addAll(Collection c)
513         {
514             createShutdownHook();
515             return this.servers.addAll(c);
516         }
517 
518         /**
519          * Clear list of Servers.
520          */
521         public void clear()
522         {
523             createShutdownHook();
524             this.servers.clear();
525         }
526 
527         /**
528          * Remove Server from list.
529          */
530         public boolean remove(Server server)
531         {
532             createShutdownHook();
533             return this.servers.remove(server);
534         }
535 
536         /**
537          * Remove all Servers in Collection from list.
538          */
539         public boolean removeAll(Collection c)
540         {
541             createShutdownHook();
542             return this.servers.removeAll(c);
543         }
544 
545         /**
546          * Stop all Servers in list.
547          */
548         public void run()
549         {
550             setName("Shutdown");
551             Log.info("Shutdown hook executing");
552             Iterator it = servers.iterator();
553             while (it.hasNext())
554             {
555                 Server svr = (Server) it.next();
556                 if (svr == null)
557                     continue;
558                 try
559                 {
560                     svr.stop();
561                 }
562                 catch (Exception e)
563                 {
564                     Log.warn(e);
565                 }
566                 Log.info("Shutdown hook complete");
567 
568                 // Try to avoid JVM crash
569                 try
570                 {
571                     Thread.sleep(1000);
572                 }
573                 catch (Exception e)
574                 {
575                     Log.warn(e);
576                 }
577             }
578         }
579     }
580 
581     
582     
583 
584     /* ------------------------------------------------------------ */
585     /**
586      */
587     public void addHandler(Handler handler)
588     {
589         if (getHandler() == null) 
590             setHandler(handler);
591         else if (getHandler() instanceof HandlerCollection)
592             ((HandlerCollection)getHandler()).addHandler(handler);
593         else
594         {
595             HandlerCollection collection=new HandlerCollection();
596             collection.setHandlers(new Handler[]{getHandler(),handler});
597             setHandler(collection);
598         }
599     }
600     
601     /* ------------------------------------------------------------ */
602     /**
603      */
604     public void removeHandler(Handler handler)
605     {
606         if (getHandler() instanceof HandlerCollection)
607             ((HandlerCollection)getHandler()).removeHandler(handler);
608     }
609 
610     /* ------------------------------------------------------------ */
611     /**
612      */
613     public Handler[] getHandlers()
614     {
615         if (getHandler() instanceof HandlerCollection)
616             return ((HandlerCollection)getHandler()).getHandlers();
617         
618         return null;
619     }
620     
621     /* ------------------------------------------------------------ */
622     /**
623      */
624     public void setHandlers(Handler[] handlers)
625     {
626         HandlerCollection collection;
627         if (getHandler() instanceof HandlerCollection)
628             collection=(HandlerCollection)getHandler();
629         else
630         {
631             collection=new HandlerCollection();
632             setHandler(collection);
633         }
634             
635         collection.setHandlers(handlers);
636     }
637 
638     /* ------------------------------------------------------------ */
639     /* 
640      * @see org.mortbay.util.AttributesMap#clearAttributes()
641      */
642     public void clearAttributes()
643     {
644         _attributes.clearAttributes();
645     }
646 
647     /* ------------------------------------------------------------ */
648     /* 
649      * @see org.mortbay.util.AttributesMap#getAttribute(java.lang.String)
650      */
651     public Object getAttribute(String name)
652     {
653         return _attributes.getAttribute(name);
654     }
655 
656     /* ------------------------------------------------------------ */
657     /* 
658      * @see org.mortbay.util.AttributesMap#getAttributeNames()
659      */
660     public Enumeration getAttributeNames()
661     {
662         return AttributesMap.getAttributeNamesCopy(_attributes);
663     }
664 
665     /* ------------------------------------------------------------ */
666     /* 
667      * @see org.mortbay.util.AttributesMap#removeAttribute(java.lang.String)
668      */
669     public void removeAttribute(String name)
670     {
671         _attributes.removeAttribute(name);
672     }
673 
674     /* ------------------------------------------------------------ */
675     /* 
676      * @see org.mortbay.util.AttributesMap#setAttribute(java.lang.String, java.lang.Object)
677      */
678     public void setAttribute(String name, Object attribute)
679     {
680         _attributes.setAttribute(name, attribute);
681     }
682 
683     /* ------------------------------------------------------------ */
684     /**
685      * @return the graceful
686      */
687     public int getGracefulShutdown()
688     {
689         return _graceful;
690     }
691 
692     /* ------------------------------------------------------------ */
693     /**
694      * Set graceful shutdown timeout.  If set, the {@link #doStop()} method will not immediately stop the 
695      * server. Instead, all {@link Connector}s will be closed so that new connections will not be accepted
696      * and all handlers that implement {@link Graceful} will be put into the shutdown mode so that no new requests
697      * will be accepted, but existing requests can complete.  The server will then wait the configured timeout 
698      * before stopping.
699      * @param timeoutMS the milliseconds to wait for existing request to complete before stopping the server.
700      * 
701      */
702     public void setGracefulShutdown(int timeoutMS)
703     {
704         _graceful=timeoutMS;
705     }
706 
707     /* ------------------------------------------------------------ */
708     /* A component that can be gracefully shutdown.
709      * Called by doStop if a {@link #setGracefulShutdown} period is set.
710      */
711     public interface Graceful
712     {
713         public void setShutdown(boolean shutdown);
714     }
715 }