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