001/* 002 * HA-JDBC: High-Availability JDBC 003 * Copyright (C) 2012 Paul Ferraro 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Lesser General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package net.sf.hajdbc.sql; 019 020import java.lang.reflect.Method; 021import java.lang.reflect.Proxy; 022import java.sql.Clob; 023import java.sql.Connection; 024import java.sql.NClob; 025import java.sql.SQLException; 026import java.sql.Savepoint; 027import java.util.Arrays; 028import java.util.HashSet; 029import java.util.Set; 030 031import net.sf.hajdbc.Database; 032import net.sf.hajdbc.durability.Durability; 033import net.sf.hajdbc.invocation.InvocationStrategies; 034import net.sf.hajdbc.invocation.InvocationStrategy; 035import net.sf.hajdbc.invocation.Invoker; 036import net.sf.hajdbc.util.StaticRegistry; 037import net.sf.hajdbc.util.reflect.Methods; 038 039/** 040 * @author Paul Ferraro 041 * @param <D> 042 * @param <P> 043 */ 044public class ConnectionInvocationHandler<Z, D extends Database<Z>, P> extends ChildInvocationHandler<Z, D, P, SQLException, Connection, SQLException, ConnectionProxyFactory<Z, D, P>> 045{ 046 private static final Set<Method> driverReadMethodSet = Methods.findMethods(Connection.class, "createStruct", "getAutoCommit", "getCatalog", "getClientInfo", "getHoldability", "getNetworkTimeout", "getSchema", "getTransactionIsolation", "getTypeMap", "getWarnings", "isClosed", "isCloseOnCompletion", "isReadOnly", "nativeSQL"); 047 private static final Set<Method> databaseReadMethodSet = Methods.findMethods(Connection.class, "isValid"); 048 private static final Set<Method> driverWriterMethodSet = Methods.findMethods(Connection.class, "abort", "clearWarnings", "closeOnCompletion", "setClientInfo", "setHoldability", "setNetworkTimeout", "setSchema", "setTypeMap"); 049 private static final Set<Method> createStatementMethodSet = Methods.findMethods(Connection.class, "createStatement"); 050 private static final Set<Method> prepareStatementMethodSet = Methods.findMethods(Connection.class, "prepareStatement"); 051 private static final Set<Method> prepareCallMethodSet = Methods.findMethods(Connection.class, "prepareCall"); 052 private static final Set<Method> setSavepointMethodSet = Methods.findMethods(Connection.class, "setSavepoint"); 053 054 private static final Method setAutoCommitMethod = Methods.getMethod(Connection.class, "setAutoCommit", Boolean.TYPE); 055 private static final Method commitMethod = Methods.getMethod(Connection.class, "commit"); 056 private static final Method rollbackMethod = Methods.getMethod(Connection.class, "rollback"); 057 private static final Method getMetaDataMethod = Methods.getMethod(Connection.class, "getMetaData"); 058 private static final Method releaseSavepointMethod = Methods.getMethod(Connection.class, "releaseSavepoint", Savepoint.class); 059 private static final Method rollbackSavepointMethod = Methods.getMethod(Connection.class, "rollback", Savepoint.class); 060 private static final Method closeMethod = Methods.getMethod(Connection.class, "close"); 061 private static final Method createArrayMethod = Methods.getMethod(Connection.class, "createArrayOf", String.class, Object[].class); 062 private static final Method createBlobMethod = Methods.getMethod(Connection.class, "createBlob"); 063 private static final Method createClobMethod = Methods.getMethod(Connection.class, "createClob"); 064 private static final Method createNClobMethod = Methods.getMethod(Connection.class, "createNClob"); 065 private static final Method createSQLXMLMethod = Methods.getMethod(Connection.class, "createSQLXML"); 066 067 private static final Set<Method> endTransactionMethodSet = new HashSet<Method>(Arrays.asList(commitMethod, rollbackMethod, setAutoCommitMethod)); 068 private static final Set<Method> createLocatorMethodSet = new HashSet<Method>(Arrays.asList(createBlobMethod, createClobMethod, createNClobMethod, createSQLXMLMethod)); 069 070 private static final StaticRegistry<Method, Durability.Phase> phaseRegistry = new DurabilityPhaseRegistry(Arrays.asList(commitMethod, setAutoCommitMethod), Arrays.asList(rollbackMethod)); 071 072 /** 073 * Constructs a new ConnectionInvocationHandler 074 * @param proxyFactory a factory for creating connection proxies 075 */ 076 public ConnectionInvocationHandler(ConnectionProxyFactory<Z, D, P> proxyFactory) 077 { 078 super(Connection.class, proxyFactory, null); 079 } 080 081 @Override 082 protected ProxyFactoryFactory<Z, D, Connection, SQLException, ?, ? extends Exception> getProxyFactoryFactory(Connection connection, Method method, Object... parameters) throws SQLException 083 { 084 if (createStatementMethodSet.contains(method)) 085 { 086 return new StatementProxyFactoryFactory<Z, D>(this.getProxyFactory().getTransactionContext()); 087 } 088 if (prepareStatementMethodSet.contains(method)) 089 { 090 String sql = (String) parameters[0]; 091 return new PreparedStatementProxyFactoryFactory<Z, D>(this.getProxyFactory().getTransactionContext(), this.getProxyFactory().extractLocks(sql), this.getProxyFactory().isSelectForUpdate(sql)); 092 } 093 if (prepareCallMethodSet.contains(method)) 094 { 095 String sql = (String) parameters[0]; 096 return new CallableStatementProxyFactoryFactory<Z, D>(this.getProxyFactory().getTransactionContext(), this.getProxyFactory().extractLocks(sql)); 097 } 098 099 if (setSavepointMethodSet.contains(method)) 100 { 101 return new SavepointProxyFactoryFactory<Z, D>(); 102 } 103 104 if (method.equals(getMetaDataMethod)) 105 { 106 return new DatabaseMetaDataProxyFactoryFactory<Z, D>(); 107 } 108 109 if (method.equals(createArrayMethod)) 110 { 111 return new ArrayProxyFactoryFactory<Z, D, Connection>(this.getProxyFactory().locatorsUpdateCopy()); 112 } 113 if (method.equals(createBlobMethod)) 114 { 115 return new BlobProxyFactoryFactory<Z, D, Connection>(this.getProxyFactory().locatorsUpdateCopy()); 116 } 117 if (method.equals(createClobMethod)) 118 { 119 return new ClobProxyFactoryFactory<Z, D, Connection, Clob>(Clob.class, this.getProxyFactory().locatorsUpdateCopy()); 120 } 121 if (method.equals(createNClobMethod)) 122 { 123 return new ClobProxyFactoryFactory<Z, D, Connection, NClob>(NClob.class, this.getProxyFactory().locatorsUpdateCopy()); 124 } 125 if (method.equals(createSQLXMLMethod)) 126 { 127 return new SQLXMLProxyFactoryFactory<Z, D, Connection>(this.getProxyFactory().locatorsUpdateCopy()); 128 } 129 130 return super.getProxyFactoryFactory(connection, method, parameters); 131 } 132 133 /** 134 * {@inheritDoc} 135 */ 136 @Override 137 protected InvocationStrategy getInvocationStrategy(Connection connection, Method method, Object... parameters) throws SQLException 138 { 139 if (driverReadMethodSet.contains(method)) 140 { 141 return InvocationStrategies.INVOKE_ON_ANY; 142 } 143 144 if (databaseReadMethodSet.contains(method) || method.equals(getMetaDataMethod)) 145 { 146 return InvocationStrategies.INVOKE_ON_NEXT; 147 } 148 149 if (driverWriterMethodSet.contains(method) || method.equals(closeMethod) || createStatementMethodSet.contains(method)) 150 { 151 return InvocationStrategies.INVOKE_ON_EXISTING; 152 } 153 154 if (prepareStatementMethodSet.contains(method) || prepareCallMethodSet.contains(method) || createLocatorMethodSet.contains(method)) 155 { 156 return InvocationStrategies.INVOKE_ON_ALL; 157 } 158 159 if (endTransactionMethodSet.contains(method)) 160 { 161 return this.getProxyFactory().getTransactionContext().end(InvocationStrategies.END_TRANSACTION_INVOKE_ON_ALL, phaseRegistry.get(method)); 162 } 163 164 if (method.equals(rollbackSavepointMethod) || method.equals(releaseSavepointMethod)) 165 { 166 return InvocationStrategies.END_TRANSACTION_INVOKE_ON_ALL; 167 } 168 169 if (setSavepointMethodSet.contains(method)) 170 { 171 return InvocationStrategies.TRANSACTION_INVOKE_ON_ALL; 172 } 173 174 return super.getInvocationStrategy(connection, method, parameters); 175 } 176 177 /** 178 * {@inheritDoc} 179 */ 180 @Override 181 protected <R> Invoker<Z, D, Connection, R, SQLException> getInvoker(Connection connection, Method method, Object... parameters) throws SQLException 182 { 183 if (method.equals(releaseSavepointMethod) || method.equals(rollbackSavepointMethod)) 184 { 185 return this.getInvoker(Savepoint.class, 0, connection, method, parameters); 186 } 187 188 if (prepareStatementMethodSet.contains(method) || prepareCallMethodSet.contains(method)) 189 { 190 parameters[0] = this.getProxyFactory().evaluate((String) parameters[0]); 191 } 192 193 Invoker<Z, D, Connection, R, SQLException> invoker = super.getInvoker(connection, method, parameters); 194 195 if (endTransactionMethodSet.contains(method)) 196 { 197 return this.getProxyFactory().getTransactionContext().end(invoker, phaseRegistry.get(method)); 198 } 199 200 return invoker; 201 } 202 203 @Override 204 protected <R> void postInvoke(Invoker<Z, D, Connection, R, SQLException> invoker, Connection proxy, Method method, Object... parameters) 205 { 206 if (driverWriterMethodSet.contains(method) || method.equals(setAutoCommitMethod)) 207 { 208 this.getProxyFactory().record(invoker); 209 } 210 else if (method.equals(closeMethod)) 211 { 212 this.getProxyFactory().getTransactionContext().close(); 213 this.getProxyFactory().remove(); 214 } 215 else if (method.equals(releaseSavepointMethod)) 216 { 217 SavepointInvocationHandler<Z, D> handler = (SavepointInvocationHandler<Z, D>) Proxy.getInvocationHandler(parameters[0]); 218 this.getProxyFactory().removeChild(handler.getProxyFactory()); 219 } 220 } 221}