package javafun.utils.jdbc;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafun.utils.Domain;
import javafun.utils.DomainContext;
import javafun.utils.logging.Logger;
/**
* A connection manager for client-side, desktop applications. This manager
* creates a single physical connection to the underlying database. This manager
* returns proxies to the application that wrap the physical DB connection.
*
* If any call to the physical DB connection results in a sql exception then a
* new physical connection will be created on the call to the createConnection
* method. A physical DB connection is closed when all its proxies are closed.
*
* Physical connections are dumped whenever they fail for any reason and a new
* connection is created and the operation is retried.
*
* This strategy for managing database connection is highly efficient for
* desktop applications as it usually only creates a single database connection
* and it keeps that connection open.
*
* @see ServerConnectionManager
*
* @author ted stockwell
*/
public class DesktopConnectionManager extends JDBCConnectionManager {
/**
* The physical connection to the DB
*/
private final Map<Connection, List<InvocationHandler>> _proxiesByConnection = new HashMap<Connection, List<InvocationHandler>>();
/**
* The physical connection to the DB.
*/
private Connection _connection = null;
synchronized void closeProxy(ConnectionHandler proxy, Connection physicalConnection) throws SQLException {
List<InvocationHandler> proxies = _proxiesByConnection.get(physicalConnection);
if (proxies == null) {
closePhysicalConnection(physicalConnection);
} else {
proxies.remove(proxy);
if (proxies.isEmpty()) {
_proxiesByConnection.remove(physicalConnection);
closePhysicalConnection(physicalConnection);
} else {
Logger.debug("Database connection proxy closed. " + proxies.size() + " proxies still open");
}
}
}
private void closePhysicalConnection(Connection physicalConnection) throws SQLException {
if (physicalConnection == _connection) {
_connection = null;
}
if (physicalConnection != null) {
physicalConnection.close();
}
}
synchronized void getNewProxyConnection(ConnectionHandler proxy) throws SQLException {
try {
closeProxy(proxy, proxy._handlerConnection);
} catch (Throwable t) {
}
if (_connection == null) {
Domain domain = DomainContext.getCurrentDomain();
// _connection = JDBCConnection.getDatabaseInstance(domain.getDriver(), domain.getUrl(), domain.getUser(), domain.getPwd());
_connection = JDBCConnection.getDatabaseInstance(domain.getDriver(), domain.getUrl(), domain.getUser(), domain.getPwd());
}
proxy._handlerConnection = _connection;
List<InvocationHandler> proxies = _proxiesByConnection.get(_connection);
if (proxies == null) {
proxies = new ArrayList<InvocationHandler>();
_proxiesByConnection.put(_connection, proxies);
}
proxies.add(proxy);
}
/**
* @see com.rpc.core.utils.JDBCConnectionManager#getConnection()
*/
synchronized public Connection getConnection() throws SQLException {
if (_connection == null) {
Domain domain = DomainContext.getCurrentDomain();
_connection = JDBCConnection.getDatabaseInstance(domain.getDriver(), domain.getUrl(), domain.getUser(), domain.getPwd());
setSQLMode(_connection);
} else {
try {
pingConnection();
}
// for some reason connection is no good, reconnect
catch (Throwable t) {
if (_connection != null) {
_proxiesByConnection.remove(_connection);
}
Domain domain = DomainContext.getCurrentDomain();
_connection = JDBCConnection.getDatabaseInstance(domain.getDriver(), domain.getUrl(), domain.getUser(), domain.getPwd());
}
}
Connection proxy = (Connection) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { Connection.class }, new ConnectionHandler(
_connection));
List<InvocationHandler> proxies = _proxiesByConnection.get(_connection);
if (proxies == null) {
proxies = new ArrayList<InvocationHandler>();
_proxiesByConnection.put(_connection, proxies);
}
proxies.add(Proxy.getInvocationHandler(proxy));
return proxy;
}
private void pingConnection() throws SQLException {
/*
* ping the connection, if the ping fails then get a new connection and
* retry the method call.
*/
Statement pingStatement = _connection.createStatement();
try {
pingStatement.execute("select CURRENT_DATE");
} finally {
try {
if (pingStatement != null) {
pingStatement.close();
}
} catch (Throwable t) {
}
}
}
/**
* @see com.rpc.core.utils.JDBCConnectionManager#closeConnection(java.sql.Connection)
*/
public void closeConnection(Connection connection) throws SQLException {
connection.close();
}
class ConnectionHandler implements InvocationHandler {
Connection _handlerConnection;
/**
* Set to true if this handler actually changes the state
* of the underlying connection's autoCommit attribute to true.
* If true then autoCommit is set back to false when the handler is closed.
*/
boolean _resetAutoCommit = false;
public ConnectionHandler(Connection connection) {
_handlerConnection = connection;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("close")) {
try {
closeProxy(this, _handlerConnection);
} catch (SQLException x) {
throw new InvocationTargetException(x);
} finally {
/**
* The underlying connection's autoCommit state was changed to true by this handler.
* Set the auto commit back to false.
*/
if (_resetAutoCommit) {
try {
_handlerConnection.setAutoCommit(true);
} catch (Throwable t) {
}
}
}
return null;
}
if (method.getName().equals("setAutoCommit")) {
boolean value = ((Boolean) args[0]).booleanValue();
if (_handlerConnection.getAutoCommit() != value) {
if (value == false) {
_resetAutoCommit = true;
}
if (_resetAutoCommit && value == true) {
_resetAutoCommit = false;
}
_handlerConnection.setAutoCommit(value);
}
return null;
}
try {
return method.invoke(_handlerConnection, args);
} catch (InvocationTargetException e) {
/*
* Some error happened during a method call. Make sure that we
* create a new physical connection for the next call to
* getConnection.
*/
synchronized (DesktopConnectionManager.this) {
if (_handlerConnection == _connection) {
_connection = null;
}
}
// if we're in the middle of a transaction then we're hosed, give up...
if (_handlerConnection.getAutoCommit() == true) {
throw e.getCause();
}
// Don't trust the current connection, get a new connection
// and try again
try {
getNewProxyConnection(this);
} catch (SQLException x2) {
throw e; // give up
}
try {
return method.invoke(_handlerConnection, args);
} catch (InvocationTargetException e2) {
throw e; // give up
}
}
}
}
}