// Copyright 2010 Google 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.google.enterprise.connector.util.database;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sql.DataSource;
/**
* A pool of JDBC database {@link Connection Connections}. In certain
* JDBC implementations, database Connections may be expensive and
* time-consuming to open. This pool maintains a LIFO stack of open
* Connections in an attempt to re-use existing Connections to the database.
* <p>
* This class requires a JDBC driver that supports the {@code isValid}
* method of {@code java.sql.Connection}, part of the JDBC 4.0
* specification in Java 6.
*
* @since 2.8
*/
public class DatabaseConnectionPool {
private static final Logger LOGGER =
Logger.getLogger(DatabaseConnectionPool.class.getName());
private final DataSource dataSource;
private final LinkedList<Connection> connections = new LinkedList<Connection>();
/**
* Constructs a pool to hold cached {@link Connection Connections}
* to the suppied JDBC {@link DataSource}. The pool is initially empty.
*
* @param dataSource a JDBC {@link DataSource}
*/
public DatabaseConnectionPool(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
protected void finalize() throws Throwable {
closeConnections();
}
/**
* Returns the JDBC {@link DataSource} that owns these {@code Connections}.
*
* @return the connection pool's {@link DataSource}
*/
public DataSource getDataSource() {
return dataSource;
}
/**
* Returns a {@link Connection} from the connection pool.
* If the pool is empty, a new {@code Connection} is
* obtained from the {@link DataSource}.
*
* @return a {@link Connection} to the {@link DataSource}
* @throws SQLException if a Connection cannot be obtained
*/
public synchronized Connection getConnection() throws SQLException {
while (!connections.isEmpty()) {
// Get a cached connection, but check if it is still functional.
Connection conn = connections.removeFirst();
if (isAlive(conn)) {
return conn;
}
// Close dead connection.
close(conn);
}
// Pool is empty. Get a new connection from the dataSource.
return dataSource.getConnection();
}
/**
* Releases a {@link Connection}, returning it to the connection pool
* for later re-use.
*
* @param connection a Connection to to return to the pool
*/
public synchronized void releaseConnection(Connection connection) {
connections.addFirst(connection);
}
/**
* Empties the connection pool, closing all its
* {@link Connection Connections}.
*/
public synchronized void closeConnections() {
for (Connection conn : connections) {
close(conn);
}
connections.clear();
}
/**
* Returns {@code true} if the connection is alive,
* {@code false} if it appears to be dead.
*/
private boolean isAlive(Connection conn) {
try {
// Using timeout as 1 second. If the timeout period expires before
// the operation completes, this method returns false.
return conn.isValid(1);
} catch (SQLException e) {
LOGGER.log(Level.INFO, "Connection is dead", e);
return false;
}
}
/** Closes the Connection silently. */
private void close(Connection conn) {
try {
conn.close();
} catch (SQLException ignored) {
}
}
}