001/*
002 * HA-JDBC: High-Availability JDBC
003 * Copyright (C) 2012  Paul Ferraro
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Lesser General Public License as published by
007 * the Free Software Foundation, either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Lesser General Public License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018package net.sf.hajdbc.sql;
019
020import java.lang.reflect.Method;
021import java.sql.Connection;
022import java.sql.ResultSet;
023import java.sql.SQLException;
024import java.sql.Statement;
025import java.util.List;
026import java.util.Set;
027import java.util.concurrent.locks.Lock;
028
029import net.sf.hajdbc.Database;
030import net.sf.hajdbc.invocation.InvocationStrategies;
031import net.sf.hajdbc.invocation.InvocationStrategy;
032import net.sf.hajdbc.invocation.Invoker;
033import net.sf.hajdbc.invocation.LockingInvocationStrategy;
034import net.sf.hajdbc.logging.Level;
035import net.sf.hajdbc.util.Resources;
036import net.sf.hajdbc.util.reflect.Methods;
037
038/**
039 * @author Paul Ferraro
040 * @param <D> 
041 * @param <S> 
042 */
043@SuppressWarnings("nls")
044public abstract class AbstractStatementInvocationHandler<Z, D extends Database<Z>, S extends Statement, F extends AbstractStatementProxyFactory<Z, D, S>> extends InputSinkRegistryInvocationHandler<Z, D, Connection, S, F>
045{
046        private static final Set<Method> driverReadMethodSet = Methods.findMethods(Statement.class, "getFetchDirection", "getFetchSize", "getGeneratedKeys", "getMaxFieldSize", "getMaxRows", "getQueryTimeout", "getResultSetConcurrency", "getResultSetHoldability", "getResultSetType", "getUpdateCount", "getWarnings", "isClosed", "isPoolable");
047        private static final Set<Method> driverWriteMethodSet = Methods.findMethods(Statement.class, "clearWarnings", "setCursorName", "setEscapeProcessing", "setFetchDirection", "setFetchSize", "setMaxFieldSize", "setMaxRows", "setPoolable", "setQueryTimeout");
048        private static final Set<Method> executeMethodSet = Methods.findMethods(Statement.class, "execute(Update)?");
049        
050        private static final Method getConnectionMethod = Methods.getMethod(Statement.class, "getConnection");
051        private static final Method executeQueryMethod = Methods.getMethod(Statement.class, "executeQuery", String.class);
052        private static final Method clearBatchMethod = Methods.getMethod(Statement.class, "clearBatch");
053        private static final Method executeBatchMethod = Methods.getMethod(Statement.class, "executeBatch");
054        private static final Method getMoreResultsMethod = Methods.getMethod(Statement.class, "getMoreResults", Integer.TYPE);
055        private static final Method getResultSetMethod = Methods.getMethod(Statement.class, "getResultSet");
056        private static final Method addBatchMethod = Methods.getMethod(Statement.class, "addBatch", String.class);
057        private static final Method closeMethod = Methods.getMethod(Statement.class, "close");
058        
059        public AbstractStatementInvocationHandler(Class<S> statementClass, F proxyFactory)
060        {
061                super(statementClass, proxyFactory, getConnectionMethod);
062        }
063
064        @Override
065        protected ProxyFactoryFactory<Z, D, S, SQLException, ?, ? extends Exception> getProxyFactoryFactory(S object, Method method, Object... parameters) throws SQLException
066        {
067                if (method.equals(executeQueryMethod) || method.equals(getResultSetMethod))
068                {
069                        return new ResultSetProxyFactoryFactory<Z, D, S>(this.getProxyFactory().getTransactionContext(), this.getProxyFactory().getInputSinkRegistry());
070                }
071                
072                return super.getProxyFactoryFactory(object, method, parameters);
073        }
074
075        @Override
076        protected InvocationStrategy getInvocationStrategy(S statement, Method method, Object... parameters) throws SQLException
077        {
078                if (driverReadMethodSet.contains(method))
079                {
080                        return InvocationStrategies.INVOKE_ON_ANY;
081                }
082                
083                if (driverWriteMethodSet.contains(method) || method.equals(closeMethod))
084                {
085                        return InvocationStrategies.INVOKE_ON_EXISTING;
086                }
087                
088                if (executeMethodSet.contains(method))
089                {
090                        List<Lock> locks = this.getProxyFactory().extractLocks((String) parameters[0]);
091                        
092                        return this.getProxyFactory().getTransactionContext().start(new LockingInvocationStrategy(InvocationStrategies.TRANSACTION_INVOKE_ON_ALL, locks), this.getProxyFactory().getParentProxy());
093                }
094                
095                if (method.equals(executeQueryMethod))
096                {
097                        String sql = (String) parameters[0];
098                        
099                        List<Lock> locks = this.getProxyFactory().extractLocks(sql);
100                        int concurrency = statement.getResultSetConcurrency();
101                        boolean selectForUpdate = this.getProxyFactory().isSelectForUpdate(sql);
102                        
103                        if (locks.isEmpty() && (concurrency == ResultSet.CONCUR_READ_ONLY) && !selectForUpdate)
104                        {
105                                boolean repeatableReadSelect = (statement.getConnection().getTransactionIsolation() >= Connection.TRANSACTION_REPEATABLE_READ);
106                                
107                                return repeatableReadSelect ? InvocationStrategies.INVOKE_ON_PRIMARY : InvocationStrategies.INVOKE_ON_NEXT;
108                        }
109                        
110                        InvocationStrategy strategy = InvocationStrategies.TRANSACTION_INVOKE_ON_ALL;
111                        if (!locks.isEmpty())
112                        {
113                                strategy = new LockingInvocationStrategy(strategy, locks);
114                        }
115                        
116                        return selectForUpdate ? this.getProxyFactory().getTransactionContext().start(strategy, this.getProxyFactory().getParentProxy()) : strategy;
117                }
118                
119                if (method.equals(executeBatchMethod))
120                {
121                        return this.getProxyFactory().getTransactionContext().start(new LockingInvocationStrategy(InvocationStrategies.TRANSACTION_INVOKE_ON_ALL, this.getProxyFactory().getBatchLocks()), this.getProxyFactory().getParentProxy());
122                }
123                
124                if (method.equals(getMoreResultsMethod))
125                {
126                        if (parameters[0].equals(Statement.KEEP_CURRENT_RESULT))
127                        {
128                                return InvocationStrategies.INVOKE_ON_EXISTING;
129                        }
130                }
131                
132                if (method.equals(getResultSetMethod))
133                {
134                        if (statement.getResultSetConcurrency() == ResultSet.CONCUR_READ_ONLY)
135                        {
136                                return InvocationStrategies.INVOKE_ON_EXISTING;
137                        }
138
139                        return InvocationStrategies.INVOKE_ON_ALL;
140                }
141                
142                return super.getInvocationStrategy(statement, method, parameters);
143        }
144
145        @Override
146        protected <R> Invoker<Z, D, S, R, SQLException> getInvoker(S proxy, Method method, Object... parameters) throws SQLException
147        {
148                if (method.equals(addBatchMethod) || method.equals(executeQueryMethod) || executeMethodSet.contains(method))
149                {
150                        parameters[0] = this.getProxyFactory().evaluate((String) parameters[0]);
151                }
152                
153                return super.getInvoker(proxy, method, parameters);
154        }
155
156        @Override
157        protected <R> void postInvoke(Invoker<Z, D, S, R, SQLException> invoker, S proxy, Method method, Object... parameters)
158        {
159                if (method.equals(addBatchMethod))
160                {
161                        this.getProxyFactory().addBatchSQL((String) parameters[0]);
162                }
163                else if (method.equals(clearBatchMethod) || method.equals(executeBatchMethod))
164                {
165                        this.getProxyFactory().clearBatch();
166                        this.logger.log(Level.TRACE, "Clearing recorded batch methods");
167                        this.getProxyFactory().clearBatchInvokers();
168                }
169                else if (method.equals(closeMethod))
170                {
171                        Resources.close(this.getProxyFactory().getInputSinkRegistry());
172                        this.getProxyFactory().remove();
173                }
174                
175                if (this.isBatchMethod(method))
176                {
177                        this.logger.log(Level.TRACE, "Recording batch method: {0}", invoker);
178                        this.getProxyFactory().addBatchInvoker(invoker);
179                }
180                else if (driverWriteMethodSet.contains(method))
181                {
182                        this.getProxyFactory().record(invoker);
183                }
184        }
185
186        protected boolean isBatchMethod(Method method)
187        {
188                return method.equals(addBatchMethod);
189        }
190}