Clover coverage report - Cactus 1.5 for J2EE API 1.3
Coverage timestamp: Wed Feb 18 2004 09:09:13 EST
file stats: LOC: 445   Methods: 16
NCLOC: 194   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
ContainerRunner.java 0% 0% 0% 0%
coverage
 1   
 /*
 2   
  * ====================================================================
 3   
  *
 4   
  * The Apache Software License, Version 1.1
 5   
  *
 6   
  * Copyright (c) 2003 The Apache Software Foundation.  All rights
 7   
  * reserved.
 8   
  *
 9   
  * Redistribution and use in source and binary forms, with or without
 10   
  * modification, are permitted provided that the following conditions
 11   
  * are met:
 12   
  *
 13   
  * 1. Redistributions of source code must retain the above copyright
 14   
  *    notice, this list of conditions and the following disclaimer.
 15   
  *
 16   
  * 2. Redistributions in binary form must reproduce the above copyright
 17   
  *    notice, this list of conditions and the following disclaimer in
 18   
  *    the documentation and/or other materials provided with the
 19   
  *    distribution.
 20   
  *
 21   
  * 3. The end-user documentation included with the redistribution, if
 22   
  *    any, must include the following acknowlegement:
 23   
  *       "This product includes software developed by the
 24   
  *        Apache Software Foundation (http://www.apache.org/)."
 25   
  *    Alternately, this acknowlegement may appear in the software itself,
 26   
  *    if and wherever such third-party acknowlegements normally appear.
 27   
  *
 28   
  * 4. The names "The Jakarta Project", "Cactus" and "Apache Software
 29   
  *    Foundation" must not be used to endorse or promote products
 30   
  *    derived from this software without prior written permission. For
 31   
  *    written permission, please contact apache@apache.org.
 32   
  *
 33   
  * 5. Products derived from this software may not be called "Apache"
 34   
  *    nor may "Apache" appear in their names without prior written
 35   
  *    permission of the Apache Group.
 36   
  *
 37   
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 38   
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 39   
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 40   
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 41   
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 42   
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 43   
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 44   
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 45   
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 46   
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 47   
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 48   
  * SUCH DAMAGE.
 49   
  * ====================================================================
 50   
  *
 51   
  * This software consists of voluntary contributions made by many
 52   
  * individuals on behalf of the Apache Software Foundation.  For more
 53   
  * information on the Apache Software Foundation, please see
 54   
  * <http://www.apache.org/>.
 55   
  *
 56   
  */
 57   
 package org.apache.cactus.integration.ant.container;
 58   
 
 59   
 import java.io.IOException;
 60   
 import java.io.InputStream;
 61   
 import java.net.HttpURLConnection;
 62   
 import java.net.URL;
 63   
 
 64   
 import org.apache.cactus.integration.ant.util.AntLog;
 65   
 import org.apache.commons.logging.Log;
 66   
 import org.apache.tools.ant.BuildException;
 67   
 
 68   
 /**
 69   
  * Support class that handles the lifecycle of a container, which basically
 70   
  * consists of startup and shutdown.
 71   
  * 
 72   
  * @author <a href="mailto:cmlenz@apache.org">Christopher Lenz</a>
 73   
  *
 74   
  * @version $Id: ContainerRunner.java,v 1.4.2.1 2003/10/23 18:20:43 vmassol Exp $
 75   
  */
 76   
 public final class ContainerRunner
 77   
 {
 78   
     // Instance Variables ------------------------------------------------------
 79   
 
 80   
     /**
 81   
      * The container to run.
 82   
      */
 83   
     private Container container;
 84   
 
 85   
     /**
 86   
      * The URL that is continuously pinged to check if the container is running.
 87   
      */
 88   
     private URL url;
 89   
 
 90   
     /**
 91   
      * Timeout in milliseconds after which to give up connecting to the
 92   
      * container.
 93   
      */
 94   
     private long timeout = 180000;
 95   
 
 96   
     /**
 97   
      * The time interval in milliseconds to sleep between polling the container.
 98   
      */
 99   
     private long checkInterval = 500;
 100   
 
 101   
     /**
 102   
      * The time to sleep after the container has shut down. 
 103   
      */
 104   
     private long shutDownWait = 2000;
 105   
 
 106   
     /**
 107   
      * Whether the container had already been running before.
 108   
      */
 109   
     private boolean alreadyRunning;
 110   
 
 111   
     /**
 112   
      * The server name as returned in the 'Server' header of the server's
 113   
      * HTTP response.
 114   
      */
 115   
     private String serverName;
 116   
 
 117   
     /**
 118   
      * The log to use.
 119   
      */
 120   
     private transient Log log = AntLog.NULL;
 121   
 
 122   
     // Constructors ------------------------------------------------------------
 123   
 
 124   
     /**
 125   
      * Constructor.
 126   
      * 
 127   
      * @param theContainer The container to run
 128   
      */
 129  0
     public ContainerRunner(Container theContainer)
 130   
     {
 131  0
         this.container = theContainer;
 132   
     }
 133   
 
 134   
     // Public Methods ----------------------------------------------------------
 135   
 
 136   
     /**
 137   
      * Returns the server name as reported in the 'Server' header of HTTP 
 138   
      * responses from the server.
 139   
      * 
 140   
      * @return The server name
 141   
      */
 142  0
     public String getServerName()
 143   
     {
 144  0
         return this.serverName;
 145   
     }
 146   
 
 147   
     /**
 148   
      * Method called by the task to perform the startup of the container. This
 149   
      * method takes care of starting the container in another thread, and
 150   
      * polling the test URL to check whether startup has completed. As soon as
 151   
      * the URL is available (or the timeout is exceeded), control is returned to
 152   
      * the caller.
 153   
      * 
 154   
      * @throws IllegalStateException If the 'url' property is <code>null</code>
 155   
      */
 156  0
     public void startUpContainer() throws IllegalStateException
 157   
     {
 158  0
         if (this.url == null)
 159   
         {
 160  0
             throw new IllegalStateException("Property 'url' must be set");
 161   
         }
 162   
 
 163   
         // Try connecting in case the server is already running. If so, does
 164   
         // nothing
 165  0
         this.alreadyRunning = isAvailable(testConnectivity(this.url));
 166  0
         if (this.alreadyRunning)
 167   
         {
 168   
             // Server is already running. Record this information so that we
 169   
             // don't stop it afterwards.
 170  0
             this.log.debug("Server is already running");
 171  0
             return;
 172   
         }
 173   
 
 174   
         // Now start the server in another thread
 175  0
         Thread thread = new Thread(new Runnable()
 176   
         {
 177  0
             public void run()
 178   
             {
 179  0
                 container.startUp();
 180   
             }
 181   
         });
 182  0
         thread.start();
 183   
 
 184   
         // Continuously try calling the test URL until it succeeds or
 185   
         // until a timeout is reached (we then throw a build exception).
 186  0
         long startTime = System.currentTimeMillis();
 187  0
         int responseCode = -1;
 188  0
         do
 189   
         {
 190  0
             if ((System.currentTimeMillis() - startTime) > this.timeout)
 191   
             {
 192  0
                 throw new BuildException("Failed to start the container after "
 193   
                     + "more than [" + this.timeout + "] ms. Trying to connect "
 194   
                     + "to the [" + this.url + "] test URL yielded a ["
 195   
                     + responseCode + "] error code. Please run in debug mode "
 196   
                     + "for more details about the error.");
 197   
             }
 198  0
             sleep(this.checkInterval);
 199  0
             this.log.debug("Checking if server is up ...");
 200  0
             responseCode = testConnectivity(this.url);
 201  0
         } while (!isAvailable(responseCode));
 202   
 
 203   
         // Wait a few ms more (just to be sure !)
 204  0
         sleep(this.container.getStartUpWait());
 205   
 
 206  0
         this.serverName = retrieveServerName(this.url);
 207  0
         this.log.trace("Server '" + this.serverName + "' started");
 208   
     }
 209   
 
 210   
     /**
 211   
      * Method called by the task to perform the stopping of the container. This
 212   
      * method takes care of stopping the container in another thread, and
 213   
      * polling the test URL to check whether shutdown has completed. As soon as
 214   
      * the URL stops responding, control is returned to the caller.
 215   
      * 
 216   
      * @throws IllegalStateException If the 'url' property is <code>null</code>
 217   
      */
 218  0
     public void shutDownContainer() throws IllegalStateException
 219   
     {
 220  0
         if (this.url == null)
 221   
         {
 222  0
             throw new IllegalStateException("Property 'url' must be set");
 223   
         }
 224   
 
 225   
         // Don't shut down a container that has not been started by us
 226  0
         if (this.alreadyRunning)
 227   
         {
 228  0
             return;
 229   
         }
 230   
         
 231  0
         if (!isAvailable(testConnectivity(this.url)))
 232   
         {
 233  0
             this.log.debug("Server isn't running!");
 234  0
             return;
 235   
         }
 236   
 
 237   
         // Call the target that stops the server, in another thread. The called
 238   
         // target must be blocking.
 239  0
         Thread thread = new Thread(new Runnable()
 240   
         {
 241  0
             public void run()
 242   
             {
 243  0
                 container.shutDown();
 244   
             }
 245   
         });
 246  0
         thread.start();
 247   
 
 248   
         // Continuously try calling the test URL until it fails
 249  0
         do 
 250   
         {
 251  0
             sleep(this.checkInterval);
 252  0
         } while (isAvailable(testConnectivity(this.url)));
 253   
 
 254   
         // sleep a bit longer to be sure the container has terminated
 255  0
         sleep(this.shutDownWait);
 256   
         
 257  0
         this.log.debug("Server stopped!");
 258   
     }
 259   
 
 260   
     /**
 261   
      * Sets the time interval to sleep between polling the container. 
 262   
      * 
 263   
      * The default interval is 500 milliseconds.
 264   
      * 
 265   
      * @param theCheckInterval The interval in milliseconds
 266   
      */
 267  0
     public void setCheckInterval(long theCheckInterval)
 268   
     {
 269  0
         this.checkInterval = theCheckInterval;
 270   
     }
 271   
 
 272   
     /**
 273   
      * Sets the log to write to.
 274   
      *  
 275   
      * @param theLog The log to set
 276   
      */
 277  0
     public void setLog(Log theLog)
 278   
     {
 279  0
         this.log = theLog;
 280   
     }
 281   
 
 282   
     /**
 283   
      * Sets the time to wait after the container has been shut down.
 284   
      * 
 285   
      * The default time is 2 seconds.
 286   
      * 
 287   
      * @param theShutDownWait The time to wait in milliseconds
 288   
      */
 289  0
     public void setShutDownWait(long theShutDownWait)
 290   
     {
 291  0
         this.shutDownWait = theShutDownWait;
 292   
     }
 293   
 
 294   
     /**
 295   
      * Sets the timeout after which to stop trying to call the container.
 296   
      * 
 297   
      * The default timeout is 3 minutes.
 298   
      * 
 299   
      * @param theTimeout The timeout in milliseconds
 300   
      */
 301  0
     public void setTimeout(long theTimeout)
 302   
     {
 303  0
         this.timeout = theTimeout;
 304   
     }
 305   
 
 306   
     /**
 307   
      * Sets the HTTP URL that will be continuously pinged to check if the
 308   
      * container is running.
 309   
      * 
 310   
      * @param theUrl The URL to set
 311   
      */
 312  0
     public void setUrl(URL theUrl)
 313   
     {
 314  0
         if (!theUrl.getProtocol().equals("http"))
 315   
         {
 316  0
             throw new IllegalArgumentException("Not a HTTP URL");
 317   
         } 
 318  0
         this.url = theUrl;
 319   
     }
 320   
 
 321   
     // Private Methods ---------------------------------------------------------
 322   
 
 323   
     /**
 324   
      * Tests whether we are able to connect to the HTTP server identified by the
 325   
      * specified URL.
 326   
      * 
 327   
      * @param theUrl The URL to check
 328   
      * @return the HTTP response code or -1 if no connection could be 
 329   
      *         established
 330   
      */
 331  0
     private int testConnectivity(URL theUrl)
 332   
     {
 333  0
         int code;
 334  0
         try
 335   
         {
 336  0
             HttpURLConnection connection = 
 337   
                 (HttpURLConnection) theUrl.openConnection();
 338  0
             connection.setRequestProperty("Connection", "close");
 339  0
             connection.connect();
 340  0
             readFully(connection);
 341  0
             connection.disconnect();
 342  0
             code = connection.getResponseCode();
 343   
         }
 344   
         catch (IOException e)
 345   
         {
 346  0
             this.log.debug("Failed to connect to " + theUrl, e);
 347  0
             code = -1;
 348   
         }
 349  0
         return code;
 350   
     }
 351   
 
 352   
 
 353   
     /**
 354   
      * Tests whether an HTTP return code corresponds to a valid connection
 355   
      * to the test URL or not. Success is 200 up to but excluding 300.
 356   
      * 
 357   
      * @param theCode the HTTP response code to verify
 358   
      * @return <code>true</code> if the test URL could be called without error,
 359   
      *         <code>false</code> otherwise
 360   
      */
 361  0
     private boolean isAvailable(int theCode)
 362   
     {
 363  0
         boolean result;
 364  0
         if ((theCode != -1) && (theCode < 300)) 
 365   
         {
 366  0
             result = true;            
 367   
         }
 368   
         else
 369   
         {
 370  0
             result = false;
 371   
         }
 372  0
         return result;
 373   
     }
 374   
 
 375   
     /**
 376   
      * Retrieves the server name of the container.
 377   
      * 
 378   
      * @param theUrl The URL to retrieve
 379   
      * @return The server name, or <code>null</code> if the server name could 
 380   
      *         not be retrieved
 381   
      */
 382  0
     private String retrieveServerName(URL theUrl)
 383   
     {
 384  0
         String retVal = null;
 385  0
         try
 386   
         {
 387  0
             HttpURLConnection connection = 
 388   
                 (HttpURLConnection) theUrl.openConnection();
 389  0
             connection.connect();
 390  0
             retVal = connection.getHeaderField("Server");
 391  0
             connection.disconnect();
 392   
         }
 393   
         catch (IOException e)
 394   
         {
 395  0
             this.log.debug("Could not get server name from " + theUrl, e);
 396   
         }
 397  0
         return retVal;
 398   
     }
 399   
 
 400   
     /**
 401   
      * Fully reads the input stream from the passed HTTP URL connection to
 402   
      * prevent (harmless) server-side exception.
 403   
      *
 404   
      * @param theConnection the HTTP URL connection to read from
 405   
      * @exception IOException if an error happens during the read
 406   
      */
 407  0
     static void readFully(HttpURLConnection theConnection)
 408   
                    throws IOException
 409   
     {
 410   
         // Only read if there is data to read ... The problem is that not
 411   
         // all servers return a content-length header. If there is no header
 412   
         // getContentLength() returns -1. It seems to work and it seems
 413   
         // that all servers that return no content-length header also do
 414   
         // not block on read() operations!
 415  0
         if (theConnection.getContentLength() != 0)
 416   
         {
 417  0
             byte[] buf = new byte[256];
 418  0
             InputStream in = theConnection.getInputStream();
 419  0
             while (in.read(buf) != -1)
 420   
             {
 421   
                 // Make sure we read all the data in the stream
 422   
             }
 423   
         }
 424   
     }
 425   
 
 426   
     /**
 427   
      * Pauses the current thread for the specified amount.
 428   
      *
 429   
      * @param theMs The time to sleep in milliseconds
 430   
      * @throws BuildException If the sleeping thread is interrupted
 431   
      */
 432  0
     private void sleep(long theMs) throws BuildException
 433   
     {
 434  0
         try
 435   
         {
 436  0
             Thread.sleep(theMs);
 437   
         }
 438   
         catch (InterruptedException e)
 439   
         {
 440  0
             throw new BuildException("Interruption during sleep", e);
 441   
         }
 442   
     }
 443   
 
 444   
 }
 445