/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License).  You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright (c) Ericsson AB, 2004-2007. All rights reserved.
 */
package com.ericsson.ssa.container;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

// inserted by hockey (automatic)
import java.util.logging.Logger;


/**
 * This class represents a pool of connections to a specific host and port.
 * A client that needs a connection may obtain one by calling
 * getConnection(). If no connection is available this method will block
 * until a connection is released by another tread by calling releaseConnection().
 * Obtained connections MUST be released by calling releaseConnection().
 *
 * @author epiesan
 * @since Apr 16, 2006
 */
public class HttpConnectionPool {
    private static Logger log = (Logger) Logger.getLogger("SipContainer");
    private final static int DEFAULT_NUM_CONNECTIONS = 2;
    private HttpHost host;
    private List<HttpConnection> allConnections;
    private BlockingQueue<HttpConnection> freeConnections;

    /**
     * Constructor for HttpConnectionPool.
     * Creates a pool with default values for max connections
     * and idle connection timeout.
     *
     * @param host The host this pool manages connections for.
     */
    public HttpConnectionPool(final HttpHost host) {
        this(host, DEFAULT_NUM_CONNECTIONS);
    }

    /**
     * Constructor for HttpConnectionPool.
     *
     * @param host The host this pool manages connections for.
     * @param numConnections The number of connections in this pool.
     */
    public HttpConnectionPool(final HttpHost host, int numConnections) {
        this(host, numConnections, -1);
    }

    /**
     * Constructor for HttpConnectionPool.
     *
     * @param host The host this pool manages connections for.
     * @param numConnections The number of connections in this pool.
     * @param socketTimeout The socket timeout value for the connections
     *   in this pool.
     */
    public HttpConnectionPool(final HttpHost host, int numConnections,
        int socketTimeout) {
        this.host = host;
        this.allConnections = new ArrayList<HttpConnection>();
        this.freeConnections = new LinkedBlockingQueue<HttpConnection>();

        // Create all connections initially.
        createConnections(host, numConnections, socketTimeout);
    }

    /**
     * Obtains a connection from the pool.
     * If no connection is currently available this method will block
     * until a connection is available (released by another thread).
     *
     * @return A free connection from the pool.
     */
    public HttpConnection getConnection() {
        return getConnection(0);
    }

    /**
     * Obtains a connection from the pool.
     * If no connection is currently available this method will block
     * the specified amount of time or forever if zero is given
     * as the timeout value.
     *
     * @param The maximum time to block. Zero means block until
     *   a connection is available.
     * @return A free connection from the pool or null
     */
    public HttpConnection getConnection(int timeout) {
        while (true) {
            try {
                if (log.isLoggable(Level.FINE)) {
                    if (freeConnections.peek() == null) {
                        log.log(Level.FINE,
                            "No connection is currently available. " +
                            "The current thread will block until another " +
                            "thread releases a connection.");
                    }
                }

                if (timeout > 0) {
                    // Wait only a limited time before giving up.
                    return freeConnections.poll(timeout, TimeUnit.MILLISECONDS);
                } else {
                    // Wait forever if necessary.
                    return freeConnections.take();
                }
            } catch (InterruptedException e) {
                // Thread was interrupted. Try again!
            }
        }
    }

    /**
     * Release a connection that was previously obtained from this pool.
     *
     * @param connection The connection to release.
     * @throws IllegalArgumentException Thrown if the passed connection
     *   is not managed by this pool.
     * @throws IllegalStateException Thrown if the passed connection has
     *   already been released.
     */
    public void releaseConnection(HttpConnection connection)
        throws IllegalArgumentException, IllegalStateException {
        if (!allConnections.contains(connection)) {
            throw new IllegalArgumentException("The connection cannot be" +
                " releases since it is not managed by this pool.");
        }

        if (freeConnections.contains(connection)) {
            throw new IllegalStateException(
                "The connection has already been released.");
        }

        // Add the connection to the list of free connections.
        freeConnections.offer(connection);
    }

    /**
     * Shuts down all connections in the pool.
     * Still not released connections will also be closed.
     *
     * @throws IllegalStateException If there still are active connections.
     */
    public void shutdown() throws IllegalStateException {
        try {
            if (freeConnections.size() < allConnections.size()) {
                throw new IllegalStateException("Some connections to host '" +
                    host + "' are still active but will be closed anyway.");
            }
        } finally {
            for (HttpConnection connection : allConnections) {
                connection.close();
            }

            allConnections.clear();
            freeConnections.clear();
        }
    }

    /**
     * Create the initial HttpConnection instances.
     *
     * @param host The host this pool manages connections for.
     * @param numConnections The number of connections in this pool.
     * @param socketTimeout The socket timeout value for the
     *   connections in this pool.
     */
    private void createConnections(final HttpHost host, int numConnections,
        int socketTimeout) {
        for (int i = 0; i < numConnections; i++) {
            HttpConnection connection;

            if (socketTimeout < 0) {
                connection = new HttpConnection(host, this);
            } else {
                connection = new HttpConnection(host, socketTimeout, this);
            }

            this.allConnections.add(connection);
            this.freeConnections.add(connection);
        }
    }
}
