KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > opensubsystems > core > application > Server


1 /*
2  * Copyright (c) 2003 - 2007 OpenSubsystems s.r.o. Slovak Republic. All rights reserved.
3  *
4  * Project: OpenSubsystems
5  *
6  * $Id: Server.java,v 1.10 2007/01/07 06:14:39 bastafidli Exp $
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; version 2 of the License.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20  */

21
22 package org.opensubsystems.core.application;
23
24 import java.io.IOException JavaDoc;
25 import java.lang.reflect.Method JavaDoc;
26 import java.net.BindException JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import java.util.Properties JavaDoc;
29 import java.util.logging.Level JavaDoc;
30 import java.util.logging.Logger JavaDoc;
31
32 import org.mortbay.http.HttpServer;
33 import org.mortbay.http.SocketListener;
34 import org.mortbay.jetty.servlet.WebApplicationContext;
35 import org.mortbay.util.InetAddrPort;
36 import org.mortbay.util.MultiException;
37 import org.opensubsystems.core.error.OSSConfigException;
38 import org.opensubsystems.core.error.OSSException;
39 import org.opensubsystems.core.error.OSSInternalErrorException;
40 import org.opensubsystems.core.util.Config;
41 import org.opensubsystems.core.util.GlobalConstants;
42 import org.opensubsystems.core.util.Log;
43 import org.opensubsystems.core.util.ProductInfo;
44 import org.opensubsystems.core.www.WebCommonConstants;
45 import org.opensubsystems.core.www.WebUtils;
46
47 /**
48  * Base class for all servers and applications developed by project
49  * OpenSubsystems.
50  *
51  * TODO: Improve: At present moment this server is tightly integrated with Jetty
52  * HTTP server mainly because it is easily embeddble. Once there is a need
53  * to support multiple servers, it can be abstracted through an intermediate
54  * interface with multiple imlementations. That's the main reason why is this
55  * class not derived from the Jetty server class but it is using it instead.
56  *
57  * The lifecycle of the server is Constructor, init(), start().
58  *
59  * The derived classes should just declare their own main method, construct the
60  * derived Server object and call startServer on it:
61  *
62  * ExampleServer srvServer;
63  *
64  * srvServer = new ExampleServer();
65  * startServer(srvServer);
66  *
67  * The web applications which should be run on the server should be configured
68  * by overriding addWebApplications method.
69  *
70  * @version $Id: Server.java,v 1.10 2007/01/07 06:14:39 bastafidli Exp $
71  * @author Miro Halas
72  * @code.reviewer Miro Halas
73  * @code.reviewed 1.6 2006/04/05 04:56:30 bastafidli
74  */

75 public abstract class Server
76 {
77    // Configuration settings ///////////////////////////////////////////////////
78

79    /**
80     * Name of the property specifying how many times we should try to restart
81     * the embedded web server if it cannot be started at the port specified by
82     * oss.webserver.port. If the port specified is the standard HTTP port 80,
83     * the server will try port 8080 and then other ports above it until an
84     * available port is found or until the specified value is reached. If the
85     * port specified is not port 80, the port number will be increased by 1 until
86     * an available port is found or until the specified value is reached. If the
87     * value is 0, the system will not attempt to restart the embedded web server
88     * if it cannot be started on the originally specified port.
89     * @see #s_iMaxServerStartRetry
90     * @see #SERVER_START_RETRYCOUNT_DEFAULT
91     */

92    public static final String JavaDoc SERVER_START_RETRYCOUNT
93                                  = "oss.server.start.retrycount";
94
95    // Constants ////////////////////////////////////////////////////////////////
96

97    /**
98     * How many try we will try to restart the server if specified port cannot
99     * be used.
100     * @see #SERVER_START_RETRYCOUNT
101     * @see #s_iMaxServerStartRetry
102     */

103    public static final int SERVER_START_RETRYCOUNT_DEFAULT = 3;
104    
105    // Attributes ///////////////////////////////////////////////////////////////
106

107    /**
108     * Port where this web server should start.
109     */

110    protected int m_iWebServerPort;
111
112    /**
113     * Web container used by this server.
114     */

115    protected HttpServer m_hsServer;
116    
117    /**
118     * This flag is true if the server is started.
119     */

120    protected boolean m_bStarted;
121    
122    // Cached values ////////////////////////////////////////////////////////////
123

124    /**
125     * Logger for this class
126     */

127    private static Logger JavaDoc s_logger = Log.getInstance(Server.class);
128
129    /**
130     * How many try we will try to restart the server if specified port cannot
131     * be used as initialized from configuration file.
132     * @see #SERVER_START_RETRYCOUNT
133     * @see #SERVER_START_RETRYCOUNT_DEFAULT
134     * @see #s_iMaxServerStartRetry
135     */

136    protected static int s_iMaxServerStartRetry;
137
138    // Constructors /////////////////////////////////////////////////////////////
139

140    /**
141     * Static initializer.
142     */

143    static
144    {
145       // Read configuration parameters
146
Properties JavaDoc prpSettings;
147
148       prpSettings = Config.getInstance().getPropertiesSafely();
149
150       s_iMaxServerStartRetry = Config.getIntPropertyInRange(
151                                          prpSettings,
152                                          SERVER_START_RETRYCOUNT,
153                                          SERVER_START_RETRYCOUNT_DEFAULT,
154                                          "Number of retries to restart web"
155                                          + " server if used port is not available",
156                                          // 0 is allowed since we start the server
157
// at least once
158
0,
159                                          Integer.MAX_VALUE);
160    }
161    
162    /**
163     * Create new instance of the server. The initialization should be done in
164     * a separate step so we can have more control over error handling.
165     *
166     * @param product - information about product which is running this server,
167     * this way we will force every application to create and
168     * publish this information in uniform way
169     */

170    public Server(
171       ProductInfo product
172    )
173    {
174       m_iWebServerPort = WebUtils.getConfiguredPort();
175       m_bStarted = false;
176       
177       GlobalConstants.setCurrentProduct(product);
178    }
179
180    /**
181     * Get the web server port.
182     *
183     * @return int
184     */

185    public int getWebServerPort(
186    )
187    {
188       return m_iWebServerPort;
189    }
190    
191    /**
192     * Set the web server port.
193     *
194     * @param iWebServerPort - new web server port
195     */

196    public void setWebServerPort(
197       int iWebServerPort
198    )
199    {
200       if (GlobalConstants.ERROR_CHECKING)
201       {
202          if ((iWebServerPort < WebCommonConstants.HTTP_PORT_MIN)
203             || (iWebServerPort > WebCommonConstants.HTTP_PORT_MAX))
204          {
205             throw new IllegalArgumentException JavaDoc("New webserver port " + iWebServerPort
206                                                + " is outside of valid range ("
207                                                + WebCommonConstants.HTTP_PORT_MIN
208                                                + " - "
209                                                + WebCommonConstants.HTTP_PORT_MAX
210                                                + ").");
211          }
212       }
213       m_iWebServerPort = iWebServerPort;
214    }
215
216    /**
217     * Initialize the server.
218     * All initializeation should be done in this method so that we can call
219     * it repetitevely with changed parameters if initialization fails.
220     *
221     * @throws OSSException - if there was an error starting persistance store
222     * @throws IOException - if there was an error accessing database properties
223     */

224    public void init(
225    ) throws OSSException,
226             IOException JavaDoc
227    {
228       // Create new HTTPServer,
229
m_hsServer = new HttpServer();
230
231       // Create listeenr which listens for incoming conenctions
232
InetAddrPort iaddrServerAdress;
233       SocketListener hlListener;
234
235       // Not sure if we want to make this configurable. At this time I prefer
236
// not to. We want to guarantee that the server always runs so for now
237
// specify some good values.
238

239       // TODO: Feature: Figure out, how to limit the listener to accept connection only
240
// from specific IP address (e.g. local host) if this should be private server
241
iaddrServerAdress = new InetAddrPort(m_iWebServerPort);
242       hlListener = new SocketListener(iaddrServerAdress);
243       // TODO: Performance: These parameters directly affect performance so
244
// figure out what are the good values since these are just taken
245
// for the default Jetty config file.
246
// TODO: Improve: Not sure if we want to make this configurable. At this
247
// time I prefer not to. We want to guarantee that the server always runs
248
// so for now specify some good values. For real highly loaded production
249
// the application will be run probably on a real server and for standalone
250
// production we should have good enough parameters here
251
hlListener.setMinThreads(80);
252       hlListener.setMaxThreads(200);
253       hlListener.setMaxIdleTimeMs(180000);
254       hlListener.setLowResourcePersistTimeMs(20000);
255       m_hsServer.addListener(hlListener);
256       
257       // TODO: Feature: Figure out, how to start here also SSL listener
258

259       addWebApplications(m_hsServer);
260    }
261    
262    /**
263     * Start the server.
264     *
265     * @throws MultiException - if there was an error starting the server
266     * @throws OSSException - if there was an error starting the server
267     */

268    public void start(
269    ) throws MultiException,
270             OSSException
271    {
272       try
273       {
274          m_hsServer.start();
275          m_bStarted = true;
276          s_logger.info("Server is ready on port " + getWebServerPort());
277       }
278       catch (MultiException meExc)
279       {
280          // Just rethrow so that the catch below doesn't get it
281
throw meExc;
282       }
283       catch (Exception JavaDoc eExc)
284       {
285          MultiException meExc = new MultiException();
286          meExc.add(eExc);
287          throw meExc;
288       }
289    }
290
291    /**
292     * Test if the server is started.
293     *
294     * @return boolean - true if started
295     */

296    public boolean isStarted(
297    )
298    {
299       return m_bStarted;
300    }
301
302    /**
303     * Stop the server.
304     *
305     * @throws OSSException - if there was en error stopping persistance store
306     * @throws InterruptedException - if the server was stopped
307     */

308    public void stop(
309    ) throws OSSException,
310             InterruptedException JavaDoc
311    {
312       try
313       {
314          m_hsServer.stop();
315          m_bStarted = false;
316       }
317       finally
318       {
319          m_hsServer.destroy();
320       }
321    }
322
323    // Helper methods ///////////////////////////////////////////////////////////
324

325    /**
326     * Start specified server. The functionality to start server was separated
327     * to this method so that derived classes can pass different kind of server
328     * to start.
329     *
330     * @param srvServer - server to start
331     * @throws OSSException - problem starting server
332     * @throws OSSException - problem initializing the server
333     */

334    protected static void startServer(
335       final Server srvServer
336    ) throws OSSException
337    {
338       int iRetry = 0;
339       boolean bServerRunning = false;
340       int iNewWebServerPort;
341       Iterator JavaDoc itrExceptions;
342       Exception JavaDoc eTemp;
343
344       // Set the shutdown hook so we can stop the server gracefully
345
// This code is taken from Jetty 1.4 class org.mortbay.jetty.Server
346
// Install it as a first thing since if the start method is overriden
347
// for thick client, it may never return and we wouldn't have chance
348
// to install this hook
349
try
350       {
351           Method JavaDoc shutdownHook = Runtime JavaDoc.class.getMethod("addShutdownHook",
352                                                          new Class JavaDoc[] {Thread JavaDoc.class});
353                      
354           Thread JavaDoc thrdHook = new Thread JavaDoc()
355                             {
356                                public void run()
357                                {
358                                   // If the server was not stopped by other
359
// means and it is still started, then stop it
360
if (srvServer.isStarted())
361                                   {
362                                      try
363                                      {
364                                         stopServer(srvServer);
365                                      }
366                                      catch (OSSException ossExc)
367                                      {
368                                         s_logger.log(Level.WARNING,
369                                                      "Unexpected exception.",
370                                                      ossExc);
371                                      }
372    
373                                      // Try to avoid JVM crash (just taken directly
374
// from Jetty, not sure what it is for
375
try
376                                      {
377                                         Thread.sleep(1000);
378                                      }
379                                      catch (Throwable JavaDoc throwable)
380                                      {
381                                         s_logger.log(Level.WARNING,
382                                                             "Unexpected exception.",
383                                                             throwable);
384                                      }
385                                   }
386                                }
387                            };
388           shutdownHook.invoke(Runtime.getRuntime(), new Object JavaDoc[]{thrdHook});
389       }
390       catch (Throwable JavaDoc throwable)
391       {
392          s_logger.log(Level.WARNING, "No shutdown hook in JVM.", throwable);
393       }
394       
395       do
396       {
397          s_logger.info("Initializing server.");
398          try
399          {
400             srvServer.init();
401          }
402          catch (IOException JavaDoc ioeExc)
403          {
404             throw new OSSInternalErrorException(
405                          "Cannot initialize server. Please check your settings.",
406                          ioeExc);
407          }
408
409          s_logger.info("Starting server.");
410          try
411          {
412             iRetry++;
413             srvServer.start();
414             bServerRunning = true;
415          }
416          catch (MultiException meExc)
417          {
418             // Figure out if the exception was caused because the port wasn't available
419
for (itrExceptions = meExc.getExceptions().iterator(); itrExceptions.hasNext();)
420             {
421                eTemp = (Exception JavaDoc)itrExceptions.next();
422                if (eTemp instanceof BindException JavaDoc)
423                {
424                   // TODO: Feature: Allow -1 to retry until port is found
425
if (iRetry < s_iMaxServerStartRetry)
426                   {
427                      iNewWebServerPort = generateNewWebPort(srvServer.getWebServerPort());
428                      s_logger.warning("Cannot start server on port "
429                                       + srvServer.getWebServerPort()
430                                       + ". Original message: "
431                                       + eTemp.getMessage()
432                                       + ". Server will try to start on new port "
433                                       + iNewWebServerPort + ".");
434                      // Set ner port and in next loop reinitialize the server
435
srvServer.setWebServerPort(iNewWebServerPort);
436                      // Try to restart server
437
break;
438                   }
439                   else
440                   {
441                      throw new OSSConfigException(
442                                   "Cannot find port to start server even after "
443                                   + iRetry + " retries. Please check your settings.");
444                   }
445                }
446                else
447                {
448                   throw new OSSInternalErrorException(
449                                "Cannot start server. Please check your settings.",
450                                eTemp);
451                }
452             }
453          }
454          finally
455          {
456             if (!bServerRunning)
457             {
458                try
459                {
460                  // If the server is not running, we need to destroy it to clean the memory
461
s_logger.log(Level.FINEST, "Stopping the unsuccessful server.");
462                  srvServer.stop();
463                }
464                catch (Throwable JavaDoc throwable)
465                {
466                   s_logger.log(Level.WARNING, "Unexpected exception.", throwable);
467                }
468             }
469          }
470       }
471       while ((!bServerRunning) && (iRetry < s_iMaxServerStartRetry));
472    }
473
474    /**
475     * Stop specified server.
476     *
477     * @param srvServer - server to stop
478     * @throws OSSException - problem stopping server
479     */

480    protected static void stopServer(
481       Server srvServer
482    ) throws OSSException
483    {
484       s_logger.info("Server is stopping.");
485       try
486       {
487          srvServer.stop();
488       }
489       catch (InterruptedException JavaDoc ieExc)
490       {
491          // Ignore this
492
}
493       s_logger.info("Server is stopped.");
494    }
495    
496    /**
497     * Generate new port number where to start the server if the old one failed.
498     *
499     * @param iOldPort - old port number which cannot be used
500     * @return int - new port number to try
501     */

502    protected static int generateNewWebPort(
503       int iOldPort
504    )
505    {
506       int iReturn;
507       
508       if (iOldPort == WebCommonConstants.HTTP_PORT_DEFAULT)
509       {
510          // If this is default port, try the backup one
511
iReturn = WebCommonConstants.HTTP_PORT_BACKUP;
512       }
513       else
514       {
515          // TODO: Improve: Once we allow feature to search for port until found
516
// we need to handle overflow here
517
// Simply try the next available
518
iReturn = iOldPort + 1;
519       }
520       
521       return iReturn;
522    }
523
524    /**
525     * Install web applications which should be run on the server.
526     *
527     * @param hsServer - HTTP server to install the applications to
528     */

529    protected abstract void addWebApplications(
530       HttpServer hsServer
531    );
532
533    /**
534     * Initialize context (URL subtree) starting with given prefix. Default
535     * implementation setup JSP servlet and resource handler which will server files
536     * from the directory under webroot mathing the URL prefix and request URL.
537     * Overriding classes can e.g. setup it's own servlets, but it should do it
538     * before it calls the overriden method, because order matters.
539     *
540     * @param strURLPrefix - URL prefix which all requests to resources
541     * in this context will have.
542     * @param wacContext - context to setup
543     */

544    protected void initContext(
545       String JavaDoc strURLPrefix,
546       WebApplicationContext wacContext
547    )
548    {
549       // Nothing to do at this time
550
}
551 }
552
Popular Tags