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.ResultSet; 032import java.sql.SQLException; 033import java.sql.Statement; 034import java.util.ArrayList; 035import java.util.Arrays; 036import java.util.LinkedList; 037import java.util.List; 038import java.util.Map; 039import java.util.Set; 040import java.util.SortedMap; 041 042import javax.sql.rowset.serial.SerialBlob; 043import javax.sql.rowset.serial.SerialClob; 044 045import net.sf.hajdbc.Database; 046import net.sf.hajdbc.util.reflect.Methods; 047import net.sf.hajdbc.util.reflect.ProxyFactory; 048import net.sf.hajdbc.util.reflect.SimpleInvocationHandler; 049 050/** 051 * @author Paul Ferraro 052 * @param <D> 053 * @param <S> 054 */ 055@SuppressWarnings("nls") 056public class ResultSetInvocationHandler<D, S extends Statement> extends AbstractChildInvocationHandler<D, S, ResultSet> 057{ 058 private static final Set<Method> driverReadMethodSet = Methods.findMethods(ResultSet.class, "findColumn", "getConcurrency", "getCursorName", "getFetchDirection", "getFetchSize", "getHoldability", "getMetaData", "getRow", "getType", "getWarnings", "isAfterLast", "isBeforeFirst", "isClosed", "isFirst", "isLast", "row(Deleted|Inserted|Updated)", "wasNull"); 059 private static final Set<Method> driverWriteMethodSet = Methods.findMethods(ResultSet.class, "clearWarnings", "setFetchDirection", "setFetchSize"); 060 private static final Set<Method> absoluteNavigationMethodSet = Methods.findMethods(ResultSet.class, "absolute", "afterLast", "beforeFirst", "first", "last"); 061 private static final Set<Method> relativeNavigationMethodSet = Methods.findMethods(ResultSet.class, "moveTo(Current|Insert)Row", "next", "previous", "relative"); 062 private static final Set<Method> transactionalWriteMethodSet = Methods.findMethods(ResultSet.class, "(delete|insert|update)Row"); 063 064 private static final Method closeMethod = Methods.getMethod(ResultSet.class, "close"); 065 private static final Method cancelRowUpdatesMethod = Methods.getMethod(ResultSet.class, "cancelRowUpdates"); 066 private static final Method getStatementMethod = Methods.getMethod(ResultSet.class, "getStatement"); 067 068 private static final Set<Method> getBlobMethodSet = Methods.findMethods(ResultSet.class, "getBlob"); 069 private static final Set<Method> getClobMethodSet = Methods.findMethods(ResultSet.class, "getClob", "getNClob"); 070 071 protected FileSupport fileSupport; 072 private TransactionContext<D> transactionContext; 073 private List<Invoker<D, ResultSet, ?>> updateInvokerList = new LinkedList<Invoker<D, ResultSet, ?>>(); 074 private Invoker<D, ResultSet, ?> absoluteNavigationInvoker = null; 075 private List<Invoker<D, ResultSet, ?>> relativeNavigationInvokerList = new LinkedList<Invoker<D, ResultSet, ?>>(); 076 077 /** 078 * @param statement the statement that created this result set 079 * @param proxy the invocation handler of the statement that created this result set 080 * @param invoker the invoker that was used to create this result set 081 * @param resultSetMap a map of database to underlying result set 082 * @param transactionContext 083 * @param fileSupport support for streams 084 * @throws Exception 085 */ 086 protected ResultSetInvocationHandler(S statement, SQLProxy<D, S> proxy, Invoker<D, S, ResultSet> invoker, Map<Database<D>, ResultSet> resultSetMap, TransactionContext<D> transactionContext, FileSupport fileSupport) throws Exception 087 { 088 super(statement, proxy, invoker, ResultSet.class, resultSetMap); 089 090 this.transactionContext = transactionContext; 091 this.fileSupport = fileSupport; 092 } 093 094 /** 095 * @see net.sf.hajdbc.sql.AbstractChildInvocationHandler#getInvocationStrategy(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) 096 */ 097 @Override 098 protected InvocationStrategy<D, ResultSet, ?> getInvocationStrategy(ResultSet resultSet, Method method, Object[] parameters) throws Exception 099 { 100 if (driverReadMethodSet.contains(method)) 101 { 102 return new DriverReadInvocationStrategy<D, ResultSet, Object>(); 103 } 104 105 if (driverWriteMethodSet.contains(method) || absoluteNavigationMethodSet.contains(method) || relativeNavigationMethodSet.contains(method) || method.equals(closeMethod) || method.equals(cancelRowUpdatesMethod)) 106 { 107 return new DriverWriteInvocationStrategy<D, ResultSet, Object>(); 108 } 109 110 if (transactionalWriteMethodSet.contains(method)) 111 { 112 return this.transactionContext.start(new DatabaseWriteInvocationStrategy<D, ResultSet, Object>(this.cluster.getTransactionalExecutor()), this.getParent().getConnection()); 113 } 114 115 if (method.equals(getStatementMethod)) 116 { 117 return new InvocationStrategy<D, ResultSet, S>() 118 { 119 public S invoke(SQLProxy<D, ResultSet> proxy, Invoker<D, ResultSet, S> invoker) throws Exception 120 { 121 return ResultSetInvocationHandler.this.getParent(); 122 } 123 }; 124 } 125 126 if (getBlobMethodSet.contains(method)) 127 { 128 return new BlobInvocationStrategy<D, ResultSet>(this.cluster, resultSet); 129 } 130 131 if (getClobMethodSet.contains(method)) 132 { 133 return new ClobInvocationStrategy<D, ResultSet>(this.cluster, resultSet, method.getReturnType().asSubclass(Clob.class)); 134 } 135 136 if (this.isGetMethod(method)) 137 { 138 return new DriverReadInvocationStrategy<D, ResultSet, Object>(); 139 } 140 141 if (this.isUpdateMethod(method)) 142 { 143 return new DriverWriteInvocationStrategy<D, ResultSet, Object>(); 144 } 145 146 return super.getInvocationStrategy(resultSet, method, parameters); 147 } 148 149 /** 150 * @see net.sf.hajdbc.sql.AbstractChildInvocationHandler#getInvoker(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) 151 */ 152 @SuppressWarnings("unchecked") 153 @Override 154 protected Invoker<D, ResultSet, ?> getInvoker(ResultSet object, final Method method, final Object[] parameters) throws Exception 155 { 156 Class<?>[] types = method.getParameterTypes(); 157 158 if (this.isUpdateMethod(method) && (parameters.length > 1) && (parameters[1] != null)) 159 { 160 Class<?> type = types[1]; 161 162 if (type.equals(InputStream.class)) 163 { 164 final File file = this.fileSupport.createFile((InputStream) parameters[1]); 165 166 return new Invoker<D, ResultSet, Object>() 167 { 168 public Object invoke(Database<D> database, ResultSet resultSet) throws SQLException 169 { 170 List<Object> parameterList = new ArrayList<Object>(Arrays.asList(parameters)); 171 172 parameterList.set(1, ResultSetInvocationHandler.this.fileSupport.getInputStream(file)); 173 174 return Methods.invoke(method, resultSet, parameterList.toArray()); 175 } 176 }; 177 } 178 179 if (type.equals(Reader.class)) 180 { 181 final File file = this.fileSupport.createFile((Reader) parameters[1]); 182 183 return new Invoker<D, ResultSet, Object>() 184 { 185 public Object invoke(Database<D> database, ResultSet resultSet) throws SQLException 186 { 187 List<Object> parameterList = new ArrayList<Object>(Arrays.asList(parameters)); 188 189 parameterList.set(1, ResultSetInvocationHandler.this.fileSupport.getReader(file)); 190 191 return Methods.invoke(method, resultSet, parameterList.toArray()); 192 } 193 }; 194 } 195 196 if (type.equals(Blob.class)) 197 { 198 Blob blob = (Blob) parameters[1]; 199 200 if (Proxy.isProxyClass(blob.getClass())) 201 { 202 InvocationHandler handler = Proxy.getInvocationHandler(blob); 203 204 if (SQLProxy.class.isInstance(handler)) 205 { 206 final SQLProxy<D, Blob> proxy = (SQLProxy) handler; 207 208 return new Invoker<D, ResultSet, Object>() 209 { 210 public Object invoke(Database<D> database, ResultSet resultSet) throws SQLException 211 { 212 List<Object> parameterList = new ArrayList<Object>(Arrays.asList(parameters)); 213 214 parameterList.set(1, proxy.getObject(database)); 215 216 return Methods.invoke(method, resultSet, parameterList.toArray()); 217 } 218 }; 219 } 220 } 221 222 parameters[1] = new SerialBlob(blob); 223 } 224 225 // Handle both clob and nclob 226 if (Clob.class.isAssignableFrom(type)) 227 { 228 Clob clob = (Clob) parameters[1]; 229 230 if (Proxy.isProxyClass(clob.getClass())) 231 { 232 InvocationHandler handler = Proxy.getInvocationHandler(clob); 233 234 if (SQLProxy.class.isInstance(handler)) 235 { 236 final SQLProxy<D, Clob> proxy = (SQLProxy) handler; 237 238 return new Invoker<D, ResultSet, Object>() 239 { 240 public Object invoke(Database<D> database, ResultSet resultSet) throws SQLException 241 { 242 List<Object> parameterList = new ArrayList<Object>(Arrays.asList(parameters)); 243 244 parameterList.set(1, proxy.getObject(database)); 245 246 return Methods.invoke(method, resultSet, parameterList.toArray()); 247 } 248 }; 249 } 250 } 251 252 Clob serialClob = new SerialClob(clob); 253 254 parameters[1] = type.equals(Clob.class) ? serialClob : ProxyFactory.createProxy(type, new SimpleInvocationHandler(serialClob)); 255 } 256 } 257 258 return super.getInvoker(object, method, parameters); 259 } 260 261 /** 262 * @see net.sf.hajdbc.sql.AbstractChildInvocationHandler#postInvoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) 263 */ 264 @Override 265 protected void postInvoke(ResultSet object, Method method, Object[] parameters) 266 { 267 if (method.equals(closeMethod)) 268 { 269 this.getParentProxy().removeChild(this); 270 } 271 } 272 273 /** 274 * @see net.sf.hajdbc.sql.AbstractChildInvocationHandler#handleFailures(java.util.SortedMap) 275 */ 276 @Override 277 public <R> SortedMap<Database<D>, R> handlePartialFailure(SortedMap<Database<D>, R> resultMap, SortedMap<Database<D>, Exception> exceptionMap) throws Exception 278 { 279 return this.getParentProxy().handlePartialFailure(resultMap, exceptionMap); 280 } 281 282 /** 283 * @see net.sf.hajdbc.sql.AbstractChildInvocationHandler#close(java.lang.Object, java.lang.Object) 284 */ 285 @Override 286 protected void close(S statement, ResultSet resultSet) throws SQLException 287 { 288 resultSet.close(); 289 } 290 291 /** 292 * @see net.sf.hajdbc.sql.AbstractInvocationHandler#record(net.sf.hajdbc.sql.Invoker, java.lang.reflect.Method, java.lang.Object[]) 293 */ 294 @Override 295 protected void record(Invoker<D, ResultSet, ?> invoker, Method method, Object[] parameters) 296 { 297 if (this.isUpdateMethod(method)) 298 { 299 synchronized (this.updateInvokerList) 300 { 301 this.updateInvokerList.add(invoker); 302 } 303 } 304 else if (transactionalWriteMethodSet.contains(method) || method.equals(cancelRowUpdatesMethod)) 305 { 306 synchronized (this.updateInvokerList) 307 { 308 this.updateInvokerList.clear(); 309 } 310 } 311 else if (absoluteNavigationMethodSet.contains(method)) 312 { 313 synchronized (this.relativeNavigationInvokerList) 314 { 315 this.absoluteNavigationInvoker = invoker; 316 this.relativeNavigationInvokerList.clear(); 317 } 318 } 319 else if (relativeNavigationMethodSet.contains(method)) 320 { 321 synchronized (this.relativeNavigationInvokerList) 322 { 323 this.relativeNavigationInvokerList.add(invoker); 324 } 325 } 326 else 327 { 328 super.record(invoker, method, parameters); 329 } 330 } 331 332 /** 333 * @see net.sf.hajdbc.sql.AbstractInvocationHandler#isRecordable(java.lang.reflect.Method) 334 */ 335 @Override 336 protected boolean isRecordable(Method method) 337 { 338 return driverWriteMethodSet.contains(method); 339 } 340 341 /** 342 * @see net.sf.hajdbc.sql.AbstractInvocationHandler#replay(net.sf.hajdbc.Database, java.lang.Object) 343 */ 344 @Override 345 protected void replay(Database<D> database, ResultSet resultSet) throws Exception 346 { 347 super.replay(database, resultSet); 348 349 synchronized (this.relativeNavigationInvokerList) 350 { 351 if (this.absoluteNavigationInvoker != null) 352 { 353 this.absoluteNavigationInvoker.invoke(database, resultSet); 354 } 355 356 for (Invoker<D, ResultSet, ?> invoker: this.relativeNavigationInvokerList) 357 { 358 invoker.invoke(database, resultSet); 359 } 360 } 361 362 synchronized (this.updateInvokerList) 363 { 364 for (Invoker<D, ResultSet, ?> invoker: this.updateInvokerList) 365 { 366 invoker.invoke(database, resultSet); 367 } 368 } 369 } 370 371 private boolean isGetMethod(Method method) 372 { 373 Class<?>[] types = method.getParameterTypes(); 374 375 return method.getName().startsWith("get") && (types != null) && (types.length > 0) && (types[0].equals(String.class) || types[0].equals(Integer.TYPE)); 376 } 377 378 private boolean isUpdateMethod(Method method) 379 { 380 Class<?>[] types = method.getParameterTypes(); 381 382 return method.getName().startsWith("update") && (types != null) && (types.length > 0) && (types[0].equals(String.class) || types[0].equals(Integer.TYPE)); 383 } 384}