ContainerRunner.java :  » Testing » jakarta-cactus » org » apache » cactus » container » Java Open Source

Java Open Source » Testing » jakarta cactus 
jakarta cactus » org » apache » cactus » container » ContainerRunner.java
/* 
 * ========================================================================
 * 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * ========================================================================
 */
package org.apache.cactus.container;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import org.apache.cactus.integration.api.exceptions.CactusRuntimeException;
import org.codehaus.cargo.util.log.Logger;

/**
 * Support class that handles the lifecycle of a container, which basically
 * consists of startup and shutdown.
 * 
 * @version $Id: ContainerRunner.java 239130 2005-01-29 15:49:18Z vmassol $
 */
public final class ContainerRunner
{
    // Instance Variables ------------------------------------------------------

    /**
     * The container to run.
     */
    //private org.codehaus.cargo.container.Container container;
    
    private ContainerWrapper containerWrapper = null;

    /**
     * The URL that is continuously pinged to check if the container is running.
     */
    private URL testURL;

    /**
     * Timeout in milliseconds after which to give up connecting to the
     * container.
     */
    private long timeout = 180000;

    /**
     * The time interval in milliseconds to sleep between polling the container.
     */
    private long checkInterval = 500;

    /**
     * The time to sleep after the container has shut down. 
     */
    private long shutDownWait = 2000;

    /**
     * Whether the container had already been running before.
     */
    private boolean alreadyRunning;

    /**
     * The server name as returned in the 'Server' header of the server's
     * HTTP response.
     */
    private String serverName;

    /**
     * The logger to use.
     */
    private transient Logger logger;

    // Constructors ------------------------------------------------------------

    /**
     * Constructor.
     * 
     * @param theContainerWrapper The container to run
     */
    public ContainerRunner(ContainerWrapper theContainerWrapper)
    {
        //this.container = theContainer;
        this.containerWrapper = theContainerWrapper;
    }

    // Public Methods ----------------------------------------------------------

    /**
     * Returns the server name as reported in the 'Server' header of HTTP 
     * responses from the server.
     * 
     * @return The server name
     */
    public String getServerName()
    {
        return this.serverName;
    }

    /**
     * Method called by the task to perform the startup of the container. This
     * method takes care of starting the container in another thread, and
     * polling the test URL to check whether startup has completed. As soon as
     * the URL is available (or the timeout is exceeded), control is returned to
     * the caller.
     * 
     * @throws IllegalStateException If the 'url' property is <code>null</code>
     */
    public void startUpContainer() throws IllegalStateException
    {
        if (this.testURL == null)
        {
            throw new IllegalStateException("Property [url] must be set");
        }

        // Try connecting in case the server is already running. If so, does
        // nothing
        this.alreadyRunning = isAvailable(testConnectivity(this.testURL));
        if (this.alreadyRunning)
        {
            // Server is already running. Record this information so that we
            // don't stop it afterwards.
            this.logger.debug("Server is already running",
                getClass().toString());
            return;
        }

        // Now start the server in another thread
        Thread thread = new Thread(new Runnable()
        {
            public void run()
            {
                containerWrapper.startUp();
            }
        });
        thread.start();

        // Continuously try calling the test URL until it succeeds or
        // until a timeout is reached (we then throw a build exception).
        long startTime = System.currentTimeMillis();
        int responseCode = -1;
        do
        {
            if ((System.currentTimeMillis() - startTime) > this.timeout)
            {
                throw new CactusRuntimeException("Failed to start the container after "
                    + "more than [" + this.timeout + "] ms. Trying to connect "
                    + "to the [" + this.testURL + "] test URL yielded a ["
                    + responseCode + "] error code. Please run in debug mode "
                    + "for more details about the error.");
            }
            sleep(this.checkInterval);
            this.logger.debug("Checking if server is up ...",
                getClass().toString());
            responseCode = testConnectivity(this.testURL);
        } while (!isAvailable(responseCode));

        // Wait a few ms more (just to be sure !)
        sleep(this.containerWrapper.getStartUpWait());

        this.serverName = retrieveServerName(this.testURL);
        this.logger.info("Server [" + this.serverName + "] started",
            getClass().toString());
    }

    /**
     * Method called by the task to perform the stopping of the container. This
     * method takes care of stopping the container in another thread, and
     * polling the test URL to check whether shutdown has completed. As soon as
     * the URL stops responding, control is returned to the caller.
     * 
     * @throws IllegalStateException If the 'url' property is <code>null</code>
     */
    public void shutDownContainer() throws IllegalStateException
    {
        if (this.testURL == null)
        {
            throw new IllegalStateException("Property [url] must be set");
        }

        // Don't shut down a container that has not been started by us
        if (this.alreadyRunning)
        {
            return;
        }
        
        if (!isAvailable(testConnectivity(this.testURL)))
        {
            this.logger.debug("Server isn't running!", getClass().toString());
            return;
        }

        // Call the target that stops the server, in another thread. The called
        // target must be blocking.
        Thread thread = new Thread(new Runnable()
        {
            public void run()
            {
                containerWrapper.shutDown();
            }
        });
        thread.start();

        // Continuously try calling the test URL until it fails
        do 
        {
            sleep(this.checkInterval);
        } while (isAvailable(testConnectivity(this.testURL)));

        // sleep a bit longer to be sure the container has terminated
        sleep(this.shutDownWait);
        
        this.logger.debug("Server stopped!", getClass().toString());
    }

    /**
     * Sets the time interval to sleep between polling the container. 
     * 
     * The default interval is 500 milliseconds.
     * 
     * @param theCheckInterval The interval in milliseconds
     */
    public void setCheckInterval(long theCheckInterval)
    {
        this.checkInterval = theCheckInterval;
    }

    /**
     * Sets the log to write to.
     *  
     * @param theLogger The log to set
     */
    public void setLogger(Logger theLogger)
    {
        this.logger = theLogger;
    }

    /**
     * Sets the time to wait after the container has been shut down.
     * 
     * The default time is 2 seconds.
     * 
     * @param theShutDownWait The time to wait in milliseconds
     */
    public void setShutDownWait(long theShutDownWait)
    {
        this.shutDownWait = theShutDownWait;
    }

    /**
     * Sets the timeout after which to stop trying to call the container.
     * 
     * The default timeout is 3 minutes.
     * 
     * @param theTimeout The timeout in milliseconds
     */
    public void setTimeout(long theTimeout)
    {
        this.timeout = theTimeout;
    }

    /**
     * Sets the HTTP/HTTPS URL that will be continuously pinged to check if the
     * container is running.
     * 
     * @param theTestURL The URL to set
     */
    public void setURL(URL theTestURL)
    {
        if (!(theTestURL.getProtocol().equalsIgnoreCase("http") 
            || theTestURL.getProtocol().equalsIgnoreCase("https")))
        {
            throw new IllegalArgumentException("Not a HTTP or HTTPS URL");
        } 
        this.testURL = theTestURL;
    }

    // Private Methods ---------------------------------------------------------

    /**
     * Tests whether we are able to connect to the HTTP server identified by the
     * specified URL.
     * 
     * @param theUrl The URL to check
     * @return the HTTP response code or -1 if no connection could be 
     *         established
     */
    public int testConnectivity(URL theUrl)
    {
        int code;
        try
        {
            HttpURLConnection connection = 
                (HttpURLConnection) theUrl.openConnection();
            connection.setRequestProperty("Connection", "close");
            connection.connect();
            readFully(connection);
            connection.disconnect();
            code = connection.getResponseCode();
        }
        catch (IOException e)
        {
            this.logger.debug("Failed to connect to [" + theUrl + "]",
                 e.getMessage());
            code = -1;
        }
        return code;
    }


    /**
     * Tests whether an HTTP return code corresponds to a valid connection
     * to the test URL or not. Success is 200 up to but excluding 300.
     * 
     * @param theCode the HTTP response code to verify
     * @return <code>true</code> if the test URL could be called without error,
     *         <code>false</code> otherwise
     */
    private boolean isAvailable(int theCode)
    {
        boolean result;
        if ((theCode != -1) && (theCode < 300)) 
        {
            result = true;            
        }
        else
        {
            result = false;
        }
        return result;
    }

    /**
     * Retrieves the server name of the container.
     * 
     * @param theUrl The URL to retrieve
     * @return The server name, or <code>null</code> if the server name could 
     *         not be retrieved
     */
    private String retrieveServerName(URL theUrl)
    {
        String retVal = null;
        try
        {
            HttpURLConnection connection = 
                (HttpURLConnection) theUrl.openConnection();
            connection.connect();
            retVal = connection.getHeaderField("Server");
            connection.disconnect();
        }
        catch (IOException e)
        {
            this.logger.debug("Could not get server name from [" 
                + theUrl + "]", e.getMessage());
        }
        return retVal;
    }

    /**
     * Fully reads the input stream from the passed HTTP URL connection to
     * prevent (harmless) server-side exception.
     *
     * @param theConnection the HTTP URL connection to read from
     * @exception IOException if an error happens during the read
     */
    static void readFully(HttpURLConnection theConnection)
                   throws IOException
    {
        // Only read if there is data to read ... The problem is that not
        // all servers return a content-length header. If there is no header
        // getContentLength() returns -1. It seems to work and it seems
        // that all servers that return no content-length header also do
        // not block on read() operations!
        if (theConnection.getContentLength() != 0)
        {
            byte[] buf = new byte[256];
            InputStream in = theConnection.getInputStream();
            while (in.read(buf) != -1)
            {
                // Make sure we read all the data in the stream
            }
        }
    }

    /**
     * Pauses the current thread for the specified amount.
     *
     * @param theMs The time to sleep in milliseconds
     * @throws CactusRuntimeException If the sleeping thread is interrupted
     */
    private void sleep(long theMs) throws CactusRuntimeException
    {
        try
        {
            Thread.sleep(theMs);
        }
        catch (InterruptedException e)
        {
            throw new CactusRuntimeException("Interruption during sleep", e);
        }
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.