org.executequery.datasource.ConnectionPoolImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.executequery.datasource.ConnectionPoolImpl.java

Source

/*
 * ConnectionPoolImpl.java
 *
 * Copyright (C) 2002-2015 Takis Diakoumis
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.executequery.datasource;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.sql.DataSource;

import org.apache.commons.lang.StringUtils;
import org.executequery.databasemediators.DatabaseConnection;
import org.executequery.log.Log;
import org.underworldlabs.jdbc.DataSourceException;

/**
 *
 * @author   Takis Diakoumis
 * @version  $Revision: 1521 $
 * @date     $Date: 2015-10-06 16:24:41 +1100 (Tue, 06 Oct 2015) $
 */
public class ConnectionPoolImpl extends AbstractConnectionPool implements PooledConnectionListener {

    private int maximumConnections = MAX_POOL_SIZE;

    private int minimumConnections = MIN_POOL_SIZE;

    private int initialConnections = INITIAL_POOL_SIZE;

    private final List<PooledConnection> openConnections = Collections
            .synchronizedList(new ArrayList<PooledConnection>());

    private final List<PooledConnection> activeConnections = Collections
            .synchronizedList(new ArrayList<PooledConnection>());

    private final DatabaseConnection databaseConnection;

    private int defaultTxIsolation = -1;

    private boolean supportsTransactions;

    private DataSource dataSource;

    private SshTunnel sshTunnel;

    public ConnectionPoolImpl(DatabaseConnection databaseConnection) {

        this.databaseConnection = databaseConnection;

        if (Log.isDebugEnabled()) {

            Log.debug("Creating new pool for connection " + databaseConnection.getName());
        }

    }

    public DatabaseConnection getDatabaseConnection() {

        return databaseConnection;
    }

    public void connectionClosed(PooledConnection pooledConnection) {

        if (Log.isDebugEnabled()) {

            Log.debug("Removing connection " + pooledConnection.getId() + " from active connections list");
        }

        activeConnections.remove(pooledConnection);
        reduceCapacity(minimumConnections);
    }

    public void close(Connection connection) {

        if (connection != null) {

            activeConnections.remove(connection);

            PooledConnection pooledConnection = (PooledConnection) connection;
            pooledConnection.destroy();

            openConnections.remove(pooledConnection);
        }

        ensureCapacity(minimumConnections);
    }

    public synchronized void close() {

        if (Log.isDebugEnabled()) {

            Log.debug("Closing connection pool for connection " + databaseConnection.getName());
        }

        for (Connection connection : openConnections) {

            PooledConnection pooledConnection = (PooledConnection) connection;
            pooledConnection.destroy();
        }

        destroySshTunnel();
        activeConnections.clear();
        openConnections.clear();
    }

    public synchronized Connection getConnection() {

        int size = openConnections.size();
        if (Log.isTraceEnabled()) {

            Log.trace("Retrieving new connection from the pool with current size: [ " + size
                    + " ] from maximum pool capacity [ " + maximumConnections + " ]");
        }

        if (databaseConnection.isSshTunnel() && sshTunnel == null) {

            createSshTunnel();
        }

        if (size < minimumConnections) {

            ensureCapacity(minimumConnections);
        }

        PooledConnection connection = getNextOpenAvailable();

        if (connection != null) {

            try {
                if (connection.isClosed()) {

                    close(connection);
                    return getConnection();
                }
            } catch (SQLException e) {
            }

            connection.setInUse(true);
            activeConnections.add(connection);

        } else if (size < maximumConnections) {

            createConnection();
            return getConnection();

        } else {

            throw new DataSourceException("Maximum open connection count exceeded");
        }

        if (Log.isDebugEnabled()) {

            Log.debug("Retrieving connection " + connection.getId());
        }

        return connection;
    }

    private void destroySshTunnel() {

        if (sshTunnel != null) {

            sshTunnel.disconnect(databaseConnection);
            sshTunnel = null;
        }
    }

    private void createSshTunnel() {

        sshTunnel = new JschSshTunnel();
        sshTunnel.connect(databaseConnection);
    }

    private void ensureCapacity(int capacity) {

        if (Log.isDebugEnabled()) {

            Log.debug("Ensuring pool capacity " + capacity);
        }

        while (openConnections.size() < capacity) {

            createConnection();
        }

    }

    private void reduceCapacity(int capacity) {

        if (Log.isDebugEnabled()) {

            Log.debug("Reducing pool capacity " + capacity);
        }

        while (openConnections.size() > capacity) {

            PooledConnection connection = getNextOpenAvailable();
            if (connection != null) {

                close(connection);

            } else {

                break;
            }

        }

    }

    private PooledConnection createConnection() {

        PooledConnection connection = null;

        try {

            if (dataSource == null) {

                DatabaseConnection _databaseConnection = databaseConnection;
                if (databaseConnection.isSshTunnel()) {

                    if (sshTunnel == null) {

                        createSshTunnel();
                    }

                    _databaseConnection = databaseConnection.copy();
                    _databaseConnection.setHost("localhost");
                    _databaseConnection.setPort(String.valueOf(sshTunnel.getTunnelPort()));
                }

                dataSource = new SimpleDataSource(_databaseConnection);
            }

            Connection realConnection = dataSource.getConnection();
            if (realConnection == null) {

                destroySshTunnel();
                throw new DataSourceException("A connection to the database could not be "
                        + "established.\nPlease ensure that the details "
                        + "are correct and the supplied host is available.");
            }

            if (defaultTxIsolation == -1) {

                configureTransactionIsolationLevel(realConnection);
            }

            int transactionIsolation = databaseConnection.getTransactionIsolation();
            if (transactionIsolation != -1) {

                try {

                    realConnection.setTransactionIsolation(databaseConnection.getTransactionIsolation());

                } catch (SQLException e) {

                    Log.warning("Error setting transaction isolation level: " + e.getMessage());
                }
            }

            connection = new PooledConnection(realConnection);
            connection.addPooledConnectionListener(this);

            openConnections.add(connection);

            if (Log.isDebugEnabled()) {

                Log.debug("Added new connection to the pool - " + connection.getId());
            }

        } catch (SQLException e) {

            destroySshTunnel();
            rethrowAsDataSourceException(e);
        }

        return connection;
    }

    private void configureTransactionIsolationLevel(Connection connection) {

        try {

            defaultTxIsolation = connection.getTransactionIsolation();
            supportsTransactions = connection.getMetaData().supportsTransactions();

        } catch (SQLException e) {

            rethrowAsDataSourceException(e);
        }

    }

    private PooledConnection getNextOpenAvailable() {

        for (PooledConnection pooledConnection : openConnections) {

            if (pooledConnection.isAvailable()) {

                return pooledConnection;
            }

        }

        return null;
    }

    public DataSource getDataSource() {

        return dataSource;
    }

    public int getMaximumConnections() {

        return maximumConnections;
    }

    public int getMaximumUseCount() {

        return 0;
    }

    public int getMinimumConnections() {

        return minimumConnections;
    }

    public int getPoolActiveSize() {

        return activeConnections.size();
    }

    public int getSize() {

        return openConnections.size();
    }

    public boolean isTransactionSupported() {

        if (defaultTxIsolation == -1) {

            Connection connection = getConnection();
            try {

                configureTransactionIsolationLevel(connection);

            } finally {

                close(connection);
            }

        }

        return supportsTransactions;
    }

    public void setDataSource(DataSource dataSource) {

        this.dataSource = dataSource;
    }

    public int getInitialConnections() {

        return initialConnections;
    }

    public void setInitialConnections(int initialConnections) {

        if (initialConnections < 1) {

            throw new IllegalArgumentException("Initial connection count must be at least 1");
        }

        this.initialConnections = initialConnections;
    }

    public void setMaximumConnections(int maximumConnections) {

        if (maximumConnections < 1) {

            throw new IllegalArgumentException("Maximum connection count must be at least 1");
        }

        this.maximumConnections = maximumConnections;
    }

    public void setMaximumUseCount(int maximumUseCount) {

    }

    public void setMinimumConnections(int minimumConnections) {

        if (minimumConnections < 1) {

            throw new IllegalArgumentException("Minimum connection count must be at least 1");
        }

        this.minimumConnections = minimumConnections;
        ensureCapacity(minimumConnections);
    }

    public void setTransactionIsolationLevel(int isolationLevel) {

        if (!isTransactionSupported()) {

            return;
        }

        int isolationLevelToApply = isolationLevel;
        if (isolationLevelToApply == -1) {

            isolationLevelToApply = defaultTxIsolation;
        }

        Log.debug("Setting transaction isolation level to open connections as [ "
                + nameForTransactionIsolationLevel(isolationLevelToApply) + " ]");

        try {

            synchronized (openConnections) {

                for (Connection connection : openConnections) {

                    if (!connection.isClosed()) {

                        connection.setTransactionIsolation(isolationLevelToApply);
                    }

                }

            }

        } catch (SQLException e) {

            throw new DataSourceException(e);
        }

    }

    private String nameForTransactionIsolationLevel(int isolationLevel) {

        for (Field field : Connection.class.getFields()) {

            String name = field.getName();
            if (StringUtils.startsWith(name, "TRANSACTION_")) {

                try {
                    if (isolationLevel == field.getInt(null)) {

                        return name;
                    }
                } catch (IllegalArgumentException | IllegalAccessException e) {
                }

            }

        }

        return String.valueOf(isolationLevel);
    }

}