/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.frameworkset.commons.dbcp.datasources;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import com.frameworkset.commons.dbcp.PoolableConnection;
import com.frameworkset.commons.dbcp.SQLNestedException;
import com.frameworkset.commons.pool.ObjectPool;
import com.frameworkset.commons.pool.PoolableObjectFactory;
/**
* A {@link PoolableObjectFactory} that creates
* {@link PoolableConnection}s.
*
* @author John D. McNally
* @version $Revision: 479137 $ $Date: 2006-11-25 08:51:48 -0700 (Sat, 25 Nov 2006) $
*/
class CPDSConnectionFactory
implements PoolableObjectFactory, ConnectionEventListener {
private static final String NO_KEY_MESSAGE
= "close() was called on a Connection, but "
+ "I have no record of the underlying PooledConnection.";
protected ConnectionPoolDataSource _cpds = null;
protected String _validationQuery = null;
protected boolean _rollbackAfterValidation = false;
protected ObjectPool _pool = null;
protected String _username = null;
protected String _password = null;
private Map validatingMap = new HashMap();
private WeakHashMap pcMap = new WeakHashMap();
/**
* Create a new <tt>PoolableConnectionFactory</tt>.
*
* @param cpds the ConnectionPoolDataSource from which to obtain
* PooledConnection's
* @param pool the {@link ObjectPool} in which to pool those
* {@link Connection}s
* @param validationQuery a query to use to {@link #validateObject validate}
* {@link Connection}s. Should return at least one row. May be
* <tt>null</tt>
* @param username
* @param password
*/
public CPDSConnectionFactory(ConnectionPoolDataSource cpds,
ObjectPool pool,
String validationQuery,
String username,
String password) {
_cpds = cpds;
_pool = pool;
_pool.setFactory(this);
_validationQuery = validationQuery;
_username = username;
_password = password;
}
/**
* Create a new <tt>PoolableConnectionFactory</tt>.
*
* @param cpds the ConnectionPoolDataSource from which to obtain
* PooledConnection's
* @param pool the {@link ObjectPool} in which to pool those {@link
* Connection}s
* @param validationQuery a query to use to {@link #validateObject
* validate} {@link Connection}s. Should return at least one row.
* May be <tt>null</tt>
* @param rollbackAfterValidation whether a rollback should be issued
* after {@link #validateObject validating} {@link Connection}s.
* @param username
* @param password
*/
public CPDSConnectionFactory(ConnectionPoolDataSource cpds,
ObjectPool pool,
String validationQuery,
boolean rollbackAfterValidation,
String username,
String password) {
this(cpds, pool, validationQuery, username, password);
_rollbackAfterValidation = rollbackAfterValidation;
}
/**
* Sets the {*link ConnectionFactory} from which to obtain base
* {*link Connection}s.
* @param connFactory the {*link ConnectionFactory} from which to obtain
* base {*link Connection}s
*/
public synchronized void setCPDS(ConnectionPoolDataSource cpds) {
_cpds = cpds;
}
/**
* Sets the query I use to {*link #validateObject validate}
* {*link Connection}s.
* Should return at least one row.
* May be <tt>null</tt>
* @param validationQuery a query to use to {*link #validateObject validate}
* {*link Connection}s.
*/
public synchronized void setValidationQuery(String validationQuery) {
_validationQuery = validationQuery;
}
/**
* Sets whether a rollback should be issued after
* {*link #validateObject validating}
* {*link Connection}s.
* @param rollbackAfterValidation whether a rollback should be issued after
* {*link #validateObject validating}
* {*link Connection}s.
*/
public synchronized void setRollbackAfterValidation(
boolean rollbackAfterValidation) {
_rollbackAfterValidation = rollbackAfterValidation;
}
/**
* Sets the {*link ObjectPool} in which to pool {*link Connection}s.
* @param pool the {*link ObjectPool} in which to pool those
* {*link Connection}s
*/
public synchronized void setPool(ObjectPool pool) throws SQLException {
if (null != _pool && pool != _pool) {
try {
_pool.close();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new SQLNestedException("Cannot set the pool on this factory", e);
}
}
_pool = pool;
}
public ObjectPool getPool() {
return _pool;
}
public synchronized Object makeObject() {
Object obj;
try {
PooledConnection pc = null;
if (_username == null) {
pc = _cpds.getPooledConnection();
} else {
pc = _cpds.getPooledConnection(_username, _password);
}
// should we add this object as a listener or the pool.
// consider the validateObject method in decision
pc.addConnectionEventListener(this);
obj = new PooledConnectionAndInfo(pc, _username, _password);
pcMap.put(pc, obj);
} catch (SQLException e) {
throw new RuntimeException(e.getMessage());
}
return obj;
}
public void destroyObject(Object obj) throws Exception {
if (obj instanceof PooledConnectionAndInfo) {
((PooledConnectionAndInfo) obj).getPooledConnection().close();
}
}
public boolean validateObject(Object obj) {
boolean valid = false;
if (obj instanceof PooledConnectionAndInfo) {
PooledConnection pconn =
((PooledConnectionAndInfo) obj).getPooledConnection();
String query = _validationQuery;
if (null != query) {
Connection conn = null;
Statement stmt = null;
ResultSet rset = null;
// logical Connection from the PooledConnection must be closed
// before another one can be requested and closing it will
// generate an event. Keep track so we know not to return
// the PooledConnection
validatingMap.put(pconn, null);
try {
conn = pconn.getConnection();
stmt = conn.createStatement();
rset = stmt.executeQuery(query);
if (rset.next()) {
valid = true;
} else {
valid = false;
}
if (_rollbackAfterValidation) {
conn.rollback();
}
} catch (Exception e) {
valid = false;
} finally {
if (rset != null) {
try {
rset.close();
} catch (Throwable t) {
// ignore
}
}
if (stmt != null) {
try {
stmt.close();
} catch (Throwable t) {
// ignore
}
}
if (conn != null) {
try {
conn.close();
} catch (Throwable t) {
// ignore
}
}
validatingMap.remove(pconn);
}
} else {
valid = true;
}
} else {
valid = false;
}
return valid;
}
public void passivateObject(Object obj) {
}
public void activateObject(Object obj) {
}
// ***********************************************************************
// java.sql.ConnectionEventListener implementation
// ***********************************************************************
/**
* This will be called if the Connection returned by the getConnection
* method came from a PooledConnection, and the user calls the close()
* method of this connection object. What we need to do here is to
* release this PooledConnection from our pool...
*/
public void connectionClosed(ConnectionEvent event) {
PooledConnection pc = (PooledConnection) event.getSource();
// if this event occured becase we were validating, ignore it
// otherwise return the connection to the pool.
if (!validatingMap.containsKey(pc)) {
Object info = pcMap.get(pc);
if (info == null) {
throw new IllegalStateException(NO_KEY_MESSAGE);
}
try {
_pool.returnObject(info);
} catch (Exception e) {
System.err.println("CLOSING DOWN CONNECTION AS IT COULD "
+ "NOT BE RETURNED TO THE POOL");
try {
destroyObject(info);
} catch (Exception e2) {
System.err.println("EXCEPTION WHILE DESTROYING OBJECT "
+ info);
e2.printStackTrace();
}
}
}
}
/**
* If a fatal error occurs, close the underlying physical connection so as
* not to be returned in the future
*/
public void connectionErrorOccurred(ConnectionEvent event) {
PooledConnection pc = (PooledConnection)event.getSource();
try {
if (null != event.getSQLException()) {
System.err.println(
"CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR ("
+ event.getSQLException() + ")");
}
//remove this from the listener list because we are no more
//interested in errors since we are about to close this connection
pc.removeConnectionEventListener(this);
} catch (Exception ignore) {
// ignore
}
Object info = pcMap.get(pc);
if (info == null) {
throw new IllegalStateException(NO_KEY_MESSAGE);
}
try {
destroyObject(info);
} catch (Exception e) {
System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info);
e.printStackTrace();
}
}
}