/* * Bitronix Transaction Manager * * Copyright (c) 2010, Bitronix Software. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package bitronix.tm.resource.jdbc.proxy; import java.lang.reflect.Constructor; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.Statement; import java.util.HashSet; import java.util.Set; import javassist.CannotCompileException; import javassist.ClassClassPath; import javassist.ClassMap; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtMethod; import javassist.CtNewConstructor; import javassist.CtNewMethod; import javassist.NotFoundException; import javax.sql.XAConnection; import bitronix.tm.resource.jdbc.JdbcPooledConnection; import bitronix.tm.resource.jdbc.LruStatementCache.CacheKey; import bitronix.tm.resource.jdbc.lrc.LrcXAResource; import bitronix.tm.utils.ClassLoaderUtils; /** * This class generates JDBC proxy classes using Javassist bytecode generated * implementations. This is the most efficient proxy factory. * * @author Brett Wooldridge */ public class JdbcJavassistProxyFactory implements JdbcProxyFactory { private ClassMap classMap; private ClassPool classPool; private Constructor<Connection> proxyConnectionConstructor; private Constructor<Statement> proxyStatementConstructor; private Constructor<CallableStatement> proxyCallableStatementConstructor; private Constructor<PreparedStatement> proxyPreparedStatementConstructor; // For LRC we just use the standard Java Proxies private JdbcJavaProxyFactory lrcProxyFactory; JdbcJavassistProxyFactory() { classMap = new ClassMap(); ClassPool defaultPool = ClassPool.getDefault(); classPool = new ClassPool(defaultPool); classPool.insertClassPath(new ClassClassPath(this.getClass())); classPool.childFirstLookup = true; createProxyConnectionClass(); createProxyStatementClass(); createProxyCallableStatementClass(); createProxyPreparedStatementClass(); lrcProxyFactory = new JdbcJavaProxyFactory(); // Clear the map, we don't need it anymore classMap.clear(); classPool = null; } /** {@inheritDoc} */ public Connection getProxyConnection(JdbcPooledConnection jdbcPooledConnection, Connection connection) { try { return proxyConnectionConstructor.newInstance(jdbcPooledConnection, connection); } catch (Exception e) { throw new RuntimeException(e); } } /** {@inheritDoc} */ public Statement getProxyStatement(JdbcPooledConnection jdbcPooledConnection, Statement statement) { try { return proxyStatementConstructor.newInstance(jdbcPooledConnection, statement); } catch (Exception e) { throw new RuntimeException(e); } } /** {@inheritDoc} */ public CallableStatement getProxyCallableStatement(JdbcPooledConnection jdbcPooledConnection, CallableStatement statement) { try { return proxyCallableStatementConstructor.newInstance(jdbcPooledConnection, statement); } catch (Exception e) { throw new RuntimeException(e); } } /** {@inheritDoc} */ public PreparedStatement getProxyPreparedStatement(JdbcPooledConnection jdbcPooledConnection, PreparedStatement statement, CacheKey cacheKey) { try { return proxyPreparedStatementConstructor.newInstance(jdbcPooledConnection, statement, cacheKey); } catch (Exception e) { throw new RuntimeException(e); } } /** {@inheritDoc} */ public XAConnection getProxyXaConnection(Connection connection) { return lrcProxyFactory.getProxyXaConnection(connection); } /** {@inheritDoc} */ public Connection getProxyConnection(LrcXAResource xaResource, Connection connection) { return lrcProxyFactory.getProxyConnection(xaResource, connection); } // --------------------------------------------------------------- // Generate Javassist Proxy Classes // --------------------------------------------------------------- /** * Create a proxy class: class ConnectionJavassistProxy extends ConnectionJavaProxy implements java.sql.Connection */ private void createProxyConnectionClass() { try { Class<Connection> proxyClass = generateProxyClass(Connection.class, ConnectionJavaProxy.class); proxyConnectionConstructor = proxyClass.getConstructor(new Class<?>[] {JdbcPooledConnection.class, Connection.class} ); } catch (Exception e) { throw new RuntimeException(e); } } /** * Create a proxy class: class StatementJavassistProxy extends StatementJavaProxy implements java.sql.Statement */ private void createProxyStatementClass() { try { Class<Statement> proxyClass = generateProxyClass(Statement.class, StatementJavaProxy.class); proxyStatementConstructor = proxyClass.getConstructor(new Class<?>[] {JdbcPooledConnection.class, Statement.class}); } catch (Exception e) { throw new RuntimeException(e); } } /** * Create a proxy class: class CallableStatementJavassistProxy extends CallableStatementJavaProxy implements java.sql.CallableStatement */ private void createProxyCallableStatementClass() { try { Class<CallableStatement> proxyClass = generateProxyClass(CallableStatement.class, CallableStatementJavaProxy.class); proxyCallableStatementConstructor = proxyClass.getConstructor(new Class<?>[] {JdbcPooledConnection.class, CallableStatement.class}); } catch (Exception e) { throw new RuntimeException(e); } } /** * Create a proxy class: class PreparedStatementJavassistProxy extends PreparedStatementJavaProxy implements java.sql.PreparedStatement */ private void createProxyPreparedStatementClass() { try { Class<PreparedStatement> proxyClass = generateProxyClass(PreparedStatement.class, PreparedStatementJavaProxy.class); proxyPreparedStatementConstructor = proxyClass.getConstructor(new Class<?>[] {JdbcPooledConnection.class, PreparedStatement.class, CacheKey.class}); } catch (Exception e) { throw new RuntimeException(e); } } @SuppressWarnings("unchecked") private <T> Class<T> generateProxyClass(Class<T> primaryInterface, Class<?> superClass) throws NotFoundException, CannotCompileException, NoSuchMethodException, SecurityException { // Make a new class that extends one of the JavaProxy classes (ie. superClass); use the name to XxxJavassistProxy instead of XxxJavaProxy String superClassName = superClass.getName(); CtClass superClassCt = classPool.getCtClass(superClassName); CtClass targetCt = classPool.makeClass(superClassName.replace("JavaProxy", "JavassistProxy"), superClassCt); // Generate constructors that simply call super(..) for (CtConstructor constructor : superClassCt.getConstructors()) { CtConstructor ctConstructor = CtNewConstructor.make(constructor.getParameterTypes(), constructor.getExceptionTypes(), targetCt); targetCt.addConstructor(ctConstructor); } // Make a set of method signatures we inherit implementation for, so we don't generate delegates for these Set<String> superSigs = new HashSet<String>(); for (CtMethod method : superClassCt.getMethods()) { superSigs.add(method.getName() + method.getSignature()); } Set<Class<?>> interfaces = ClassLoaderUtils.getAllInterfaces(primaryInterface); for (Class<?> intf : interfaces) { CtClass intfCt = classPool.getCtClass(intf.getName()); targetCt.addInterface(intfCt); for (CtMethod intfMethod : intfCt.getDeclaredMethods()) { if (superSigs.contains(intfMethod.getName() + intfMethod.getSignature())) { // don't generate delegates for methods we override continue; } // Generate a method that simply invokes the same method on the delegate CtMethod method = CtNewMethod.copy(intfMethod, targetCt, classMap); StringBuilder call = new StringBuilder("{"); if ( method.getReturnType() != CtClass.voidType) { call.append("return "); } call.append("((").append(primaryInterface.getName()).append(')'); // cast to primary interface call.append("delegate)."); call.append(method.getName()).append("($$);"); call.append('}'); method.setBody(call.toString()); targetCt.addMethod(method); } } return targetCt.toClass(ClassLoaderUtils.getClassLoader(), null); } }