/*********************************************************************************
* The contents of this file are subject to the Common Public Attribution
* License Version 1.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.openemm.org/cpal1.html. The License is based on the Mozilla
* Public License Version 1.1 but Sections 14 and 15 have been added to cover
* use of software over a computer network and provide for limited attribution
* for the Original Developer. In addition, Exhibit A has been modified to be
* consistent with Exhibit B.
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
* the specific language governing rights and limitations under the License.
*
* The Original Code is OpenEMM.
* The Original Developer is the Initial Developer.
* The Initial Developer of the Original Code is AGNITAS AG. All portions of
* the code written by AGNITAS AG are Copyright (c) 2007 AGNITAS AG. All Rights
* Reserved.
*
* Contributor(s): AGNITAS AG.
********************************************************************************/
package org.agnitas.dao;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.commons.dbcp.DriverConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.dbcp.SQLNestedException;
import org.apache.commons.pool.impl.GenericKeyedObjectPool;
import org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.log4j.Logger;
public class LoggingEnhBasicDataSource extends EnhBasicDataSource {
private static final transient Logger logger = Logger.getLogger(LoggingEnhBasicDataSource.class);
private static long last=0;
public static String trace2string(StackTraceElement[] st) {
String trace="";
int i=0;
for(int c=0; c < st.length && i < 5; c++) {
while(c < st.length) {
if(st[c].toString().substring(4, 11).equals("agnitas")) {
trace+=st[c].toString()+"\t";
i++;
break;
}
c++;
}
}
return trace;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
public class LoggingObjectPool extends GenericObjectPool {
private Map active=new HashMap();
public void dumpMap() {
GregorianCalendar cal=new GregorianCalendar();
long cur=cal.getTimeInMillis();
if(cur > last+3000 || this.getNumActive() > 30) {
HashMap act=new HashMap(active);
Iterator i=act.keySet().iterator();
logger.error("Connections: " + this.getNumActive() + " class " + this.getClass());
logger.error("+++++++++++++++++++++++++++++");
while(i.hasNext()) {
Object con=(Object) i.next();
StackTraceElement[] st=(StackTraceElement[])act.get(con);
String trace=LoggingEnhBasicDataSource.trace2string(st);
logger.error("Connection " + con.toString() + "(" + con.hashCode() + ") " + trace);
}
last=cur;
}
}
public Object borrowObject() throws Exception {
Exception e=new Exception();
dumpMap();
Object ret=super.borrowObject();
active.put(ret, e.getStackTrace());
return ret;
}
public void invalidateObject(Object obj) throws Exception {
super.invalidateObject(obj);
}
public void returnObject(Object obj) throws Exception {
active.remove(obj);
super.returnObject(obj);
}
}
private static void validateConnectionFactory(PoolableConnectionFactory connectionFactory) throws Exception {
Connection conn = null;
try {
conn = (Connection) connectionFactory.makeObject();
connectionFactory.activateObject(conn);
connectionFactory.validateConnection(conn);
connectionFactory.passivateObject(conn);
}
finally {
connectionFactory.destroyObject(conn);
}
}
/**
* <p>Create (if necessary) and return the internal data source we are
* using to manage our connections.</p>
*
* <p><strong>IMPLEMENTATION NOTE</strong> - It is tempting to use the
* "double checked locking" idiom in an attempt to avoid synchronizing
* on every single call to this method. However, this idiom fails to
* work correctly in the face of some optimizations that are legal for
* a JVM to perform.</p>
*
* @exception SQLException if the object pool cannot be created.
*/
protected synchronized DataSource createDataSource()
throws SQLException {
// Return the pool if we have already created it
if (dataSource != null) {
return (dataSource);
}
logger.error("-------------------------------------------- Create new datasource");
// Load the JDBC driver class
if (driverClassName != null) {
try {
Class.forName(driverClassName);
} catch (Throwable t) {
String message = "Cannot load JDBC driver class '" +
driverClassName + "'";
logWriter.println(message);
t.printStackTrace(logWriter);
throw new SQLNestedException(message, t);
}
}
// Create a JDBC driver instance
Driver driver = null;
try {
driver = DriverManager.getDriver(url);
} catch (Throwable t) {
String message = "Cannot create JDBC driver of class '" +
(driverClassName != null ? driverClassName : "") +
"' for connect URL '" + url + "'";
logWriter.println(message);
t.printStackTrace(logWriter);
throw new SQLNestedException(message, t);
}
// Can't test without a validationQuery
if (validationQuery == null) {
setTestOnBorrow(false);
setTestOnReturn(false);
setTestWhileIdle(false);
}
// Create an object pool to contain our active connections
connectionPool = new LoggingObjectPool();
connectionPool.setMaxActive(maxActive);
connectionPool.setMaxIdle(maxIdle);
connectionPool.setMinIdle(minIdle);
connectionPool.setMaxWait(maxWait);
connectionPool.setTestOnBorrow(testOnBorrow);
connectionPool.setTestOnReturn(testOnReturn);
connectionPool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
connectionPool.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
connectionPool.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
connectionPool.setTestWhileIdle(testWhileIdle);
// Set up statement pool, if desired
GenericKeyedObjectPoolFactory statementPoolFactory = null;
if (isPoolPreparedStatements()) {
statementPoolFactory = new GenericKeyedObjectPoolFactory(null,
-1, // unlimited maxActive (per key)
GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL,
0, // maxWait
1, // maxIdle (per key)
maxOpenPreparedStatements);
}
// Set up the driver connection factory we will use
if (username != null) {
connectionProperties.put("user", username);
} else {
logger.error("DBCP DataSource configured without a 'username'");
}
if (password != null) {
connectionProperties.put("password", password);
} else {
logger.error("DBCP DataSource configured without a 'password'");
}
DriverConnectionFactory driverConnectionFactory =
new DriverConnectionFactory(driver, url, connectionProperties);
// Set up the poolable connection factory we will use
PoolableConnectionFactory connectionFactory = null;
try {
connectionFactory =
new PoolableConnectionFactory(driverConnectionFactory,
connectionPool,
statementPoolFactory,
validationQuery,
defaultReadOnly,
defaultAutoCommit,
defaultTransactionIsolation,
defaultCatalog,
null);
if (connectionFactory == null) {
throw new SQLException("Cannot create PoolableConnectionFactory");
}
validateConnectionFactory(connectionFactory);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new SQLNestedException("Cannot create PoolableConnectionFactory (" + e.getMessage() + ")", e);
}
// Create and return the pooling data source to manage the connections
dataSource = new PoolingDataSource(connectionPool);
((PoolingDataSource) dataSource).setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
dataSource.setLogWriter(logWriter);
try {
for (int i = 0 ; i < initialSize ; i++) {
connectionPool.addObject();
}
} catch (Exception e) {
throw new SQLNestedException("Error preloading the connection pool", e);
}
return dataSource;
}
}