001/* 002 * HA-JDBC: High-Availability JDBC 003 * Copyright (c) 2004-2007 Paul Ferraro 004 * 005 * This library is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Lesser General Public License as published by the 007 * Free Software Foundation; either version 2.1 of the License, or (at your 008 * option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, but WITHOUT 011 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 012 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 013 * for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License 016 * along with this library; if not, write to the Free Software Foundation, 017 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018 * 019 * Contact: ferraro@users.sourceforge.net 020 */ 021package net.sf.hajdbc.sql; 022 023import java.io.File; 024import java.io.InputStream; 025import java.io.Reader; 026import java.lang.reflect.InvocationHandler; 027import java.lang.reflect.Method; 028import java.lang.reflect.Proxy; 029import java.sql.Blob; 030import java.sql.Clob; 031import java.sql.Connection; 032import java.sql.PreparedStatement; 033import java.sql.ResultSet; 034import java.sql.SQLException; 035import java.util.ArrayList; 036import java.util.Arrays; 037import java.util.Collections; 038import java.util.List; 039import java.util.Map; 040import java.util.Set; 041import java.util.concurrent.locks.Lock; 042 043import javax.sql.rowset.serial.SerialBlob; 044import javax.sql.rowset.serial.SerialClob; 045 046import net.sf.hajdbc.Database; 047import net.sf.hajdbc.util.reflect.Methods; 048import net.sf.hajdbc.util.reflect.ProxyFactory; 049import net.sf.hajdbc.util.reflect.SimpleInvocationHandler; 050 051/** 052 * @author Paul Ferraro 053 * @param <D> 054 * @param <S> 055 */ 056@SuppressWarnings("nls") 057public class AbstractPreparedStatementInvocationHandler<D, S extends PreparedStatement> extends AbstractStatementInvocationHandler<D, S> 058{ 059 private static final Set<Method> databaseReadMethodSet = Methods.findMethods(PreparedStatement.class, "getMetaData", "getParameterMetaData"); 060 private static final Method executeMethod = Methods.getMethod(PreparedStatement.class, "execute"); 061 private static final Method executeUpdateMethod = Methods.getMethod(PreparedStatement.class, "executeUpdate"); 062 private static final Method executeQueryMethod = Methods.getMethod(PreparedStatement.class, "executeQuery"); 063 private static final Method clearParametersMethod = Methods.getMethod(PreparedStatement.class, "clearParameters"); 064 private static final Method addBatchMethod = Methods.getMethod(PreparedStatement.class, "addBatch"); 065 066 protected List<Lock> lockList = Collections.emptyList(); 067 protected boolean selectForUpdate = false; 068 private Set<Method> setMethodSet; 069 070 /** 071 * @param connection 072 * @param proxy 073 * @param invoker 074 * @param statementClass 075 * @param statementMap 076 * @param transactionContext 077 * @param fileSupport 078 * @throws Exception 079 */ 080 public AbstractPreparedStatementInvocationHandler(Connection connection, SQLProxy<D, Connection> proxy, Invoker<D, Connection, S> invoker, Class<S> statementClass, Map<Database<D>, S> statementMap, TransactionContext<D> transactionContext, FileSupport fileSupport, Set<Method> setMethods) throws Exception 081 { 082 super(connection, proxy, invoker, statementClass, statementMap, transactionContext, fileSupport); 083 084 this.setMethodSet = setMethods; 085 } 086 087 /** 088 * @see net.sf.hajdbc.sql.AbstractStatementInvocationHandler#getInvocationStrategy(java.sql.Statement, java.lang.reflect.Method, java.lang.Object[]) 089 */ 090 @Override 091 protected InvocationStrategy<D, S, ?> getInvocationStrategy(S statement, Method method, Object[] parameters) throws Exception 092 { 093 if (databaseReadMethodSet.contains(method)) 094 { 095 return new DatabaseReadInvocationStrategy<D, S, Object>(); 096 } 097 098 if (this.setMethodSet.contains(method) || method.equals(clearParametersMethod) || method.equals(addBatchMethod)) 099 { 100 return new DriverWriteInvocationStrategy<D, S, Object>(); 101 } 102 103 if (method.equals(executeMethod) || method.equals(executeUpdateMethod)) 104 { 105 return this.transactionContext.start(new LockingInvocationStrategy<D, S, Object>(new DatabaseWriteInvocationStrategy<D, S, Object>(this.cluster.getTransactionalExecutor()), this.lockList), this.getParent()); 106 } 107 108 if (method.equals(executeQueryMethod)) 109 { 110 int concurrency = statement.getResultSetConcurrency(); 111 112 if (this.lockList.isEmpty() && (concurrency == ResultSet.CONCUR_READ_ONLY) && !this.selectForUpdate) 113 { 114 return new DatabaseReadInvocationStrategy<D, S, Object>(); 115 } 116 117 InvocationStrategy<D, S, ResultSet> strategy = new LockingInvocationStrategy<D, S, ResultSet>(new EagerResultSetInvocationStrategy<D, S>(this.cluster, statement, this.transactionContext, this.fileSupport), this.lockList); 118 119 return this.selectForUpdate ? this.transactionContext.start(strategy, this.getParent()) : strategy; 120 } 121 122 return super.getInvocationStrategy(statement, method, parameters); 123 } 124 125 /** 126 * @see net.sf.hajdbc.sql.AbstractChildInvocationHandler#getInvoker(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) 127 */ 128 @SuppressWarnings("unchecked") 129 @Override 130 protected Invoker<D, S, ?> getInvoker(S statement, final Method method, final Object[] parameters) throws Exception 131 { 132 Class<?>[] types = method.getParameterTypes(); 133 134 if (this.isParameterSetMethod(method) && (parameters.length > 1) && (parameters[1] != null)) 135 { 136 Class<?> type = types[1]; 137 138 if (type.equals(InputStream.class)) 139 { 140 final File file = this.fileSupport.createFile((InputStream) parameters[1]); 141 142 return new Invoker<D, S, Object>() 143 { 144 public Object invoke(Database<D> database, S statement) throws SQLException 145 { 146 List<Object> parameterList = new ArrayList<Object>(Arrays.asList(parameters)); 147 148 parameterList.set(1, AbstractPreparedStatementInvocationHandler.this.fileSupport.getInputStream(file)); 149 150 return Methods.invoke(method, statement, parameterList.toArray()); 151 } 152 }; 153 } 154 155 if (type.equals(Reader.class)) 156 { 157 final File file = this.fileSupport.createFile((Reader) parameters[1]); 158 159 return new Invoker<D, S, Object>() 160 { 161 public Object invoke(Database<D> database, S statement) throws SQLException 162 { 163 List<Object> parameterList = new ArrayList<Object>(Arrays.asList(parameters)); 164 165 parameterList.set(1, AbstractPreparedStatementInvocationHandler.this.fileSupport.getReader(file)); 166 167 return Methods.invoke(method, statement, parameterList.toArray()); 168 } 169 }; 170 } 171 172 if (type.equals(Blob.class)) 173 { 174 Blob blob = (Blob) parameters[1]; 175 176 if (Proxy.isProxyClass(blob.getClass())) 177 { 178 InvocationHandler handler = Proxy.getInvocationHandler(blob); 179 180 if (SQLProxy.class.isInstance(handler)) 181 { 182 final SQLProxy<D, Blob> proxy = (SQLProxy) handler; 183 184 return new Invoker<D, S, Object>() 185 { 186 public Object invoke(Database<D> database, S statement) throws SQLException 187 { 188 List<Object> parameterList = new ArrayList<Object>(Arrays.asList(parameters)); 189 190 parameterList.set(1, proxy.getObject(database)); 191 192 return Methods.invoke(method, statement, parameterList.toArray()); 193 } 194 }; 195 } 196 } 197 198 parameters[1] = new SerialBlob(blob); 199 } 200 201 // Handle both clob and nclob 202 if (Clob.class.isAssignableFrom(type)) 203 { 204 Clob clob = (Clob) parameters[1]; 205 206 if (Proxy.isProxyClass(clob.getClass())) 207 { 208 InvocationHandler handler = Proxy.getInvocationHandler(clob); 209 210 if (SQLProxy.class.isInstance(handler)) 211 { 212 final SQLProxy<D, Clob> proxy = (SQLProxy) handler; 213 214 return new Invoker<D, S, Object>() 215 { 216 public Object invoke(Database<D> database, S statement) throws SQLException 217 { 218 List<Object> parameterList = new ArrayList<Object>(Arrays.asList(parameters)); 219 220 parameterList.set(1, proxy.getObject(database)); 221 222 return Methods.invoke(method, statement, parameterList.toArray()); 223 } 224 }; 225 } 226 } 227 228 Clob serialClob = new SerialClob(clob); 229 230 parameters[1] = type.equals(Clob.class) ? serialClob : ProxyFactory.createProxy(type, new SimpleInvocationHandler(serialClob)); 231 } 232 } 233 234 return super.getInvoker(statement, method, parameters); 235 } 236 237 /** 238 * @see net.sf.hajdbc.sql.AbstractStatementInvocationHandler#isBatchMethod(java.lang.reflect.Method) 239 */ 240 @Override 241 protected boolean isBatchMethod(Method method) 242 { 243 return method.equals(addBatchMethod) || method.equals(clearParametersMethod) || this.isParameterSetMethod(method) || super.isBatchMethod(method); 244 } 245 246 private boolean isParameterSetMethod(Method method) 247 { 248 Class<?>[] types = method.getParameterTypes(); 249 250 return this.setMethodSet.contains(method) && (types.length > 0) && this.isIndexType(types[0]); 251 } 252 253 protected boolean isIndexType(Class<?> type) 254 { 255 return type.equals(Integer.TYPE); 256 } 257}