/*
* Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
* Copyright [2016-2017] EMBL-European Bioinformatics Institute
*
* 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 org.ensembl.healthcheck.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Implements connection pooling.
*/
public final class ConnectionPool {
/** The logger to use for this class */
private static Logger logger = Logger.getLogger("HealthCheckLogger");
// store connections; key = database URL (as String), Connection object
private static Map<String, Connection> pool = new HashMap<String, Connection>();
// hide constructor to stop people instantiating this
private ConnectionPool() { }
/**
* Get a connection from the pool. If a connection to this database already
* exists in the pool, it is returned. If not, it is created and added to
* the pool.
*
* @return A new connection, or one re-used from the pool.
* @param driverClassName
* The class of the JDBC driver.
* @param databaseURL
* The URL of the database to connect to.
* @param user
* The username to connect to the database with.
* @param password
* The password for username.
* @throws SQLException
*/
public static Connection getConnection(String driverClassName, String databaseURL, String user, String password) throws SQLException {
Connection con = null;
if (pool.containsKey(databaseURL)) {
logger.finest("Got connection to " + databaseURL + " from pool");
con = getConnectionFromPool(driverClassName, databaseURL, user, password);
// mnuhn: Turn off connection pooling, see if this fixes the problems we are having.
//
//con = getConnectionByClassloader(driverClassName, databaseURL, user, password);
} else {
con = getConnectionByClassloader(driverClassName, databaseURL, user, password);
}
return con;
}
public static boolean isValidConnection(Connection con) {
String url;
boolean valid = true;
try {
url = con.getMetaData().getURL();
}
catch (SQLException e1) {
url = "(Could not get url of connection)";
}
Statement stmt = null;
ResultSet rs = null;
try {
stmt = con.createStatement();
rs = stmt.executeQuery("select 5;");
rs.next();
int i = rs.getInt(1);
if (i != 5) {
throw new RuntimeException("Got unexpected value (" + i + ") when "
+ "testing connection to " + url + ". Expected value was 5");
}
}
catch (Exception e) {
String message;
if (e instanceof java.net.SocketException) {
message = "Connection threw a SocketException, so the "
+ "connection probably timed out";
}
else {
if (e instanceof java.sql.SQLException) {
// The original exception thrown is an EOFException. It is
// wrapped by a SQLException, so that is what we get here.
message = "Connection threw a SQLException";
}
else {
message = "Exception thrown was not a SocketException and not a SQLException!"
+ "something else is going wrong";
}
}
logger.fine("Connection is not valid");
logger.log(Level.FINE, message, e);
valid = false;
}
finally {
DBUtils.closeQuietly(rs);
DBUtils.closeQuietly(stmt);
}
if(valid)
logger.fine("Connection is valid");
return valid;
}
public static Connection getConnectionFromPool(String driverClassName, String databaseURL, String user, String password) throws SQLException {
Connection con = (Connection) pool.get(databaseURL);
boolean connectionIsValid;
if (con.isClosed()) {
con = getConnectionByClassloader(driverClassName, databaseURL, user, password);
return con;
}
try {
// Currently throws a java.lang.AbstractMethodError, but maybe
// someday this will work.
//
connectionIsValid = con.isValid(5000);
} catch(java.lang.AbstractMethodError e) {
logger.finest("Connection object does not implement \"isValid()\" call. Using manual implementation");
connectionIsValid = isValidConnection(con);
}
if (!connectionIsValid) {
logger.warning("Connection in pool was invalid. Creating again from scratch.");
con = getConnectionByClassloader(driverClassName, databaseURL, user, password);
}
return con;
}
public static Connection getConnectionByClassloader(String driverClassName, String databaseURL, String user, String password) throws SQLException {
Connection con = null;
// create a connection and add it to the pool
try {
Class.forName(driverClassName);
} catch (ClassNotFoundException e) {
logger.severe("Can't load class " + driverClassName);
throw new RuntimeException(e);
}
con = DriverManager.getConnection(databaseURL, user, password);
pool.put(databaseURL, con);
logger.finest("Added connection to " + databaseURL + " to pool");
return con;
}
// -------------------------------------------------------------------------
/**
* Close all the connections in the pool.
*/
public static void closeAll() {
Set<String> keys = pool.keySet();
Iterator<String> it = keys.iterator();
while (it.hasNext()) {
try {
Connection con = (Connection) pool.get(it.next());
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
} // closeAll
} // ConnectionPool