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}