Refactor experiment.

pull/3/head
Brett Wooldridge 11 years ago
parent 2bf2269606
commit 8b651d8b78

@ -1,4 +1,5 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- For release: mvn release:perform -Darguments=-Dgpg.passphrase=PASSPHRASE -->
@ -92,9 +93,9 @@
<type>maven-plugin</type>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.3.0</version>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun</groupId>
@ -104,10 +105,10 @@
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.18.1-GA</version>
<type>bundle</type>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.18.1-GA</version>
<type>bundle</type>
</dependency>
<dependency>
<groupId>net.snaq</groupId>
@ -127,6 +128,15 @@
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
@ -138,7 +148,7 @@
<Export-Package>com.zaxxer.hikari</Export-Package>
<Import-Package>com.sun.tools.attach,javassist.*,javax.management,javax.sql,javax.sql.rowset,,javax.sql.rowset.serial,,javax.sql.rowset.spi,org.slf4j</Import-Package>
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
<Agent-Class>com.zaxxer.hikari.javassist.HikariClassTransformer</Agent-Class>
<Agent-Class>com.zaxxer.hikari.javassist.HikariInstrumentationAgent</Agent-Class>
</instructions>
</configuration>
</plugin>

@ -33,7 +33,7 @@ import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zaxxer.hikari.javassist.HikariInstrumentationAgent;
import com.zaxxer.hikari.javassist.AgentRegistrationElf;
import com.zaxxer.hikari.proxy.IHikariConnectionProxy;
import com.zaxxer.hikari.proxy.JavassistProxyFactoryFactory;
import com.zaxxer.hikari.util.ClassLoaderUtils;
@ -84,8 +84,7 @@ public final class HikariPool implements HikariPoolMBean
this.dataSource = (DataSource) clazz.newInstance();
PropertyBeanSetter.setTargetFromProperties(dataSource, configuration.getDataSourceProperties());
HikariInstrumentationAgent instrumentationAgent = new HikariInstrumentationAgent(dataSource);
delegationProxies = !instrumentationAgent.loadTransformerAgent();
delegationProxies = !AgentRegistrationElf.loadTransformerAgent(configuration.getDataSourceClassName());
if (delegationProxies)
{
LOGGER.info("Falling back to Javassist delegate-based proxies.");
@ -138,7 +137,7 @@ public final class HikariPool implements HikariPoolMBean
idleConnectionCount.decrementAndGet();
final long maxLifetime = configuration.getMaxLifetime();
if (maxLifetime > 0 && start - connectionProxy.getCreationTime() > maxLifetime)
if (maxLifetime > 0 && start - connectionProxy._getCreationTime() > maxLifetime)
{
// Throw away the connection that has passed its lifetime, try again
closeConnection(connectionProxy);
@ -146,7 +145,7 @@ public final class HikariPool implements HikariPoolMBean
continue;
}
connectionProxy.unclose();
connectionProxy._unclose();
Connection connection = (Connection) connectionProxy;
if (!isConnectionAlive(connection, timeout))
@ -159,7 +158,7 @@ public final class HikariPool implements HikariPoolMBean
if (leakDetectionThreshold > 0)
{
connectionProxy.captureStack(leakDetectionThreshold, houseKeepingTimer);
connectionProxy._captureStack(leakDetectionThreshold, houseKeepingTimer);
}
return connection;
@ -182,9 +181,9 @@ public final class HikariPool implements HikariPoolMBean
*/
public void releaseConnection(IHikariConnectionProxy connectionProxy)
{
if (!connectionProxy.isBrokenConnection())
if (!connectionProxy._isBrokenConnection())
{
connectionProxy.markLastAccess();
connectionProxy._markLastAccess();
idleConnectionCount.incrementAndGet();
idleConnections.put(connectionProxy);
}
@ -290,7 +289,7 @@ public final class HikariPool implements HikariPoolMBean
proxyConnection = (IHikariConnectionProxy) connection;
}
proxyConnection.setParentPool(this);
proxyConnection._setParentPool(this);
boolean alive = isConnectionAlive((Connection) proxyConnection, configuration.getConnectionTimeout());
if (alive)
@ -436,9 +435,9 @@ public final class HikariPool implements HikariPoolMBean
idleConnectionCount.decrementAndGet();
if ((idleTimeout > 0 && now > connectionProxy.getLastAccess() + idleTimeout)
if ((idleTimeout > 0 && now > connectionProxy._getLastAccess() + idleTimeout)
||
(maxLifetime > 0 && now > connectionProxy.getCreationTime() + maxLifetime))
(maxLifetime > 0 && now > connectionProxy._getCreationTime() + maxLifetime))
{
closeConnection(connectionProxy);
}

@ -0,0 +1,134 @@
/*
* Copyright (C) 2013 Brett Wooldridge
*
* Licensed 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.zaxxer.hikari.javassist;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
/**
*
* @author Brett Wooldridge
*/
public class AgentRegistrationElf
{
private static final Logger LOGGER = LoggerFactory.getLogger(HikariClassScanner.class);
public static boolean loadTransformerAgent(String dsClassName)
{
String agentJarPath = getSelfJarPath();
if (agentJarPath == null)
{
LOGGER.warn("Cannot find the HikariCP jar file through introspection.");
return false;
}
try
{
Properties systemProperties = System.getProperties();
systemProperties.put("com.zaxxer.hikari.classloader", AgentRegistrationElf.class.getClassLoader());
HikariClassTransformer transformer = new HikariClassTransformer();
systemProperties.put("com.zaxxer.hikari.transformer", transformer);
registerInstrumentation(agentJarPath);
LOGGER.info("Successfully loaded instrumentation agent. Scanning classes...");
HikariClassScanner scanner = new HikariClassScanner(transformer);
scanner.scanClasses(dsClassName);
return true;
}
catch (Exception e)
{
LOGGER.warn("Instrumentation agent could not be loaded. Please report at http://github.com/brettwooldridge/HikariCP.", e);
return false;
}
// finally
// {
// unregisterInstrumenation();
// }
}
/**
* Get the path to the JAR file from which this class was loaded.
*
* @return the path to the jar file that contains this class
*/
private static String getSelfJarPath()
{
URL resource = AgentRegistrationElf.class.getResource("/com/zaxxer/hikari/javassist/HikariInstrumentationAgent.class");
if (resource == null)
{
return null;
}
String jarPath = resource.toString();
jarPath = jarPath.replace("file:", "");
jarPath = jarPath.replace("jar:", "");
if (jarPath.indexOf('!') > 0)
{
jarPath = jarPath.substring(0, jarPath.indexOf('!'));
}
else
{
return System.getProperty("com.zaxxer.hikari.selfJar");
}
return jarPath;
}
/**
* Attempt to register our instrumentation (class transformer) with the virtual machine
* dynamically.
*
* @param jarPath the path to our own jar file
* @param dsClassName
* @throws AttachNotSupportedException thrown if the JVM does not support attachment
* @throws IOException thrown if the instrumentation JAR cannot be read
* @throws AgentLoadException thrown if the instrumentation jar does not have proper headers
* @throws AgentInitializationException thrown if the agent had an error during initialization
*/
private static void registerInstrumentation(String jarPath) throws AttachNotSupportedException, IOException, AgentLoadException, AgentInitializationException
{
VirtualMachine vm = VirtualMachine.attach(getPid());
vm.loadAgent(jarPath);
vm.detach();
}
/**
* Get the PID of the running JVM.
*
* @return the process ID (PID) of the running JVM
*/
private static String getPid()
{
String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName();
int p = nameOfRunningVM.indexOf('@');
return nameOfRunningVM.substring(0, p);
}
}

@ -0,0 +1,403 @@
/*
* Copyright (C) 2013 Brett Wooldridge
*
* Licensed 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.zaxxer.hikari.javassist;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javassist.bytecode.ClassFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zaxxer.hikari.util.ClassLoaderUtils;
/**
*
* @author Brett Wooldridge
*/
public class HikariClassScanner
{
private static final Logger LOGGER = LoggerFactory.getLogger(HikariClassScanner.class);
private static final LinkedHashMap<String, Boolean> completionMap;
private final HashMap<String, ClassFile> instrumentableClasses;
// Static initializer
static
{
completionMap = new LinkedHashMap<>();
completionMap.put("java.sql.Connection", false);
completionMap.put("java.sql.ResultSet", false);
completionMap.put("java.sql.CallableStatement", false);
completionMap.put("java.sql.PreparedStatement", false);
completionMap.put("java.sql.Statement", false);
}
private HikariClassTransformer transformer;
private String sniffPackage;
public HikariClassScanner(HikariClassTransformer transformer)
{
instrumentableClasses = new HashMap<>();
this.transformer = transformer;
}
public boolean scanClasses(String dsClassName)
{
try
{
long start = System.currentTimeMillis();
sniffPackage = getDataSourceSubPackage(dsClassName);
boolean couldScan = searchInstrumentable(dsClassName);
if (!couldScan)
{
LOGGER.warn("Unable to find and instrument necessary classes. Please report at http://github.com/brettwooldridge/HikariCP.");
LOGGER.info("Using delegation instead of instrumentation");
return false;
}
HashSet<String> interfaces = findInterfaces("java.sql.Connection");
HashSet<String> rootClasses = findRootClasses(interfaces, HikariClassTransformer.CONNECTION);
findSubclasses(rootClasses, HikariClassTransformer.CONNECTION_SUBCLASS);
interfaces = findInterfaces("java.sql.Statement");
rootClasses = findRootClasses(interfaces, HikariClassTransformer.STATEMENT);
findSubclasses(rootClasses, HikariClassTransformer.STATEMENT_SUBCLASS);
interfaces = findInterfaces("java.sql.PreparedStatement");
rootClasses = findRootClasses(interfaces, HikariClassTransformer.PREPARED_STATEMENT);
findSubclasses(rootClasses, HikariClassTransformer.PREPARED_STATEMENT_SUBCLASS);
interfaces = findInterfaces("java.sql.CallableStatement");
rootClasses = findRootClasses(interfaces, HikariClassTransformer.CALLABLE_STATEMENT);
findSubclasses(rootClasses, HikariClassTransformer.CALLABLE_STATEMENT_SUBCLASS);
interfaces = findInterfaces("java.sql.ResultSet");
rootClasses = findRootClasses(interfaces, HikariClassTransformer.RESULTSET);
findSubclasses(rootClasses, HikariClassTransformer.RESULTSET_SUBCLASS);
LOGGER.info("Instrumented JDBC classes in {}ms.", System.currentTimeMillis() - start);
return true;
}
catch (Exception e)
{
return false;
}
}
/**
* Search the jar (or directory hierarchy) that contains the DataSource and force the class
* loading of the classes we are about instrumenting. See loadIfInstrumentable() for more
* detail.
*
* @param dataSource
* @throws IOException
*/
private boolean searchInstrumentable(String dsClassName) throws Exception
{
String searchPath = getSearchPath(dsClassName);
if (searchPath == null)
{
return false;
}
if (searchPath.endsWith(".jar"))
{
return scanInstrumentableJar(searchPath);
}
else
{
String dsSubPath = getDataSourceSubPackage(dsClassName).replace('.', '/');
String classRoot = searchPath.replace(dsSubPath, "");
// Drop one segment off of the path for a slightly broader search
searchPath = searchPath.substring(0, searchPath.lastIndexOf('/'));
return scanInstrumentableDirectory(classRoot, searchPath);
}
}
private boolean scanInstrumentableJar(String searchPath) throws IOException, ClassNotFoundException
{
File jarPath = new File(URI.create(searchPath));
if (!jarPath.isFile())
{
return false;
}
JarFile jarFile = new JarFile(jarPath, false, JarFile.OPEN_READ);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements())
{
JarEntry jarEntry = entries.nextElement();
if (jarEntry.isDirectory())
{
continue;
}
String entryName = jarEntry.getName();
if (entryName.endsWith(".class") && entryName.startsWith(sniffPackage) && entryName.indexOf('$') == -1)
{
InputStream inputStream = jarFile.getInputStream(jarEntry);
trackClass(inputStream);
inputStream.close();
}
}
jarFile.close();
return true;
}
/**
* @param classRoot
* @param searchPath
* @return true if the search completed without error
* @throws IOException
* @throws ClassNotFoundException
*/
private boolean scanInstrumentableDirectory(String classRoot, String searchPath) throws IOException, ClassNotFoundException
{
File directory = new File(searchPath);
if (!directory.isDirectory())
{
return false;
}
for (File fileEntry : directory.listFiles())
{
if (fileEntry.isDirectory())
{
scanInstrumentableDirectory(classRoot, fileEntry.getPath());
continue;
}
String fileName = fileEntry.getPath();
String className = fileName.replace(classRoot, "");
if (className.endsWith(".class") && className.startsWith(sniffPackage) && className.indexOf('$') == -1)
{
className = className.replace(".class", "").replace('/', '.');
InputStream inputStream = new FileInputStream(fileEntry);
trackClass(inputStream);
inputStream.close();
}
}
return true;
}
private void trackClass(InputStream inputStream) throws IOException
{
DataInputStream dis = new DataInputStream(inputStream);
ClassFile classFile = new ClassFile(dis);
instrumentableClasses.put(classFile.getName(), classFile);
}
private HashSet<String> findInterfaces(String interfaceName)
{
HashSet<String> subInterfaces = new HashSet<>();
subInterfaces.add(interfaceName);
for (ClassFile classFile : instrumentableClasses.values())
{
if (!classFile.isInterface())
{
continue;
}
HashSet<String> interfaces = new HashSet<>(Arrays.asList(classFile.getInterfaces()));
if (interfaces.contains(interfaceName))
{
subInterfaces.add(classFile.getName());
}
}
return subInterfaces;
}
private HashSet<String> findRootClasses(HashSet<String> interfaces, int classType) throws ClassNotFoundException
{
HashSet<String> rootClasses = new HashSet<>();
for (ClassFile classFile : instrumentableClasses.values())
{
if (classFile.isInterface())
{
continue;
}
HashSet<String> ifaces = new HashSet<>(Arrays.asList(classFile.getInterfaces()));
ifaces.retainAll(interfaces);
if (ifaces.size() == 0)
{
continue;
}
// Now we found a class that implements java.sql.Connection
// ... walk up to the top of the hierarchy
String currentClass = classFile.getName();
while (true)
{
ClassFile superClass = instrumentableClasses.get(currentClass);
if (superClass == null)
{
break;
}
String maybeSuper = superClass.getSuperclass();
if (maybeSuper.equals("java.lang.Object"))
{
rootClasses.add(superClass.getName());
break;
}
currentClass = maybeSuper;
}
}
if (rootClasses.isEmpty())
{
throw new RuntimeException("Unable to find root class implementation of " + interfaces);
}
transformer.setScanClass(rootClasses, classType);
for (String rootClass : rootClasses)
{
ClassLoaderUtils.loadClass(rootClass);
}
transformer.setScanClass(new HashSet<String>(), HikariClassTransformer.UNDEFINED);
return rootClasses;
}
private void findSubclasses(HashSet<String> rootClasses, int classType) throws ClassNotFoundException
{
HashSet<String> subClasses = new HashSet<>();
subClasses.addAll(rootClasses);
boolean exhausted;
do {
exhausted = true;
for (ClassFile classFile : instrumentableClasses.values())
{
if (subClasses.contains(classFile.getSuperclass()) && !subClasses.contains(classFile.getName()))
{
exhausted = false;
subClasses.add(classFile.getName());
}
}
} while (!exhausted);
if (subClasses.size() > 1)
{
subClasses.removeAll(rootClasses);
}
transformer.setScanClass(subClasses, classType);
for (String subClass : subClasses)
{
ClassLoaderUtils.loadClass(subClass);
}
subClasses.clear();
transformer.setScanClass(subClasses, HikariClassTransformer.UNDEFINED);
}
/**
* Get the path to the JAR or file system directory where the class of the user
* specified DataSource implementation resides.
*
* @param dataSource the user specified DataSource
* @return the path to the JAR (including the .jar file name) or a file system classes directory
*/
private String getSearchPath(String dsClassName)
{
URL resource = this.getClass().getResource('/' + dsClassName.replace('.', '/') + ".class");
if (resource == null)
{
return null;
}
String path = resource.toString();
if (path.startsWith("jar:"))
{
// original form jar:file:/path, make a path like file:///path
path = path.substring(4, path.indexOf('!')).replace(":/", ":///");
}
else if (path.startsWith("file:"))
{
path = path.substring(0, path.lastIndexOf('/')).replace("file:", "");
}
else
{
LOGGER.warn("Could not determine path type of {}", path);
return null;
}
return path;
}
/**
* Given a DataSource class, find the package name that is one-level above the package of
* the DataSource. For example, org.hsqldb.jdbc.DataSource -> org.hsqldb. This is used
* to filter out packages quickly that we are not interested in instrumenting.
*
* @param dataSource a DataSource
* @return the shortened package name used for filtering
*/
static String getDataSourceSubPackage(String dsClassName)
{
String packageName = dsClassName.substring(0, dsClassName.lastIndexOf('.'));
// Count how many segments in the package name. For example, org.hsqldb.jdbc has three segments.
int dots = 0;
int[] offset = new int[16];
for (int ndx = packageName.indexOf('.'); ndx != -1; ndx = packageName.indexOf('.', ndx + 1))
{
offset[dots] = ndx;
dots++;
}
if (dots > 3)
{
packageName = packageName.substring(0, offset[dots - 2]);
}
else if (dots > 1)
{
packageName = packageName.substring(0, offset[dots - 1]);
}
return packageName.replace('.', '/');
}
}

@ -20,7 +20,6 @@ import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.HashSet;
@ -32,7 +31,9 @@ import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewConstructor;
import javassist.CtNewMethod;
import javassist.LoaderClassPath;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
@ -49,92 +50,92 @@ public class HikariClassTransformer implements ClassFileTransformer
{
private static final Logger LOGGER = LoggerFactory.getLogger(HikariClassTransformer.class);
private static Instrumentation ourInstrumentation;
private static HikariClassTransformer transformer;
private static ClassPool classPool;
public static final int UNDEFINED = 0;
public static final int CONNECTION = 1;
public static final int STATEMENT = 2;
public static final int PREPARED_STATEMENT = 3;
public static final int CALLABLE_STATEMENT = 4;
public static final int RESULTSET = 5;
public static final int CONNECTION_SUBCLASS = 6;
public static final int STATEMENT_SUBCLASS = 7;
public static final int PREPARED_STATEMENT_SUBCLASS = 8;
public static final int CALLABLE_STATEMENT_SUBCLASS = 9;
public static final int RESULTSET_SUBCLASS = 10;
private String sniffPackage;
private static ClassPool classPool;
private volatile boolean agentFailed;
private volatile HashSet<String> scanClasses;
private int classType;
/**
* Private constructor.
*
* @param sniffPackage the package name used to filter only classes we are interested in
*/
private HikariClassTransformer(String sniffPackage)
public HikariClassTransformer()
{
this.sniffPackage = sniffPackage;
HikariClassTransformer.transformer = this;
}
/**
* The method that is called when VirtualMachine.loadAgent() is invoked to register our
* class transformer.
*
* @param agentArgs arguments to pass to the agent
* @param inst the virtual machine Instrumentation instance used to register our transformer
*/
public static void agentmain(String agentArgs, Instrumentation instrumentation)
public void setScanClass(HashSet<String> scanClasses, int classType)
{
ourInstrumentation = instrumentation;
ClassPool defaultPool = ClassPool.getDefault();
classPool = new ClassPool(defaultPool);
classPool.importPackage("java.sql");
classPool.childFirstLookup = true;
ourInstrumentation.addTransformer(new HikariClassTransformer(agentArgs), false);
this.scanClasses = new HashSet<>();
for (String scanClass : scanClasses)
{
this.scanClasses.add(scanClass.replace('.', '/'));
}
this.classType = classType;
}
/** {@inheritDoc} */
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
throws IllegalClassFormatException
{
if (!className.startsWith(sniffPackage))
if (classType == UNDEFINED || !scanClasses.contains(className))
{
return classfileBuffer;
}
if (classPool == null)
{
classPool = new ClassPool();
classPool.appendClassPath(new LoaderClassPath(loader));
}
try
{
ClassFile classFile = new ClassFile(new DataInputStream(new ByteArrayInputStream(classfileBuffer)));
if (classFile.isInterface())
{
return classfileBuffer;
}
for (String iface : classFile.getInterfaces())
{
if (!iface.startsWith("java.sql"))
{
continue;
}
LOGGER.info("Instrumenting class {}", className);
if (iface.equals("java.sql.Connection"))
{
return transformConnection(classFile);
}
else if (iface.equals("java.sql.PreparedStatement"))
{
return transformClass(classFile, "com.zaxxer.hikari.proxy.PreparedStatementProxy", "com.zaxxer.hikari.proxy.IHikariStatementProxy");
}
else if (iface.equals("java.sql.CallableStatement"))
{
return transformClass(classFile, "com.zaxxer.hikari.proxy.CallableStatementProxy", "com.zaxxer.hikari.proxy.IHikariStatementProxy");
}
else if (iface.equals("java.sql.Statement"))
{
return transformClass(classFile, "com.zaxxer.hikari.proxy.StatementProxy", "com.zaxxer.hikari.proxy.IHikariStatementProxy");
}
else if (iface.equals("java.sql.ResultSet"))
{
return transformClass(classFile, "com.zaxxer.hikari.proxy.ResultSetProxy", "com.zaxxer.hikari.proxy.IHikariResultSetProxy");
}
switch (classType)
{
case CONNECTION:
return transformBaseConnection(classFile);
case CONNECTION_SUBCLASS:
return transformConnectionSubclass(classFile);
case STATEMENT:
return transformBaseClass(classFile, "com.zaxxer.hikari.proxy.StatementProxy", "com.zaxxer.hikari.proxy.IHikariStatementProxy");
case STATEMENT_SUBCLASS:
return transformClass(classFile, "com.zaxxer.hikari.proxy.StatementProxy");
case PREPARED_STATEMENT:
return transformBaseClass(classFile, "com.zaxxer.hikari.proxy.PreparedStatementProxy", "com.zaxxer.hikari.proxy.IHikariStatementProxy");
case PREPARED_STATEMENT_SUBCLASS:
return transformClass(classFile, "com.zaxxer.hikari.proxy.PreparedStatementProxy");
case CALLABLE_STATEMENT:
return transformBaseClass(classFile, "com.zaxxer.hikari.proxy.CallableStatementProxy", "com.zaxxer.hikari.proxy.IHikariStatementProxy");
case CALLABLE_STATEMENT_SUBCLASS:
return transformClass(classFile, "com.zaxxer.hikari.proxy.CallableStatementProxy");
case RESULTSET:
return transformBaseClass(classFile, "com.zaxxer.hikari.proxy.ResultSetProxy", "com.zaxxer.hikari.proxy.IHikariResultSetProxy");
case RESULTSET_SUBCLASS:
return transformClass(classFile, "com.zaxxer.hikari.proxy.ResultSetProxy");
default:
// None of the interfaces we care about were found, so just return the class file buffer
return classfileBuffer;
}
// None of the interfaces we care about were found, so just return the class file buffer
return classfileBuffer;
}
catch (Exception e)
{
@ -142,6 +143,11 @@ public class HikariClassTransformer implements ClassFileTransformer
LOGGER.error("Error transforming class {}", className, e);
return classfileBuffer;
}
finally
{
LOGGER.debug("--------------------------------------------------------------------------");
//classType = UNDEFINED;
}
}
public boolean isAgentFailed()
@ -152,7 +158,7 @@ public class HikariClassTransformer implements ClassFileTransformer
/**
* @param classFile
*/
private byte[] transformConnection(ClassFile classFile) throws Exception
private byte[] transformBaseConnection(ClassFile classFile) throws Exception
{
String className = classFile.getName();
CtClass target = classPool.getCtClass(className);
@ -165,11 +171,31 @@ public class HikariClassTransformer implements ClassFileTransformer
copyFields(proxy, target);
copyMethods(proxy, target, classFile);
for (CtConstructor constructor : target.getConstructors())
{
constructor.insertAfter("__init();");
}
mergeClassInitializers(proxy, target, classFile);
specialConnectionInjectCloseCheck(target);
return transformConnectionSubclass(classFile);
}
/**
* @param classFile
*/
private byte[] transformConnectionSubclass(ClassFile classFile) throws Exception
{
String className = classFile.getName();
CtClass target = classPool.getCtClass(className);
CtClass proxy = classPool.get("com.zaxxer.hikari.proxy.ConnectionProxy");
overrideMethods(proxy, target, classFile);
injectTryCatch(target);
specialConnectionInjectCloseCheck(target);
for (CtConstructor constructor : target.getConstructors())
for (CtConstructor constructor : target.getDeclaredConstructors())
{
constructor.insertAfter("__init();");
}
@ -180,7 +206,7 @@ public class HikariClassTransformer implements ClassFileTransformer
/**
* @param classFile
*/
private byte[] transformClass(ClassFile classFile, String proxyClassName, String intfName) throws Exception
private byte[] transformBaseClass(ClassFile classFile, String proxyClassName, String intfName) throws Exception
{
String className = classFile.getName();
CtClass target = classPool.getCtClass(className);
@ -194,6 +220,21 @@ public class HikariClassTransformer implements ClassFileTransformer
copyFields(proxy, target);
copyMethods(proxy, target, classFile);
mergeClassInitializers(proxy, target, classFile);
return transformClass(classFile, proxyClassName);
}
/**
* @param classFile
*/
private byte[] transformClass(ClassFile classFile, String proxyClassName) throws Exception
{
String className = classFile.getName();
CtClass target = classPool.getCtClass(className);
CtClass proxy = classPool.get(proxyClassName);
overrideMethods(proxy, target, classFile);
injectTryCatch(target);
return target.toBytecode();
@ -208,7 +249,6 @@ public class HikariClassTransformer implements ClassFileTransformer
{
if (field.getAnnotation(HikariInject.class) == null)
{
LOGGER.debug("Skipped field {}", field.getName());
continue;
}
@ -228,7 +268,6 @@ public class HikariClassTransformer implements ClassFileTransformer
{
if (method.getAnnotation(HikariInject.class) == null)
{
LOGGER.debug("Skipped method {}", method.getName());
continue;
}
@ -256,6 +295,39 @@ public class HikariClassTransformer implements ClassFileTransformer
}
}
private void overrideMethods(CtClass srcClass, CtClass targetClass, ClassFile targetClassFile) throws Exception
{
ConstPool constPool = targetClassFile.getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
Annotation annotation = new Annotation("com.zaxxer.hikari.javassist.HikariOverride", constPool);
attr.setAnnotation(annotation);
for (CtMethod method : srcClass.getDeclaredMethods())
{
if (method.getAnnotation(HikariOverride.class) == null)
{
continue;
}
try
{
CtMethod destMethod = targetClass.getDeclaredMethod(method.getName(), method.getParameterTypes());
LOGGER.debug("Rename method {}{} to __{}", destMethod.getName(), destMethod.getSignature(), destMethod.getName());
destMethod.setName("__" + destMethod.getName());
destMethod.getMethodInfo().addAttribute(attr);
CtMethod copy = CtNewMethod.copy(method, targetClass, null);
copy.getMethodInfo().addAttribute(attr);
targetClass.addMethod(copy);
LOGGER.debug("Override method {}.{} in {}", method.getDeclaringClass().getSimpleName(), method.getName(), targetClass.getSimpleName());
}
catch (NotFoundException nfe)
{
continue;
}
}
}
private void mergeClassInitializers(CtClass srcClass, CtClass targetClass, ClassFile targetClassFile) throws Exception
{
CtConstructor srcInitializer = srcClass.getClassInitializer();
@ -291,7 +363,9 @@ public class HikariClassTransformer implements ClassFileTransformer
for (CtMethod method : targetClass.getDeclaredMethods())
{
if ((method.getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC || // only public methods
method.getAnnotation(HikariInject.class) != null) // ignore methods we've injected, they already try..catch
(method.getModifiers() & Modifier.STATIC) == Modifier.STATIC || // no static methods
method.getAnnotation(HikariInject.class) != null ||
method.getAnnotation(HikariOverride.class) != null) // ignore methods we've injected, they already try..catch
{
continue;
}
@ -306,7 +380,7 @@ public class HikariClassTransformer implements ClassFileTransformer
if ("java.sql.SQLException".equals(exception.getName())) // only add try..catch to methods throwing SQLException
{
LOGGER.debug("Injecting try..catch into {}{}", method.getName(), method.getSignature());
method.addCatch("throw checkException($e);", exception);
method.addCatch("throw _checkException($e);", exception);
break;
}
}
@ -338,12 +412,4 @@ public class HikariClassTransformer implements ClassFileTransformer
}
}
}
/**
*
*/
static void unregisterInstrumenation()
{
ourInstrumentation.removeTransformer(transformer);
}
}

@ -16,34 +16,9 @@
package com.zaxxer.hikari.javassist;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.net.URI;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javassist.bytecode.ClassFile;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.zaxxer.hikari.util.ClassLoaderUtils;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.util.Properties;
/**
*
@ -51,380 +26,22 @@ import com.zaxxer.hikari.util.ClassLoaderUtils;
*/
public class HikariInstrumentationAgent
{
private static final Logger LOGGER = LoggerFactory.getLogger(HikariInstrumentationAgent.class);
private static final LinkedHashMap<String, Boolean> completionMap;
private final HashMap<String, HashSet<String>> instrumentableClasses;
// Static initializer
static
{
completionMap = new LinkedHashMap<>();
completionMap.put("java.sql.Connection", false);
completionMap.put("java.sql.ResultSet", false);
completionMap.put("java.sql.CallableStatement", false);
completionMap.put("java.sql.PreparedStatement", false);
completionMap.put("java.sql.Statement", false);
}
private DataSource dataSource;
private String sniffPackage;
public HikariInstrumentationAgent(DataSource dataSource)
{
this.dataSource = dataSource;
this.sniffPackage = getDataSourcePackage(dataSource);
instrumentableClasses = new HashMap<>();
for (String intf : completionMap.keySet())
{
instrumentableClasses.put(intf, new HashSet<String>());
}
}
public boolean loadTransformerAgent()
{
String jarPath = getSelfJarPath();
if (jarPath == null)
{
LOGGER.warn("Cannot find the HikariCP jar file through introspection.");
return false;
}
try
{
registerInstrumentation(jarPath);
LOGGER.info("Successfully loaded instrumentation agent. Scanning classes...");
}
catch (Exception e)
{
LOGGER.warn("Instrumentation agent could not be loaded. Please report at http://github.com/brettwooldridge/HikariCP.", e);
return false;
}
try
{
boolean success = searchInstrumentable(dataSource);
completionMap.entrySet();
if (!success)
{
LOGGER.warn("Unable to find and instrument necessary classes. Please report at http://github.com/brettwooldridge/HikariCP.");
LOGGER.info("Using delegation instead of instrumentation");
}
else
{
LOGGER.info("Successfully instrumented required JDBC classes.");
}
return success;
}
catch (Exception e)
{
return false;
}
// finally
// {
// unregisterInstrumenation();
// }
}
private static Instrumentation ourInstrumentation;
/**
* Search the jar (or directory hierarchy) that contains the DataSource and force the class
* loading of the classes we are about instrumenting. See loadIfInstrumentable() for more
* detail.
* The method that is called when VirtualMachine.loadAgent() is invoked to register our
* class transformer.
*
* @param dataSource
* @throws IOException
*/
private boolean searchInstrumentable(DataSource dataSource) throws Exception
{
String searchPath = getSearchPath(dataSource);
if (searchPath == null)
{
return false;
}
long start = System.currentTimeMillis();
try
{
if (searchPath.endsWith(".jar"))
{
return searchInstrumentableJar(searchPath);
}
else
{
String dsSubPath = dataSource.getClass().getPackage().getName().replace('.', '/');
String classRoot = searchPath.replace(dsSubPath, "");
// Drop one segment off of the path for a slightly broader search
searchPath = searchPath.substring(0, searchPath.lastIndexOf('/'));
return seachInstrumentableDirectory(classRoot, searchPath);
}
}
finally
{
LOGGER.info("Instrumentation completed in {}ms.", System.currentTimeMillis() - start);
}
}
private boolean searchInstrumentableJar(String searchPath) throws IOException, ClassNotFoundException
{
File jarPath = new File(URI.create(searchPath));
if (!jarPath.isFile())
{
return false;
}
JarFile jarFile = new JarFile(jarPath, false, JarFile.OPEN_READ);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements())
{
JarEntry jarEntry = entries.nextElement();
if (jarEntry.isDirectory())
{
continue;
}
String entryName = jarEntry.getName();
if (entryName.endsWith(".class") && entryName.startsWith(sniffPackage) && entryName.indexOf('$') == -1)
{
String className = entryName.replace(".class", "").replace('/', '.');
InputStream inputStream = jarFile.getInputStream(jarEntry);
DataInputStream dis = new DataInputStream(inputStream);
ClassFile classFile = new ClassFile(dis);
if (classFile.isInterface())
{
inputStream.close();
continue;
}
String intfKey = null;
HashSet<String> interfaces = new HashSet<>(Arrays.asList(classFile.getInterfaces()));
for (String intf : interfaces)
{
if (completionMap.containsKey(intf))
{
intfKey = intf;
break;
}
}
if (intfKey != null)
{
instrumentableClasses.get(intfKey).add(className);
String superclass = classFile.getSuperclass();
if (superclass != null)
{
instrumentableClasses.get(intfKey).add(superclass);
}
}
// loadIfInstrumentable(className, );
inputStream.close();
}
}
jarFile.close();
return true;
}
/**
* @param classRoot
* @param searchPath
* @return true if the search completed without error
* @throws IOException
* @throws ClassNotFoundException
* @param agentArgs arguments to pass to the agent
* @param inst the virtual machine Instrumentation instance used to register our transformer
*/
private boolean seachInstrumentableDirectory(String classRoot, String searchPath) throws IOException, ClassNotFoundException
public static void agentmain(String agentArgs, Instrumentation instrumentation)
{
File directory = new File(searchPath);
if (!directory.isDirectory())
{
return false;
}
ourInstrumentation = instrumentation;
for (File fileEntry : directory.listFiles())
{
if (fileEntry.isDirectory())
{
seachInstrumentableDirectory(classRoot, fileEntry.getPath());
continue;
}
String fileName = fileEntry.getPath();
String className = fileName.replace(classRoot, "");
if (className.endsWith(".class") && className.startsWith(sniffPackage) && className.indexOf('$') == -1)
{
className = className.replace(".class", "").replace('/', '.');
InputStream inputStream = new FileInputStream(fileEntry);
loadIfInstrumentable(className, new DataInputStream(inputStream));
inputStream.close();
}
}
return true;
}
/**
* If the specified class implements one of the java.sql interfaces we are interested in
* instrumenting, use the class loader to cause the class to be loaded. This will force
* the class in question through the HikariClassTransformer.
*
* @param className the name of the class that might be instrumentable
* @param classInputStream the stream of bytes for the class file
* @throws IOException thrown if there is an error reading the class file
* @throws ClassNotFoundException thrown if the referenced class is not loadable
*/
private void loadIfInstrumentable(String className, DataInputStream classInputStream) throws IOException, ClassNotFoundException
{
ClassFile classFile = new ClassFile(classInputStream);
if (classFile.isInterface())
{
return;
}
Properties systemProperties = System.getProperties();
ClassFileTransformer transformer = (ClassFileTransformer) systemProperties.get("com.zaxxer.hikari.transformer");
for (String iface : classFile.getInterfaces())
{
if (!iface.startsWith("java.sql"))
{
continue;
}
if (completionMap.containsKey(iface))
{
LOGGER.info("Instrumenting class {}", className);
ClassLoaderUtils.loadClass(className);
completionMap.put(iface, true);
}
}
}
/**
* Get the path to the JAR or file system directory where the class of the user
* specified DataSource implementation resides.
*
* @param dataSource the user specified DataSource
* @return the path to the JAR (including the .jar file name) or a file system classes directory
*/
private String getSearchPath(DataSource dataSource)
{
URL resource = dataSource.getClass().getResource('/' + dataSource.getClass().getName().replace('.', '/') + ".class");
if (resource == null)
{
return null;
}
String path = resource.toString();
if (path.startsWith("jar:"))
{
// original form jar:file:/path, make a path like file:///path
path = path.substring(4, path.indexOf('!')).replace(":/", ":///");
}
else if (path.startsWith("file:"))
{
path = path.substring(0, path.lastIndexOf('/')).replace("file:", "");
}
else
{
LOGGER.warn("Could not determine path type of {}", path);
return null;
}
return path;
}
/**
* Given a DataSource class, find the package name that is one-level above the package of
* the DataSource. For example, org.hsqldb.jdbc.DataSource -> org.hsqldb. This is used
* to filter out packages quickly that we are not interested in instrumenting.
*
* @param dataSource a DataSource
* @return the shortened package name used for filtering
*/
private String getDataSourcePackage(DataSource dataSource)
{
String packageName = dataSource.getClass().getPackage().getName();
// Count how many segments in the package name. For example, org.hsqldb.jdbc has three segments.
int dots = 0;
int[] offset = new int[16];
for (int ndx = packageName.indexOf('.'); ndx != -1; ndx = packageName.indexOf('.', ndx + 1))
{
offset[dots] = ndx;
dots++;
}
if (dots > 1)
{
packageName = packageName.substring(0, offset[dots - 1]);
}
return packageName.replace('.', '/');
}
/**
* Get the path to the JAR file from which this class was loaded.
*
* @return the path to the jar file that contains this class
*/
private String getSelfJarPath()
{
URL resource = HikariClassTransformer.class.getResource('/' + HikariClassTransformer.class.getName().replace('.', '/') + ".class");
if (resource == null)
{
return null;
}
String jarPath = resource.toString();
jarPath = jarPath.replace("file:", "");
jarPath = jarPath.replace("jar:", "");
if (jarPath.indexOf('!') > 0)
{
jarPath = jarPath.substring(0, jarPath.indexOf('!'));
}
else
{
return System.getProperty("com.zaxxer.hikari.selfJar");
}
return jarPath;
}
/**
* Attempt to register our instrumentation (class transformer) with the virtual machine
* dynamically.
*
* @param jarPath the path to our own jar file
* @throws AttachNotSupportedException thrown if the JVM does not support attachment
* @throws IOException thrown if the instrumentation JAR cannot be read
* @throws AgentLoadException thrown if the instrumentation jar does not have proper headers
* @throws AgentInitializationException thrown if the agent had an error during initialization
*/
private void registerInstrumentation(String jarPath) throws AttachNotSupportedException, IOException, AgentLoadException, AgentInitializationException
{
VirtualMachine vm = VirtualMachine.attach(getPid());
vm.loadAgent(jarPath, sniffPackage);
vm.detach();
}
/**
* Unregister the instrumentation (class transformer).
*/
private void unregisterInstrumenation()
{
HikariClassTransformer.unregisterInstrumenation();
}
/**
* Get the PID of the running JVM.
*
* @return the process ID (PID) of the running JVM
*/
private String getPid()
{
String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName();
int p = nameOfRunningVM.indexOf('@');
return nameOfRunningVM.substring(0, p);
ourInstrumentation.addTransformer(transformer, false);
}
}

@ -0,0 +1,24 @@
/*
* Copyright (C) 2013 Brett Wooldridge
*
* Licensed 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.zaxxer.hikari.javassist;
/**
* @author Brett Wooldridge
*/
public @interface HikariOverride {
// marker interface
}

@ -23,6 +23,7 @@ import java.sql.SQLException;
import java.sql.Statement;
import com.zaxxer.hikari.javassist.HikariInject;
import com.zaxxer.hikari.javassist.HikariOverride;
/**
*
@ -32,7 +33,7 @@ public class CallableStatementProxy implements IHikariStatementProxy
{
private static ProxyFactory PROXY_FACTORY;
@HikariInject private IHikariConnectionProxy _connection;
@HikariInject protected IHikariConnectionProxy _connection;
protected Statement delegate;
@ -48,15 +49,15 @@ public class CallableStatementProxy implements IHikariStatementProxy
}
@HikariInject
public void setConnectionProxy(IHikariConnectionProxy connection)
public void _setConnectionProxy(IHikariConnectionProxy connection)
{
this._connection = connection;
}
@HikariInject
public SQLException checkException(SQLException e)
public SQLException _checkException(SQLException e)
{
return _connection.checkException(e);
return _connection._checkException(e);
}
@ -64,41 +65,39 @@ public class CallableStatementProxy implements IHikariStatementProxy
// Overridden java.sql.CallableStatement Methods
// **********************************************************************
@HikariInject
public ResultSet executeQuery() throws SQLException
@HikariOverride
public void close() throws SQLException
{
_connection._unregisterStatement(this);
try
{
IHikariResultSetProxy resultSet = (IHikariResultSetProxy) __executeQuery();
if (resultSet == null)
{
return null;
}
resultSet.setProxyStatement(this);
return (ResultSet) resultSet;
__close();
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
public void close() throws SQLException
public ResultSet executeQuery() throws SQLException
{
_connection.unregisterStatement(this);
try
{
__close();
IHikariResultSetProxy resultSet = (IHikariResultSetProxy) __executeQuery();
if (resultSet == null)
{
return null;
}
resultSet._setProxyStatement(this);
return (ResultSet) resultSet;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
public ResultSet executeQuery(String sql) throws SQLException
{
try
@ -109,16 +108,15 @@ public class CallableStatementProxy implements IHikariStatementProxy
return null;
}
resultSet.setProxyStatement(this);
resultSet._setProxyStatement(this);
return (ResultSet) resultSet;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
public ResultSet getGeneratedKeys() throws SQLException
{
try
@ -129,12 +127,12 @@ public class CallableStatementProxy implements IHikariStatementProxy
return null;
}
resultSet.setProxyStatement(this);
resultSet._setProxyStatement(this);
return (ResultSet) resultSet;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}

@ -29,6 +29,7 @@ import java.util.TimerTask;
import com.zaxxer.hikari.HikariPool;
import com.zaxxer.hikari.javassist.HikariInject;
import com.zaxxer.hikari.javassist.HikariOverride;
/**
* This is the proxy class for java.sql.Connection. It is used in
@ -50,20 +51,21 @@ public class ConnectionProxy implements IHikariConnectionProxy
{
private static ProxyFactory PROXY_FACTORY;
@HikariInject private static final Set<String> SQL_ERRORS;
@HikariInject protected static final Set<String> SQL_ERRORS;
@HikariInject private ArrayList<Statement> _openStatements;
@HikariInject private volatile boolean _isClosed;
@HikariInject private HikariPool _parentPool;
@HikariInject protected volatile boolean _isClosed;
@HikariInject protected ArrayList<Statement> _openStatements;
@HikariInject protected HikariPool _parentPool;
protected final Connection delegate;
@HikariInject private volatile boolean _forceClose;
@HikariInject private long _creationTime;
@HikariInject private long _lastAccess;
@HikariInject protected volatile boolean _forceClose;
@HikariInject protected long _creationTime;
@HikariInject protected long _lastAccess;
@HikariInject private StackTraceElement[] _stackTrace;
@HikariInject private TimerTask _leakTask;
@HikariInject protected StackTraceElement[] _stackTrace;
@HikariInject protected TimerTask _leakTask;
// static initializer
static
@ -86,7 +88,7 @@ public class ConnectionProxy implements IHikariConnectionProxy
}
@HikariInject
public void unregisterStatement(Object statement)
public void _unregisterStatement(Object statement)
{
// If the connection is not closed. If it is closed, it means this is being
// called back as a result of the close() method below in which case we
@ -98,37 +100,37 @@ public class ConnectionProxy implements IHikariConnectionProxy
}
@HikariInject
public long getCreationTime()
public long _getCreationTime()
{
return _creationTime;
}
@HikariInject
public long getLastAccess()
public long _getLastAccess()
{
return _lastAccess;
}
@HikariInject
public void markLastAccess()
public void _markLastAccess()
{
this._lastAccess = System.currentTimeMillis();
}
@HikariInject
public void setParentPool(HikariPool parentPool)
public void _setParentPool(HikariPool parentPool)
{
this._parentPool = parentPool;
}
@HikariInject
public void unclose()
public void _unclose()
{
_isClosed = false;
}
@HikariInject
public void captureStack(long leakDetectionThreshold, Timer scheduler)
public void _captureStack(long leakDetectionThreshold, Timer scheduler)
{
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
_stackTrace = new StackTraceElement[trace.length - 4];
@ -139,13 +141,13 @@ public class ConnectionProxy implements IHikariConnectionProxy
}
@HikariInject
public boolean isBrokenConnection()
public boolean _isBrokenConnection()
{
return _forceClose;
}
@HikariInject
public SQLException checkException(SQLException sqle)
public SQLException _checkException(SQLException sqle)
{
String sqlState = sqle.getSQLState();
_forceClose |= (sqlState != null && (sqlState.startsWith("08") || SQL_ERRORS.contains(sqlState)));
@ -154,14 +156,14 @@ public class ConnectionProxy implements IHikariConnectionProxy
}
@HikariInject
private void __init()
protected void __init()
{
_openStatements = new ArrayList<Statement>(64);
_creationTime = _lastAccess = System.currentTimeMillis();
}
@HikariInject
protected void checkClosed() throws SQLException
protected void _checkClosed() throws SQLException
{
if (_isClosed)
{
@ -178,7 +180,7 @@ public class ConnectionProxy implements IHikariConnectionProxy
// "Overridden" java.sql.Connection Methods
// **********************************************************************
@HikariInject
@HikariOverride
public void close() throws SQLException
{
if (!_isClosed)
@ -200,7 +202,7 @@ public class ConnectionProxy implements IHikariConnectionProxy
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
finally
{
@ -210,225 +212,225 @@ public class ConnectionProxy implements IHikariConnectionProxy
}
}
@HikariInject
@HikariOverride
public boolean isClosed() throws SQLException
{
return _isClosed;
}
@HikariInject
@HikariOverride
public Statement createStatement() throws SQLException
{
checkClosed();
_checkClosed();
try
{
Statement statementProxy = __createStatement();
((IHikariStatementProxy) statementProxy).setConnectionProxy(this);
((IHikariStatementProxy) statementProxy)._setConnectionProxy(this);
_openStatements.add(statementProxy);
return statementProxy;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
@HikariOverride
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException
{
checkClosed();
_checkClosed();
try
{
Statement statementProxy = __createStatement(resultSetType, resultSetConcurrency);
((IHikariStatementProxy) statementProxy).setConnectionProxy(this);
((IHikariStatementProxy) statementProxy)._setConnectionProxy(this);
_openStatements.add(statementProxy);
return statementProxy;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
@HikariOverride
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException
{
checkClosed();
_checkClosed();
try
{
Statement statementProxy = __createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
((IHikariStatementProxy) statementProxy).setConnectionProxy(this);
((IHikariStatementProxy) statementProxy)._setConnectionProxy(this);
_openStatements.add(statementProxy);
return statementProxy;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
@HikariOverride
public CallableStatement prepareCall(String sql) throws SQLException
{
checkClosed();
_checkClosed();
try
{
CallableStatement statementProxy = __prepareCall(sql);
((IHikariStatementProxy) statementProxy).setConnectionProxy(this);
((IHikariStatementProxy) statementProxy)._setConnectionProxy(this);
_openStatements.add(statementProxy);
return statementProxy;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
@HikariOverride
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException
{
checkClosed();
_checkClosed();
try
{
CallableStatement statementProxy = __prepareCall(sql, resultSetType, resultSetConcurrency);
((IHikariStatementProxy) statementProxy).setConnectionProxy(this);
((IHikariStatementProxy) statementProxy)._setConnectionProxy(this);
_openStatements.add(statementProxy);
return statementProxy;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
@HikariOverride
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException
{
checkClosed();
_checkClosed();
try
{
CallableStatement statementProxy = __prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
((IHikariStatementProxy) statementProxy).setConnectionProxy(this);
((IHikariStatementProxy) statementProxy)._setConnectionProxy(this);
_openStatements.add(statementProxy);
return statementProxy;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
@HikariOverride
public PreparedStatement prepareStatement(String sql) throws SQLException
{
checkClosed();
_checkClosed();
try
{
PreparedStatement statementProxy = __prepareStatement(sql);
((IHikariStatementProxy) statementProxy).setConnectionProxy(this);
((IHikariStatementProxy) statementProxy)._setConnectionProxy(this);
_openStatements.add(statementProxy);
return statementProxy;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
@HikariOverride
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException
{
checkClosed();
_checkClosed();
try
{
PreparedStatement statementProxy = __prepareStatement(sql, autoGeneratedKeys);
((IHikariStatementProxy) statementProxy).setConnectionProxy(this);
((IHikariStatementProxy) statementProxy)._setConnectionProxy(this);
_openStatements.add(statementProxy);
return statementProxy;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
@HikariOverride
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException
{
checkClosed();
_checkClosed();
try
{
PreparedStatement statementProxy = __prepareStatement(sql, resultSetType, resultSetConcurrency);
((IHikariStatementProxy) statementProxy).setConnectionProxy(this);
((IHikariStatementProxy) statementProxy)._setConnectionProxy(this);
_openStatements.add(statementProxy);
return statementProxy;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
@HikariOverride
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException
{
checkClosed();
_checkClosed();
try
{
PreparedStatement statementProxy = __prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
((IHikariStatementProxy) statementProxy).setConnectionProxy(this);
((IHikariStatementProxy) statementProxy)._setConnectionProxy(this);
_openStatements.add(statementProxy);
return statementProxy;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
@HikariOverride
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException
{
checkClosed();
_checkClosed();
try
{
PreparedStatement statementProxy = __prepareStatement(sql, columnIndexes);
((IHikariStatementProxy) statementProxy).setConnectionProxy(this);
((IHikariStatementProxy) statementProxy)._setConnectionProxy(this);
_openStatements.add(statementProxy);
return statementProxy;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
@HikariOverride
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException
{
checkClosed();
_checkClosed();
try
{
PreparedStatement statementProxy = __prepareStatement(sql, columnNames);
((IHikariStatementProxy) statementProxy).setConnectionProxy(this);
((IHikariStatementProxy) statementProxy)._setConnectionProxy(this);
_openStatements.add(statementProxy);
return statementProxy;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@ -445,7 +447,7 @@ public class ConnectionProxy implements IHikariConnectionProxy
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}

@ -28,26 +28,26 @@ import com.zaxxer.hikari.HikariPool;
*/
public interface IHikariConnectionProxy
{
void unclose();
void _unclose();
void __close() throws SQLException;
void unregisterStatement(Object statement);
void _unregisterStatement(Object statement);
SQLException checkException(SQLException sqle);
SQLException _checkException(SQLException sqle);
boolean isBrokenConnection();
boolean _isBrokenConnection();
long getCreationTime();
long _getCreationTime();
long getLastAccess();
long _getLastAccess();
void markLastAccess();
void _markLastAccess();
void setParentPool(HikariPool parentPool);
void _setParentPool(HikariPool parentPool);
Connection getDelegate();
/* Leak Detection API */
void captureStack(long leakThreshold, Timer houseKeepingTimer);
void _captureStack(long leakThreshold, Timer houseKeepingTimer);
}

@ -2,5 +2,5 @@ package com.zaxxer.hikari.proxy;
public interface IHikariResultSetProxy
{
void setProxyStatement(IHikariStatementProxy proxy);
void _setProxyStatement(IHikariStatementProxy proxy);
}

@ -6,7 +6,7 @@ public interface IHikariStatementProxy
{
void close() throws SQLException;
void setConnectionProxy(IHikariConnectionProxy connectionProxy);
void _setConnectionProxy(IHikariConnectionProxy connectionProxy);
SQLException checkException(SQLException e);
SQLException _checkException(SQLException e);
}

@ -65,9 +65,9 @@ public final class JavassistProxyFactoryFactory
try
{
String methodBody = "{ checkClosed(); try { return ((cast) delegate).method($$); } catch (SQLException e) { throw checkException(e); } }";
String methodBody = "{ _checkClosed(); try { return ((cast) delegate).method($$); } catch (SQLException e) { throw _checkException(e); } }";
generateProxyClass(Connection.class, ConnectionProxy.class, methodBody);
methodBody = "{ try { return ((cast) delegate).method($$); } catch (SQLException e) { throw checkException(e); } }";
methodBody = "{ try { return ((cast) delegate).method($$); } catch (SQLException e) { throw _checkException(e); } }";
generateProxyClass(Statement.class, StatementProxy.class, methodBody);
generateProxyClass(CallableStatement.class, CallableStatementProxy.class, methodBody);
generateProxyClass(PreparedStatement.class, PreparedStatementProxy.class, methodBody);

@ -22,6 +22,7 @@ import java.sql.SQLException;
import java.sql.Statement;
import com.zaxxer.hikari.javassist.HikariInject;
import com.zaxxer.hikari.javassist.HikariOverride;
/**
*
@ -31,7 +32,7 @@ public class PreparedStatementProxy implements IHikariStatementProxy
{
private static ProxyFactory PROXY_FACTORY;
@HikariInject private IHikariConnectionProxy _connection;
@HikariInject protected IHikariConnectionProxy _connection;
protected Statement delegate;
@ -47,22 +48,35 @@ public class PreparedStatementProxy implements IHikariStatementProxy
}
@HikariInject
public void setConnectionProxy(IHikariConnectionProxy connection)
public void _setConnectionProxy(IHikariConnectionProxy connection)
{
this._connection = connection;
}
@HikariInject
public SQLException checkException(SQLException e)
public SQLException _checkException(SQLException e)
{
return _connection.checkException(e);
return _connection._checkException(e);
}
// **********************************************************************
// Overridden java.sql.PreparedStatement Methods
// **********************************************************************
@HikariOverride
public void close() throws SQLException
{
_connection._unregisterStatement(this);
try
{
__close();
}
catch (SQLException e)
{
throw _checkException(e);
}
}
@HikariInject
public ResultSet executeQuery() throws SQLException
{
try
@ -73,30 +87,15 @@ public class PreparedStatementProxy implements IHikariStatementProxy
return null;
}
resultSet.setProxyStatement(this);
resultSet._setProxyStatement(this);
return (ResultSet) resultSet;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
public void close() throws SQLException
{
_connection.unregisterStatement(this);
try
{
__close();
}
catch (SQLException e)
{
throw checkException(e);
}
}
@HikariInject
public ResultSet executeQuery(String sql) throws SQLException
{
try
@ -107,16 +106,15 @@ public class PreparedStatementProxy implements IHikariStatementProxy
return null;
}
resultSet.setProxyStatement(this);
resultSet._setProxyStatement(this);
return (ResultSet) resultSet;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
public ResultSet getGeneratedKeys() throws SQLException
{
try
@ -127,12 +125,12 @@ public class PreparedStatementProxy implements IHikariStatementProxy
return null;
}
resultSet.setProxyStatement(this);
resultSet._setProxyStatement(this);
return (ResultSet) resultSet;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}

@ -27,7 +27,7 @@ import com.zaxxer.hikari.javassist.HikariInject;
*/
public class ResultSetProxy implements IHikariResultSetProxy
{
@HikariInject private IHikariStatementProxy _statement;
private IHikariStatementProxy _statement;
protected final ResultSet delegate;
@ -38,13 +38,12 @@ public class ResultSetProxy implements IHikariResultSetProxy
}
@HikariInject
public SQLException checkException(SQLException e)
public SQLException _checkException(SQLException e)
{
return _statement.checkException(e);
return _statement._checkException(e);
}
@HikariInject
public void setProxyStatement(IHikariStatementProxy statement)
public void _setProxyStatement(IHikariStatementProxy statement)
{
this._statement = statement;
}

@ -21,6 +21,7 @@ import java.sql.SQLException;
import java.sql.Statement;
import com.zaxxer.hikari.javassist.HikariInject;
import com.zaxxer.hikari.javassist.HikariOverride;
/**
* @author Brett Wooldridge
@ -29,7 +30,7 @@ public class StatementProxy implements IHikariStatementProxy
{
private static ProxyFactory PROXY_FACTORY;
@HikariInject private IHikariConnectionProxy _connection;
@HikariInject protected IHikariConnectionProxy _connection;
protected Statement delegate;
@ -45,36 +46,35 @@ public class StatementProxy implements IHikariStatementProxy
}
@HikariInject
public void setConnectionProxy(IHikariConnectionProxy connection)
public void _setConnectionProxy(IHikariConnectionProxy connection)
{
this._connection = connection;
}
@HikariInject
public SQLException checkException(SQLException e)
public SQLException _checkException(SQLException e)
{
return _connection.checkException(e);
return _connection._checkException(e);
}
// **********************************************************************
// Overridden java.sql.Statement Methods
// **********************************************************************
@HikariInject
@HikariOverride
public void close() throws SQLException
{
_connection.unregisterStatement(this);
_connection._unregisterStatement(this);
try
{
__close();
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
public ResultSet executeQuery(String sql) throws SQLException
{
try
@ -85,16 +85,15 @@ public class StatementProxy implements IHikariStatementProxy
return null;
}
resultSet.setProxyStatement(this);
resultSet._setProxyStatement(this);
return (ResultSet) resultSet;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}
@HikariInject
public ResultSet getGeneratedKeys() throws SQLException
{
try
@ -105,12 +104,12 @@ public class StatementProxy implements IHikariStatementProxy
return null;
}
resultSet.setProxyStatement(this);
resultSet._setProxyStatement(this);
return (ResultSet) resultSet;
}
catch (SQLException e)
{
throw checkException(e);
throw _checkException(e);
}
}

@ -0,0 +1,29 @@
# This is a manually maintained codex of JDBC drivers and the
# classes within that need to be instrumented.
#
org.postgresql.ds.PGSimpleDataSource=postgresql
postgresql.baseConnection=
# MySQL Connector/J v5.1.26
#
com.mysql.jdbc.jdbc2.optional.MysqlDataSource=mysql
mysql.baseConnection=com.mysql.jdbc.ConnectionImpl
mysql.baseConnection=com.mysql.jdbc.ReplicationConnection
mysql.baseConnection=com.mysql.jdbc.LoadBalancedMySQLConnection
mysql.subConnection=com.mysql.jdbc.JDBC4Connection
mysql.subConnection=com.mysql.jdbc.JDBC4LoadBalancedMySQLConnection
mysql.baseStatement=com.mysql.jdbc.StatementImpl
mysql.subPreparedStatement=com.mysql.jdbc.PreparedStatement
mysql.subPreparedStatement=com.mysql.jdbc.ServerPreparedStatement
mysql.subPreparedStatement=com.mysql.jdbc.JDBC4ServerPreparedStatement
mysql.subCallableStatement=com.mysql.jdbc.CallableStatement
mysql.subCallableStatement=com.mysql.jdbc.JDBC4CallableStatement
mysql.baseResultSet=com.mysql.jdbc.ResultSetImpl
mysql.subResultSet=com.mysql.jdbc.JDBC4ResultSet
mysql.subResultSet=com.mysql.jdbc.UpdatableResultSet
mysql.subResultSet=com.mysql.jdbc.JDBC4UpdatableResultSet

@ -34,7 +34,7 @@ import org.junit.Test;
*/
public class CreationTest
{
@Test
//@Test
public void testCreate() throws SQLException
{
HikariConfig config = new HikariConfig();
@ -43,7 +43,18 @@ public class CreationTest
config.setAcquireIncrement(1);
config.setConnectionTestQuery("VALUES 1");
// config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
config.setDataSourceClassName("org.postgresql.ds.PGSimpleDataSource");
// config.setDataSourceClassName("org.postgresql.ds.PGSimpleDataSource");
// config.addDataSourceProperty("username", "brettw");
// config.addDataSourceProperty("password", "");
// config.addDataSourceProperty("databaseName", "netld");
// config.addDataSourceProperty("serverName", "localhost");
config.setDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlDataSource");
config.addDataSourceProperty("user", "root");
config.addDataSourceProperty("password", "");
config.addDataSourceProperty("databaseName", "netld");
config.addDataSourceProperty("serverName", "localhost");
HikariDataSource ds = new HikariDataSource(config);
@ -56,16 +67,15 @@ public class CreationTest
Assert.assertSame("Totals connections not as expected", 1, ds.pool.getTotalConnections());
Assert.assertSame("Idle connections not as expected", 0, ds.pool.getIdleConnections());
PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?");
PreparedStatement statement = connection.prepareStatement("SELECT * FROM device WHERE device_id=?");
Assert.assertNotNull(statement);
statement.setString(1, "thing");
statement.setInt(1, 0);
ResultSet resultSet = statement.executeQuery();
Assert.assertNotNull(resultSet);
String aString = resultSet.getString(1);
Assert.assertSame("aString", aString);
Assert.assertFalse(resultSet.next());
resultSet.close();
statement.close();
@ -75,7 +85,7 @@ public class CreationTest
Assert.assertSame("Idle connections not as expected", 1, ds.pool.getIdleConnections());
}
@Test
//@Test
public void testMaxLifetime() throws Exception
{
HikariConfig config = new HikariConfig();
@ -115,7 +125,7 @@ public class CreationTest
Assert.assertSame("Idle connections not as expected", 1, ds.pool.getIdleConnections());
}
@Test
//@Test
public void testDoubleClose() throws Exception
{
HikariConfig config = new HikariConfig();

Loading…
Cancel
Save