package org.caudexorigo.jdbc; import java.io.Closeable; import java.sql.ResultSet; import java.util.Collection; import java.util.Collections; import java.util.Properties; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DbPool implements Closeable { private static final int DEFAULT_MAX_POOL_SIZE = 20; private static final int DEFAULT_TIMEOUT = 5; // 5 seconds private static final int DEFAULT_TTL = 300; // 5 minutes private static final boolean DEFAULT_USE_CACHE = true; private static final Logger log = LoggerFactory.getLogger(DbPool.class); private Db dequeue(BlockingQueue<Db> pool) { try { Db d = pool.poll(timeout, TimeUnit.SECONDS); if (d == null) { throw new RuntimeException("Could not acquire a connection from the pool in the specified timeout."); } if (!d.isActive()) { d.destroy(); DbInfo dbinfo_o = d.getDbInfo(); DbInfo dbinfo_n = new DbInfo(dbinfo_o.getConnectionGroupName(), dbinfo_o.getDriverClass(), dbinfo_o.getDriverUrl(), dbinfo_o.getUsername(), dbinfo_o.getPassword(), dbinfo_o.getTtl(), dbinfo_o.getQueryTimeout(), dbinfo_o.getUseCache()); d = new Db(dbinfo_n); } if (log.isDebugEnabled()) { log.debug("Using connection: {}", d.getDbInfo().toString()); } return d; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } public static void destroy(Db dbexec) { dbexec.destroy(); } public DbType getDbType() { return dbType; } public Set<String> getPoolNames() { return Collections.unmodifiableSet(pools.keySet()); } public Db obtain() { return obtain(defaultPool); } public Db obtain(String poolName) { if (log.isDebugEnabled()) { log.debug("Obtain connection group: {}", poolName); } return dequeue(pools.get(poolName)); } public void release(Db d) { if (d != null) { String poolName = d.getDbInfo().getConnectionGroupName(); if (d.isActive()) { pools.get(poolName).offer(d); } else { d.destroy(); DbInfo dbinfo_o = d.getDbInfo(); DbInfo dbinfo_n = new DbInfo(dbinfo_o.getConnectionGroupName(), dbinfo_o.getDriverClass(), dbinfo_o.getDriverUrl(), dbinfo_o.getUsername(), dbinfo_o.getPassword(), dbinfo_o.getTtl(), dbinfo_o.getQueryTimeout(), dbinfo_o.getUseCache()); pools.get(poolName).offer(new Db(dbinfo_n)); } } } private int con_nr; private final AtomicInteger current_idx = new AtomicInteger(0); private DbConfigReader dbcr; private DbType dbType; private String defaultPool; private ConcurrentMap<String, BlockingQueue<Db>> pools; private int query_timeout; private boolean use_cache; private int timeout; private int ttl; public DbPool(Properties props) { dbcr = new DbConfigReader(props); init(); } public DbPool(String bundleName) { dbcr = new DbConfigReader(bundleName); init(); } public void close() { Collection<BlockingQueue<Db>> inner_pools = pools.values(); for (BlockingQueue<Db> bq : inner_pools) { for (Db db : bq) { db.destroy(); } bq.clear(); } pools.clear(); } public void ping(String sql, PingHandler ping_handler) { Collection<BlockingQueue<Db>> inner_pools = pools.values(); for (BlockingQueue<Db> bq : inner_pools) { for (Db db : bq) { ResultSet rs = null; try { rs = db.fetchResultSetWithStatment(sql); ping_handler.beforeFirst(rs); while (rs.next()) { ping_handler.process(rs); } ping_handler.afterLast(rs); ping_handler.result(db, null); } catch (Throwable ex) { db.destroy(); ping_handler.result(db, ex); } finally { Db.closeQuietly(rs); } } } } public Db pick() { int s = pools.size(); if (s == 1) { return obtain(); } if (s == 0) { throw new RuntimeException("No available pools from which to pick a connection."); } int n = Math.abs(current_idx.incrementAndGet() % s); Collection<BlockingQueue<Db>> db_queues = pools.values(); int idx = 0; for (BlockingQueue<Db> pool : db_queues) { idx++; if (idx > n) { return dequeue(pool); } } // we should never get here but i've been wrong before. in that case // just return the default connection return obtain(); } public void reset() { close(); init(); } private void init() { con_nr = tryParse("connections", dbcr.getString("connections"), 1); defaultPool = dbcr.getString("default.connection"); ttl = tryParse("connection.ttl", dbcr.getString("connection.ttl"), DEFAULT_TTL); timeout = tryParse("connection.timeout", dbcr.getString("connection.timeout"), DEFAULT_TIMEOUT); query_timeout = tryParse("query.timeout", dbcr.getString("query.timeout"), DEFAULT_TIMEOUT); use_cache = tryParse("use.statement.cache", dbcr.getString("use.statement.cache"), DEFAULT_USE_CACHE); log.info("Number of configured database connection groups: {}", con_nr); log.info("Default database connection group: '{}'", defaultPool); pools = new ConcurrentHashMap<String, BlockingQueue<Db>>(); for (int i = 1; i <= con_nr; i++) { int con_poll_size = tryParse(i + ".max.pool.size", dbcr.getString(i + ".max.pool.size"), DEFAULT_MAX_POOL_SIZE); log.info("Connection group: '{}'; Number of connections: {}", i, con_poll_size); String con_name = "" + i; String driver_class = dbcr.getString(i + ".driver.name"); String driver_url = dbcr.getString(i + ".conn.url"); String user = dbcr.getString(i + ".user.name"); String password = dbcr.getString(i + ".password"); DbInfo dbinfo = new DbInfo(con_name, driver_class, driver_url, user, password, ttl, query_timeout, use_cache); log.info("Connection: {}", dbinfo.toString()); try { Class.forName(driver_class); setDbType(con_name, driver_class); } catch (Exception e) { throw new IllegalArgumentException("Jdbc driver class not found", e); } BlockingQueue<Db> pool = new LinkedBlockingQueue<Db>(con_poll_size); for (int j = 0; j < con_poll_size; j++) { try { Db dbexec = new Db(dbinfo); pool.offer(dbexec); } catch (Throwable t) { throw new RuntimeException(t); } } pools.put(con_name, pool); } } private void setDbType(String con_name, String driver_class) { if (con_name.equals(defaultPool)) { if ("org.postgresql.Driver".equals(driver_class)) { dbType = DbType.PGSQL; } else if ("com.mysql.jdbc.Driver".equals(driver_class)) { dbType = DbType.MYSQL; } else if ("net.sourceforge.jtds.jdbc.Driver".equals(driver_class) || "com.microsoft.sqlserver.jdbc.SQLServerDriver".equals(driver_class)) { dbType = DbType.MSSQL; } else if ("oracle.jdbc.OracleDriver".equals(driver_class)) { dbType = DbType.ORACLE; } else if ("org.h2.Driver".equals(driver_class)) { dbType = DbType.H2; } else if ("nl.cwi.monetdb.jdbc.MonetDriver".equals(driver_class)) { dbType = DbType.MONETDB; } else { dbType = DbType.OTHER; } } } private int tryParse(String fieldName, String intValue, int defaultValue) { int r; try { if (StringUtils.isBlank(intValue)) { log.warn(String.format("Apply default value for '%s': %s", fieldName, defaultValue)); r = defaultValue; } else { r = Integer.parseInt(intValue); } } catch (Throwable t) { log.warn(String.format("Apply default value for '%s': %s -> '%s'", fieldName, defaultValue, t.getMessage())); r = defaultValue; } return r; } private boolean tryParse(String fieldName, String boolValue, boolean defaultValue) { boolean r; try { if (StringUtils.isBlank(boolValue)) { log.warn(String.format("Apply default value for '%s': %s", fieldName, defaultValue)); r = defaultValue; } else { r = Boolean.parseBoolean(boolValue); } } catch (Throwable t) { log.warn(String.format("Apply default value for '%s': %s -> '%s'", fieldName, defaultValue, t.getMessage())); r = defaultValue; } return r; } @Override public String toString() { return String.format("DbPool [propos=%s]", dbcr.getPropsAsString()); } }