diff --git a/pom.xml b/pom.xml index b54cb767..ae817a79 100644 --- a/pom.xml +++ b/pom.xml @@ -127,7 +127,7 @@ com.zaxxer.hikari com.sun.tools.attach,javassist.*,javax.management,javax.sql,javax.sql.rowset,,javax.sql.rowset.serial,,javax.sql.rowset.spi,org.slf4j ${project.groupId}.${project.artifactId} - com.zaxxer.hikari.proxy.HikariClassTransformer + com.zaxxer.hikari.javassist.HikariClassTransformer diff --git a/src/main/java/com/zaxxer/hikari/javassist/HikariClassTransformer.java b/src/main/java/com/zaxxer/hikari/javassist/HikariClassTransformer.java new file mode 100644 index 00000000..75fd7506 --- /dev/null +++ b/src/main/java/com/zaxxer/hikari/javassist/HikariClassTransformer.java @@ -0,0 +1,345 @@ +/* + * 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.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; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtField; +import javassist.CtMethod; +import javassist.CtNewConstructor; +import javassist.CtNewMethod; +import javassist.bytecode.ClassFile; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author Brett Wooldridge + */ +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; + + private String sniffPackage; + + private volatile boolean agentFailed; + + /** + * Private constructor. + * + * @param sniffPackage the package name used to filter only classes we are interested in + */ + private HikariClassTransformer(String sniffPackage) + { + 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) + { + ourInstrumentation = instrumentation; + + ClassPool defaultPool = ClassPool.getDefault(); + classPool = new ClassPool(defaultPool); + classPool.importPackage("java.sql"); + classPool.childFirstLookup = true; + + ourInstrumentation.addTransformer(new HikariClassTransformer(agentArgs), false); + } + + /** {@inheritDoc} */ + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) + throws IllegalClassFormatException + { + if (!className.startsWith(sniffPackage)) + { + return classfileBuffer; + } + + try + { + ClassFile classFile = new ClassFile(new DataInputStream(new ByteArrayInputStream(classfileBuffer))); + for (String iface : classFile.getInterfaces()) + { + if (!iface.startsWith("java.sql")) + { + continue; + } + + if (iface.equals("java.sql.Connection")) + { + return transformConnection(classFile); + } + else if (iface.equals("java.sql.PreparedStatement")) + { + return transformPreparedStatement(classFile); + } + else if (iface.equals("java.sql.CallableStatement")) + { + return transformCallableStatement(classFile); + } + else if (iface.equals("java.sql.Statement")) + { + return transformStatement(classFile); + } + else if (iface.equals("java.sql.ResultSet")) + { + return transformResultSet(classFile); + } + } + + // None of the interfaces we care about were found, so just return the class file buffer + return classfileBuffer; + } + catch (Exception e) + { + agentFailed = true; + LOGGER.error("Error transforming class {}", className, e); + return classfileBuffer; + } + } + + public boolean isAgentFailed() + { + return agentFailed; + } + + /** + * @param classFile + */ + private byte[] transformConnection(ClassFile classFile) throws Exception + { + String className = classFile.getName(); + CtClass target = classPool.getCtClass(className); + + CtClass intf = classPool.get("com.zaxxer.hikari.proxy.IHikariConnectionProxy"); + target.addInterface(intf); + LOGGER.debug("Added interface {} to {}", intf.getName(), className); + + CtClass proxy = classPool.get("com.zaxxer.hikari.proxy.ConnectionProxy"); + + copyFields(proxy, target); + copyMethods(proxy, target, classFile); + mergeClassInitializers(proxy, target, classFile); + + for (CtConstructor constructor : target.getConstructors()) + { + constructor.insertAfter("__init();"); + } + + target.debugWriteFile("/tmp"); + return target.toBytecode(); + } + + /** + * @param classFile + */ + private byte[] transformPreparedStatement(ClassFile classFile) throws Exception + { + String className = classFile.getName(); + CtClass target = classPool.getCtClass(className); + + CtClass intf = classPool.get("com.zaxxer.hikari.proxy.IHikariStatementProxy"); + target.addInterface(intf); + LOGGER.debug("Added interface {} to {}", intf.getName(), className); + + CtClass proxy = classPool.get("com.zaxxer.hikari.proxy.PreparedStatementProxy"); + + copyFields(proxy, target); + copyMethods(proxy, target, classFile); + mergeClassInitializers(proxy, target, classFile); + + target.debugWriteFile("/tmp"); + return target.toBytecode(); + } + + /** + * @param classFile + */ + private byte[] transformCallableStatement(ClassFile classFile) throws Exception + { + String className = classFile.getName(); + CtClass target = classPool.getCtClass(className); + + CtClass intf = classPool.get("com.zaxxer.hikari.proxy.IHikariStatementProxy"); + target.addInterface(intf); + LOGGER.debug("Added interface {} to {}", intf.getName(), className); + + CtClass proxy = classPool.get("com.zaxxer.hikari.proxy.CallableStatementProxy"); + + copyFields(proxy, target); + copyMethods(proxy, target, classFile); + mergeClassInitializers(proxy, target, classFile); + + target.debugWriteFile("/tmp"); + return target.toBytecode(); + } + + /** + * @param classFile + */ + private byte[] transformStatement(ClassFile classFile) throws Exception + { + String className = classFile.getName(); + CtClass target = classPool.getCtClass(className); + + CtClass intf = classPool.get("com.zaxxer.hikari.proxy.IHikariStatementProxy"); + target.addInterface(intf); + LOGGER.debug("Added interface {} to {}", intf.getName(), className); + + CtClass proxy = classPool.get("com.zaxxer.hikari.proxy.StatementProxy"); + + copyFields(proxy, target); + copyMethods(proxy, target, classFile); + mergeClassInitializers(proxy, target, classFile); + + target.debugWriteFile("/tmp"); + return target.toBytecode(); + } + + /** + * @param classFile + */ + private byte[] transformResultSet(ClassFile classFile) throws Exception + { + String className = classFile.getName(); + CtClass target = classPool.getCtClass(className); + + CtClass intf = classPool.get("com.zaxxer.hikari.proxy.IHikariResultSetProxy"); + target.addInterface(intf); + LOGGER.debug("Added interface {} to {}", intf.getName(), className); + + CtClass proxy = classPool.get("com.zaxxer.hikari.proxy.ResultSetProxy"); + + copyFields(proxy, target); + copyMethods(proxy, target, classFile); + mergeClassInitializers(proxy, target, classFile); + + target.debugWriteFile("/tmp"); + return target.toBytecode(); + } + + private void copyFields(CtClass srcClass, CtClass destClass) throws Exception + { + HashSet srcFields = new HashSet(); + srcFields.addAll(Arrays.asList(srcClass.getDeclaredFields())); + srcFields.addAll(Arrays.asList(srcClass.getFields())); + for (CtField field : srcFields) + { + if (field.getAnnotation(HikariInject.class) == null) + { + LOGGER.debug("Skipped field {}", field.getName()); + continue; + } + + CtField copy = new CtField(field.getType(), field.getName(), destClass); + copy.setModifiers(field.getModifiers()); + destClass.addField(copy); + LOGGER.debug("Copied field {}.{} to {}", srcClass.getSimpleName(), field.getName(), destClass.getSimpleName()); + } + } + + private void copyMethods(CtClass srcClass, CtClass destClass, ClassFile destClassFile) throws Exception + { + CtMethod[] destMethods = destClass.getMethods(); + + HashSet srcMethods = new HashSet(); + srcMethods.addAll(Arrays.asList(srcClass.getMethods())); + srcMethods.addAll(Arrays.asList(srcClass.getDeclaredMethods())); + for (CtMethod method : srcMethods) + { + if (method.getAnnotation(HikariInject.class) == null) + { + LOGGER.debug("Skipped method {}", method.getName()); + continue; + } + + if (destClassFile.getMethod(method.getName()) != null) // maybe we have a name collision + { + String signature = method.getSignature(); + for (CtMethod destMethod : destMethods) + { + if (destMethod.getName().equals(method.getName()) && destMethod.getSignature().equals(signature)) + { + LOGGER.debug("Rename method {}.{} to __{}", destClass.getSimpleName(), destMethod.getName(), destMethod.getName()); + destMethod.setName("__" + destMethod.getName()); + break; + } + } + } + + CtMethod copy = CtNewMethod.copy(method, destClass, null); + destClass.addMethod(copy); + LOGGER.debug("Copied method {}.{} to {}", srcClass.getSimpleName(), method.getName(), destClass.getSimpleName()); + } + } + + private void mergeClassInitializers(CtClass srcClass, CtClass destClass, ClassFile destClassFile) throws Exception + { + CtConstructor srcInitializer = srcClass.getClassInitializer(); + if (srcInitializer == null) + { + return; + } + + CtConstructor destInitializer = destClass.getClassInitializer(); + if (destInitializer == null && srcInitializer != null) + { + CtConstructor copy = CtNewConstructor.copy(srcInitializer, destClass, null); + destClass.addConstructor(copy); + LOGGER.debug("Copied static initializer of {} to {}", srcClass.getSimpleName(), destClass.getSimpleName()); + } + else + { + CtMethod method = destInitializer.toMethod("__static", destClass); + destClass.addMethod(method); + destClass.removeConstructor(destInitializer); + LOGGER.debug("Move static initializer of {}", destClass.getSimpleName()); + mergeClassInitializers(srcClass, destClass, destClassFile); + } + } + + /** + * + */ + static void unregisterInstrumenation() + { + ourInstrumentation.removeTransformer(transformer); + } +} diff --git a/src/main/java/com/zaxxer/hikari/javassist/HikariInject.java b/src/main/java/com/zaxxer/hikari/javassist/HikariInject.java new file mode 100644 index 00000000..5d86975a --- /dev/null +++ b/src/main/java/com/zaxxer/hikari/javassist/HikariInject.java @@ -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 HikariInject { + // marker interface +} diff --git a/src/main/java/com/zaxxer/hikari/proxy/HikariInstrumentationAgent.java b/src/main/java/com/zaxxer/hikari/javassist/HikariInstrumentationAgent.java similarity index 99% rename from src/main/java/com/zaxxer/hikari/proxy/HikariInstrumentationAgent.java rename to src/main/java/com/zaxxer/hikari/javassist/HikariInstrumentationAgent.java index 58b84a68..281f1352 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/HikariInstrumentationAgent.java +++ b/src/main/java/com/zaxxer/hikari/javassist/HikariInstrumentationAgent.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.zaxxer.hikari.proxy; +package com.zaxxer.hikari.javassist; import java.io.DataInputStream; import java.io.File; @@ -239,6 +239,11 @@ public class HikariInstrumentationAgent private void loadIfInstrumentable(String className, DataInputStream classInputStream) throws IOException, ClassNotFoundException { ClassFile classFile = new ClassFile(classInputStream); + if (classFile.isAbstract()) + { + return; + } + for (String iface : classFile.getInterfaces()) { if (!iface.startsWith("java.sql")) diff --git a/src/main/java/com/zaxxer/hikari/proxy/HikariClassTransformer.java b/src/main/java/com/zaxxer/hikari/proxy/HikariClassTransformer.java deleted file mode 100644 index 32fa452e..00000000 --- a/src/main/java/com/zaxxer/hikari/proxy/HikariClassTransformer.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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.proxy; - -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 javassist.bytecode.ClassFile; - -/** - * - * @author Brett Wooldridge - */ -public class HikariClassTransformer implements ClassFileTransformer -{ - // private static final Logger LOGGER = LoggerFactory.getLogger(HikariClassTransformer.class); - - private static Instrumentation ourInstrumentation; - private static HikariClassTransformer transformer; - - private String sniffPackage; - - /** - * Private constructor. - * - * @param sniffPackage the package name used to filter only classes we are interested in - */ - private HikariClassTransformer(String sniffPackage) - { - 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) - { - ourInstrumentation = instrumentation; - - ourInstrumentation.addTransformer(new HikariClassTransformer(agentArgs), false); - } - - /** {@inheritDoc} */ - public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) - throws IllegalClassFormatException - { - if (!className.startsWith(sniffPackage)) - { - return classfileBuffer; - } - - try - { - ClassFile classFile = new ClassFile(new DataInputStream(new ByteArrayInputStream(classfileBuffer))); - for (String iface : classFile.getInterfaces()) - { - if (!iface.startsWith("java.sql")) - { - continue; - } - - if (iface.equals("java.sql.Connection")) - { - transformConnection(classFile); - } - else if (iface.equals("java.sql.PreparedStatement")) - { - transformPreparedStatement(classFile); - } - else if (iface.equals("java.sql.CallableStatement")) - { - transformCallableStatement(classFile); - } - else if (iface.equals("java.sql.Statement")) - { - transformStatement(classFile); - } - else if (iface.equals("java.sql.ResultSet")) - { - transformResultSet(classFile); - } - } - - // None of the interfaces we care about were found, so just return the class file buffer - return classfileBuffer; - } - catch (Exception e) - { - return classfileBuffer; - } - } - - /** - * @param classFile - */ - private void transformConnection(ClassFile classFile) - { - } - - /** - * @param classFile - */ - private void transformPreparedStatement(ClassFile classFile) - { - } - - /** - * @param classFile - */ - private void transformCallableStatement(ClassFile classFile) - { - } - - /** - * @param classFile - */ - private void transformStatement(ClassFile classFile) - { - } - - /** - * @param classFile - */ - private void transformResultSet(ClassFile classFile) - { - } - - /** - * - */ - static void unregisterInstrumenation() - { - ourInstrumentation.removeTransformer(transformer); - } -}