package org.sef4j.jdbc.optional.hikari;
import java.sql.Connection;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import javax.sql.DataSource;
import org.sef4j.jdbc.optional.InstrumenterHelper;
import org.sef4j.jdbc.wrappers.SefConnectionProxy;
import org.sef4j.jdbc.wrappers.SefDataSourceProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.pool.HikariPool;
import com.zaxxer.hikari.pool.PoolBagEntry;
import com.zaxxer.hikari.util.ConcurrentBag;
import com.zaxxer.hikari.util.ConcurrentBag.BagEntry;
public class HikariDataSourceProxyInstrumenter extends InstrumenterHelper {
private static final Logger LOG = LoggerFactory.getLogger(HikariDataSourceProxyInstrumenter.class);
public static SefDataSourceProxy injectSefDataSourceProxyInto(HikariDataSource ds) {
// before: HikariDS -> .datasource=UnderlyngDS
// after : HikariDS -> .datasource=SefDataSourceProxy -> .to=UnderlyngDS
DataSource delegateDataSource = ds.getDataSource();
SefDataSourceProxy newDS = new SefDataSourceProxy(delegateDataSource);
ds.setDataSource(newDS);
// problem: ... should also rewrap Pool and all already opened Pooled Connection
// HikariDataSource (final).pool .... contains final reference to UnderlyingDS !!!
// => must wrap underlying DataSource directly ...
// or use reflect (or UNSAFE) to force change final field ?!!
HikariPool pool = (HikariPool) unsafeGetField(ds, "pool");
unsafeSetField(pool, "dataSource", newDS);
int totalConnections = pool.getTotalConnections();
int activeConnections = pool.getActiveConnections();
if (totalConnections != 0) {
LOG.info("injectSefDataSourceProxyInto()... pool already contains " + activeConnections + " active /" + totalConnections + " total connections! ... also injecting");
ConcurrentBag<PoolBagEntry> connectionBag = unsafeGetField(pool, "connectionBag");
// borrow all ?... instrument them, and requite them
CopyOnWriteArrayList<PoolBagEntry> sharedList = unsafeGetField(connectionBag, "sharedList");
for(int i = 0; i < sharedList.size(); i++) {
PoolBagEntry bagEntry = sharedList.get(i);
if (bagEntry != null) {
AtomicInteger bagEntryState = (AtomicInteger) unsafeGetField(bagEntry, BagEntry.class, "state");
if (bagEntryState.compareAndSet(ConcurrentBag.STATE_NOT_IN_USE, ConcurrentBag.STATE_IN_USE)) {
injectSefConnectionProxy(newDS, bagEntry);
bagEntryState.compareAndSet(ConcurrentBag.STATE_IN_USE, ConcurrentBag.STATE_NOT_IN_USE);
} else {
LOG.error("Connection already in used?! ... should call injectSefDataSourceProxyInto(); only at startup");
// do anyway?! ... may forece error, like bagEntry.connection.close();
injectSefConnectionProxy(newDS, bagEntry);
}
}
}
}
return newDS;
}
private static void injectSefConnectionProxy(SefDataSourceProxy newDS, PoolBagEntry bagEntry) {
Connection prevConnection = bagEntry.connection;
Connection newConnection = new SefConnectionProxy(newDS, prevConnection);
unsafeSetField(bagEntry, "connection", newConnection);
}
}