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}