com.cinchapi.concourse.ConnectionPool.java Source code

Java tutorial

Introduction

Here is the source code for com.cinchapi.concourse.ConnectionPool.java

Source

/*
 * Copyright (c) 2013-2016 Cinchapi Inc.
 * 
 * Licensed 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 com.cinchapi.concourse;

import java.util.Queue;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.concurrent.ThreadSafe;
import com.cinchapi.concourse.config.ConcourseClientPreferences;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * <p>
 * A {@link ConnectionPool} manages multiple concurrent connections to Concourse
 * for a single user. Generally speaking, a ConnectionPool is handy in long
 * running server API endpoints that want to reduce the overhead of creating a
 * new client connection for every asynchronous request. Using a ConnectionPool,
 * those applications can maintain a finite number of connections while ensuring
 * the resources are disconnected gracefully when necessary.
 * </p>
 * <h2>Usage</h2>
 * 
 * <pre>
 * Concourse concourse = pool.request();
 * try {
 *  ...
 * }
 * finally {
 *  pool.release(concourse);
 * }
 * ...
 * // All the threads with connections are done
 * pool.close()
 * </pre>
 * 
 * @author Jeff Nelson
 */
@ThreadSafe
public abstract class ConnectionPool implements AutoCloseable {

    // NOTE: This class does not define #hashCode or #equals because the
    // defaults are the desired behaviour

    /**
     * Return a {@link ConnectionPool} that has no limit on the number of
     * connections it can manage to the Concourse instance described in the
     * {@code concourse_client.prefs} file located in the working directory or
     * the default connection info if no such file exists, but will try to use
     * previously created connections before establishing new ones for any
     * request.
     * 
     * @return the ConnectionPool
     */
    public static ConnectionPool newCachedConnectionPool() {
        return newCachedConnectionPool(DEFAULT_PREFS_FILE);
    }

    /**
     * Return a {@link ConnectionPool} that has no limit on the number of
     * connections it can manage to the Concourse instance at {@code host}:
     * {@code port} on behalf of the user identified by {@code username} and
     * {@code password}, but will try to use previously created connections
     * before establishing new ones for any request.
     * 
     * @param prefs
     * @return the ConnectionPool
     */
    public static ConnectionPool newCachedConnectionPool(String prefs) {
        ConcourseClientPreferences cp = ConcourseClientPreferences.open(prefs);
        return new CachedConnectionPool(cp.getHost(), cp.getPort(), cp.getUsername(), new String(cp.getPassword()),
                cp.getEnvironment(), DEFAULT_POOL_SIZE);
    }

    /**
     * Return a {@link ConnectionPool} that has no limit on the number of
     * connections it can manage to the Concourse instance defined in the client
     * {@code prefs} on behalf of the user defined in the client {@code prefs},
     * but will try to use previously created connections before establishing
     * new ones for any request.
     * 
     * @param host
     * @param port
     * @param username
     * @param password
     * @return the ConnectionPool
     */
    public static ConnectionPool newCachedConnectionPool(String host, int port, String username, String password) {
        return new CachedConnectionPool(host, port, username, password, DEFAULT_POOL_SIZE);
    }

    /**
     * Return a {@link ConnectionPool} that has no limit on the number of
     * connections it can manage to the Concourse instance defined in the client
     * {@code prefs} on behalf of the user defined in the client {@code prefs},
     * but will try to use previously created connections before establishing
     * new ones for any request.
     * 
     * @param host
     * @param port
     * @param username
     * @param password
     * @param environment
     * @return the ConnectionPool
     */
    public static ConnectionPool newCachedConnectionPool(String host, int port, String username, String password,
            String environment) {
        return new CachedConnectionPool(host, port, username, password, environment, DEFAULT_POOL_SIZE);
    }

    /**
     * Return a new {@link ConnectionPool} that provides connections to the
     * Concourse instance defined in the client {@code prefs} on behalf of the
     * user defined in the client {@code prefs}.
     * 
     * @param prefs
     * @return the ConnectionPool
     * @deprecated As of version 0.3.2, replaced by
     *             {@link #newFixedConnectionPool(String, int)}.
     */
    @Deprecated
    public static ConnectionPool newConnectionPool(String prefs) {
        return newFixedConnectionPool(prefs, DEFAULT_POOL_SIZE);
    }

    /**
     * Return a new {@link ConnectionPool} that provides {@code poolSize}
     * connections to the Concourse instance defined in the client {@code prefs}
     * on behalf of the user defined in the client {@code prefs}.
     * 
     * @param prefs
     * @param poolSize
     * @return the ConnectionPool
     * @deprecated As of version 0.3.2, replaced by
     *             {@link #newFixedConnectionPool(String, int)}.
     */
    @Deprecated
    public static ConnectionPool newConnectionPool(String prefs, int poolSize) {
        return newFixedConnectionPool(prefs, poolSize);
    }

    /**
     * Return a new {@link ConnectionPool} that provides connections to the
     * Concourse instance at {@code host}:{@code port} on behalf of the user
     * identified by {@code username} and {@code password}.
     * 
     * @param host
     * @param port
     * @param username
     * @param password
     * @return the ConnectionPool
     * @deprecated As of version 0.3.2, replaced by
     *             {@link #newFixedConnectionPool(String, int, String, String, int)}
     *             .
     */
    @Deprecated
    public static ConnectionPool newConnectionPool(String host, int port, String username, String password) {
        return newFixedConnectionPool(host, port, username, password, DEFAULT_POOL_SIZE);
    }

    /**
     * Return a new {@link ConnectionPool} that provides {@code poolSize}
     * connections to the Concourse instance at {@code host}:{@code port} on
     * behalf of the user identified by {@code username} and {@code password}.
     * 
     * @param host
     * @param port
     * @param username
     * @param password
     * @param poolSize
     * @return the ConnectionPool
     * @deprecated As of version 0.3.2, replaced by
     *             {@link #newFixedConnectionPool(String, int, String, String, int)}
     *             .
     */
    @Deprecated
    public static ConnectionPool newConnectionPool(String host, int port, String username, String password,
            int poolSize) {
        return newFixedConnectionPool(host, port, username, password, poolSize);
    }

    /**
     * Return a new {@link ConnectionPool} with a fixed number of connections to
     * the Concourse instance defined in the {@code concourse_client.prefs} file
     * located in the working directory or using the default connection info if
     * no such file exists.
     * <p>
     * If all the connections from the pool are active, subsequent request
     * attempts will block until a connection is returned.
     * </p>
     * 
     * @param poolSize
     * @return the ConnectionPool
     */
    public static ConnectionPool newFixedConnectionPool(int poolSize) {
        return newFixedConnectionPool(DEFAULT_PREFS_FILE, poolSize);
    }

    /**
     * Return a new {@link ConnectionPool} with a fixed number of
     * connections to the Concourse instance defined in the client {@code prefs}
     * on behalf of the user defined in the client {@code prefs}.
     * <p>
     * If all the connections from the pool are active, subsequent request
     * attempts will block until a connection is returned.
     * </p>
     * 
     * @param prefs
     * @param poolSize
     * @return the ConnectionPool
     */
    public static ConnectionPool newFixedConnectionPool(String prefs, int poolSize) {
        ConcourseClientPreferences cp = ConcourseClientPreferences.open(prefs);
        return new FixedConnectionPool(cp.getHost(), cp.getPort(), cp.getUsername(), new String(cp.getPassword()),
                cp.getEnvironment(), poolSize);
    }

    /**
     * Return a new {@link ConnectionPool} with a fixed number of connections to
     * the Concourse instance at {@code host}:{@code port} on behalf of the user
     * identified by {@code username} and {@code password}.
     * 
     * <p>
     * If all the connections from the pool are active, subsequent request
     * attempts will block until a connection is returned.
     * </p>
     * 
     * @param host
     * @param port
     * @param username
     * @param password
     * @param poolSize
     * @return the ConnectionPool
     */
    public static ConnectionPool newFixedConnectionPool(String host, int port, String username, String password,
            int poolSize) {
        return new FixedConnectionPool(host, port, username, password, poolSize);
    }

    /**
     * Return a new {@link ConnectionPool} with a fixed number of connections to
     * the Concourse instance at {@code host}:{@code port} on behalf of the user
     * identified by {@code username} and {@code password}.
     * 
     * <p>
     * If all the connections from the pool are active, subsequent request
     * attempts will block until a connection is returned.
     * </p>
     * 
     * @param host
     * @param port
     * @param username
     * @param password
     * @param environment
     * @param poolSize
     * @return the ConnectionPool
     */
    public static ConnectionPool newFixedConnectionPool(String host, int port, String username, String password,
            String environment, int poolSize) {
        return new FixedConnectionPool(host, port, username, password, environment, poolSize);
    }

    /**
     * The default connection pool size.
     */
    protected static final int DEFAULT_POOL_SIZE = 10;

    /**
     * The default preferences file to use if none is specified.
     */
    private static final String DEFAULT_PREFS_FILE = "concourse_client.prefs";

    /**
     * A FIFO queue of connections that are available to be leased.
     */
    protected final Queue<Concourse> available;

    /**
     * The connections that have been leased from this pool.
     */
    private final Set<Concourse> leased;

    /**
     * A flag to indicate if the pool is currently open and operational.
     */
    private AtomicBoolean open = new AtomicBoolean(true);

    /**
     * Construct a new instance.
     * 
     * @param host
     * @param port
     * @param username
     * @param password
     * @param poolSize
     */
    protected ConnectionPool(String host, int port, String username, String password, int poolSize) {
        this(host, port, username, password, "", poolSize);

    }

    /**
     * Construct a new instance.
     * 
     * @param host
     * @param port
     * @param username
     * @param password
     * @param environment
     * @param poolSize
     */
    protected ConnectionPool(String host, int port, String username, String password, String environment,
            int poolSize) {
        this.available = buildQueue(poolSize);
        this.leased = Sets.newSetFromMap(Maps.<Concourse, Boolean>newConcurrentMap());
        for (int i = 0; i < poolSize; ++i) {
            available.offer(Concourse.connect(host, port, username, password, environment));
        }
        // Ensure that the client connections are forced closed when the JVM is
        // shutdown in case the user does not properly close the pool
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

            @Override
            public void run() {
                forceClose();
            }

        }));
    }

    @Override
    public void close() throws Exception {
        Preconditions.checkState(isClosable(),
                "Cannot shutdown the connection pool " + "until all the connections have been returned");
        if (open.compareAndSet(true, false)) {
            exitAllConnections();
        } else {
            throw new IllegalStateException("Connection pool is closed");
        }
    }

    /**
     * Return {@code true} if the pool has any available connections.
     * 
     * @return {@code true} if there are one or more available connections
     */
    public boolean hasAvailableConnection() {
        verifyOpenState();
        return available.peek() != null;
    }

    /**
     * Return {@code true} if this {@link ConnectionPool} has been closed.
     * 
     * @return a boolean that indicates whether the connection pool is closed or
     *         not
     */
    public boolean isClosed() {
        return !open.get();
    }

    /**
     * Return a previously requested connection back to the pool.
     * 
     * @param connection
     */
    public void release(Concourse connection) {
        verifyOpenState();
        verifyValidOrigin(connection);
        available.offer(connection);
        leased.remove(connection);
    }

    /**
     * Request a connection from the pool and block until one is available and
     * returned.
     * 
     * @return a connection
     */
    public Concourse request() {
        verifyOpenState();
        Concourse connection = getConnection();
        leased.add(connection);
        return connection;
    }

    /**
     * Return the {@link Queue} that will hold the connections.
     * 
     * @param size
     * 
     * @return the connections cache
     */
    protected abstract Queue<Concourse> buildQueue(int size);

    /**
     * Force the connection pool to close regardless of whether it is or is not
     * in a {@link #isClosable() closable} state.
     */
    protected void forceClose() {
        if (open.compareAndSet(true, false)) {
            exitConnections(available);
            exitConnections(leased);
        }
    }

    /**
     * Get a connection from the queue of {@code available} ones. The subclass
     * should use the correct method depending upon whether this method should
     * block or not.
     * 
     * @return the connection
     */
    protected abstract Concourse getConnection();

    /**
     * Exit all the connections managed of the pool that has a
     * {@link #available}.
     */
    private void exitAllConnections() {
        exitConnections(available);
    }

    /**
     * Close each of the given {@code connections} to {@link Concourse}
     * regardless of whether it is currently {@link #available} or
     * {@link #leased}.
     * 
     * @param connections an {@link Iterable} collection of connections.
     */
    private void exitConnections(Iterable<Concourse> connections) {
        for (Concourse concourse : connections) {
            boolean exited = false;
            while (!exited) {
                try {
                    concourse.exit();
                    exited = true;
                } catch (Exception e) {
                    // If a shutdown hook is used to close the connection pool,
                    // its possible to run into a situation where multiple
                    // threads operating on a client connection may trigger an
                    // out-of-sequence error with Thrift. If that is the case,
                    // keep retrying...
                    exited = false;
                }
            }

        }
    }

    /**
     * Return {@code true} if none of the connections are currently active.
     * 
     * @return {@code true} if the pool can be closed
     */
    private boolean isClosable() {
        return leased.isEmpty();
    }

    /**
     * Ensure that the connection pool is open. If it is not, throw an
     * IllegalStateException.
     */
    private void verifyOpenState() {
        Preconditions.checkState(open.get(), "Connection pool is closed");
    }

    /**
     * Verify that the {@code connection} was leased from this pool.
     * 
     * @param connection
     */
    private void verifyValidOrigin(Concourse connection) {
        if (!leased.contains(connection)) {
            throw new IllegalArgumentException(
                    "Cannot release the connection because it " + "was not previously requested from this pool");
        }
    }
}