/*
*-------------------
* The ConnectionPool.java is part of ASH Viewer
*-------------------
*
* ASH Viewer 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
* (at your option) any later version.
*
* ASH Viewer 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 ASH Viewer. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (c) 2009, Alex Kardapolov, All rights reserved.
*
*/
package org.ash.conn.model;
import java.sql.*;
import java.util.*;
/**
* The Class ConnectionPool.
*/
public class ConnectionPool implements Runnable{
/** The password. */
private String driver, url, username, password;
/** The max connections. */
private int maxConnections;
/** The wait if busy. */
private boolean waitIfBusy;
/** The busy connections. */
private Vector availableConnections, busyConnections;
/** The connection pending. */
private boolean connectionPending = false;
/**
* Instantiates a new connection pool.
*
* @param driver the driver
* @param url the url
* @param username the username
* @param password the password
* @param initialConnections the initial connections
* @param maxConnections the max connections
* @param waitIfBusy the wait if busy
*
* @throws SQLException the SQL exception
*/
public ConnectionPool(String driver, String url, String username,
String password, int initialConnections, int maxConnections,
boolean waitIfBusy) throws SQLException {
this.driver = driver;
this.url = url;
this.username = username;
this.password = password;
this.maxConnections = maxConnections;
this.waitIfBusy = waitIfBusy;
if (initialConnections > maxConnections) {
initialConnections = maxConnections;
}
availableConnections = new Vector(initialConnections);
busyConnections = new Vector();
for (int i = 0; i < initialConnections; i++) {
availableConnections.addElement(makeNewConnection());
}
}
/**
* Gets the connection.
*
* @return the connection
*
* @throws SQLException the SQL exception
*/
public synchronized Connection getConnection() throws SQLException {
if (!availableConnections.isEmpty()) {
Connection existingConnection = (Connection) availableConnections
.lastElement();
int lastIndex = availableConnections.size() - 1;
availableConnections.removeElementAt(lastIndex);
// If connection on available list is closed (e.g.,
// it timed out), then remove it from available list
// and repeat the process of obtaining a connection.
// Also wake up threads that were waiting for a
// connection because maxConnection limit was reached.
if (existingConnection.isClosed()) {
notifyAll(); // Freed up a spot for anybody waiting
return (getConnection());
} else {
busyConnections.addElement(existingConnection);
return (existingConnection);
}
} else {
// Three possible cases:
// 1) You haven�t reached maxConnections limit. So
// establish one in the background if there isn�t
// already one pending, then wait for
// the next available connection (whether or not
// it was the newly established one).
// 2) You reached maxConnections limit and waitIfBusy
// flag is false. Throw SQLException in such a case.
// 3) You reached maxConnections limit and waitIfBusy
// flag is true. Then do the same thing as in second
// part of step 1: wait for next available connection.
if ((totalConnections() < maxConnections) && !connectionPending) {
makeBackgroundConnection();
} else if (!waitIfBusy) {
throw new SQLException("Connection limit reached");
}
// Wait for either a new connection to be established
// (if you called makeBackgroundConnection) or for
// an existing connection to be freed up.
try {
wait();
} catch (InterruptedException ie) {
}
// Someone freed up a connection, so try again.
return (getConnection());
}
}
// You can�t just make a new connection in the foreground
// when none are available, since this can take several
// seconds with a slow network connection. Instead,
// start a thread that establishes a new connection,
// then wait. You get woken up either when the new connection
// is established or if someone finishes with an existing
// connection.
/**
* Make background connection.
*/
private void makeBackgroundConnection() {
connectionPending = true;
try {
Thread connectThread = new Thread(this);
connectThread.start();
} catch (OutOfMemoryError oome) {
// Give up on new connection
}
}
/* (non-Javadoc)
* @see org.oonline.conn.model.AbstractConnectionPool#run()
*/
public void run() {
try {
Connection connection = makeNewConnection();
synchronized (this) {
availableConnections.addElement(connection);
connectionPending = false;
notifyAll();
}
} catch (Exception e) { // SQLException or OutOfMemory
// Give up on new connection and wait for existing one
// to free up.
}
}
/**
* This explicitly makes a new connection. Called in
* the foreground when initializing the ConnectionPool,
* and called in the background when running.
*
* @return the connection
*
* @throws SQLException the SQL exception
*/
private Connection makeNewConnection() throws SQLException {
try {
// Load database driver if not already loaded
Class.forName(driver);
// Establish network connection to database
Connection connection = DriverManager.getConnection(url, username,
password);
// set module and action name for session
setModuleActionName(connection);
return (connection);
} catch (ClassNotFoundException cnfe) {
// Simplify try/catch blocks of people using this by
// throwing only one exception type.
throw new SQLException("Can�t find class for driver: " + driver);
}
}
/**
* Sets the module action name DBMS_APPLICATION_INFO.SET_MODULE
*
* @param conn the new module action name
*/
private void setModuleActionName(Connection conn) {
CallableStatement stmt = null;
try {
stmt = conn
.prepareCall("begin DBMS_APPLICATION_INFO.SET_MODULE(?,?); end;");
stmt.setString(1, "ASH Viewer");
stmt.setString(2, "ASH Viewer");
stmt.execute();
} catch (SQLException ex) {
} finally {
try {
if (stmt != null) {
stmt.close(); // close the statement
}
} catch (SQLException ex) {
}
}
}
/**
* Free the connection
*
* @param connection the connection
*/
public synchronized void free(Connection connection) {
busyConnections.removeElement(connection);
availableConnections.addElement(connection);
// Wake up threads that are waiting for a connection
notifyAll();
}
/**
* Get number of total connections.
*
* @return the int
*/
public synchronized int totalConnections() {
return (availableConnections.size() + busyConnections.size());
}
/**
* Close all connections.
*/
public synchronized void closeAllConnections() {
closeConnections(availableConnections);
availableConnections = new Vector();
closeConnections(busyConnections);
busyConnections = new Vector();
}
/**
* Close connections.
*
* @param connections the connections
*/
private void closeConnections(Vector connections) {
try {
for (int i = 0; i < connections.size(); i++) {
Connection connection = (Connection) connections.elementAt(i);
if (!connection.isClosed()) {
connection.close();
}
}
} catch (SQLException sqle) {
// Ignore errors; garbage collect anyhow
}
}
/* (non-Javadoc)
* @see org.oonline.conn.model.AbstractConnectionPool#toString()
*/
@Override
public synchronized String toString() {
String info = "ConnectionPool(" + url + "," + username + ")"
+ ", available=" + availableConnections.size() + ", busy="
+ busyConnections.size() + ", max=" + maxConnections;
return (info);
}
}