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.Clob;
022import java.sql.NClob;
023import java.sql.ResultSet;
024import java.sql.SQLException;
025import java.sql.Statement;
026import java.util.Set;
027
028import net.sf.hajdbc.Database;
029import net.sf.hajdbc.invocation.InvocationStrategies;
030import net.sf.hajdbc.invocation.InvocationStrategy;
031import net.sf.hajdbc.invocation.Invoker;
032import net.sf.hajdbc.util.reflect.Methods;
033
034/**
035 * @author Paul Ferraro
036 * @param <D> 
037 * @param <S> 
038 */
039@SuppressWarnings("nls")
040public class ResultSetInvocationHandler<Z, D extends Database<Z>, S extends Statement> extends InputSinkRegistryInvocationHandler<Z, D, S, ResultSet, ResultSetProxyFactory<Z, D, S>>
041{
042        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");
043        private static final Set<Method> driverWriteMethodSet = Methods.findMethods(ResultSet.class, "absolute", "afterLast", "beforeFirst", "cancelRowUpdates", "clearWarnings", "first", "last", "moveTo(Current|Insert)Row", "next", "previous", "relative", "setFetchDirection", "setFetchSize");
044        private static final Set<Method> transactionalWriteMethodSet = Methods.findMethods(ResultSet.class, "(delete|insert|update)Row");
045        private static final Set<Method> getArrayMethodSet = Methods.findMethods(ResultSet.class, "getArray");
046        private static final Set<Method> getBlobMethodSet = Methods.findMethods(ResultSet.class, "getBlob");
047        private static final Set<Method> getClobMethodSet = Methods.findMethods(ResultSet.class, "getClob");
048        private static final Set<Method> getNClobMethodSet = Methods.findMethods(ResultSet.class, "getNClob");
049        private static final Set<Method> getRefMethodSet = Methods.findMethods(ResultSet.class, "getRef");
050        private static final Set<Method> getSQLXMLMethodSet = Methods.findMethods(ResultSet.class, "getSQLXML");
051
052        private static final Method closeMethod = Methods.getMethod(ResultSet.class, "close");
053        private static final Method getStatementMethod = Methods.getMethod(ResultSet.class, "getStatement");
054        
055        /**
056         * @param factory a factory for creating result set proxies
057         */
058        public ResultSetInvocationHandler(ResultSetProxyFactory<Z, D, S> factory)
059        {
060                super(ResultSet.class, factory, getStatementMethod);
061        }
062
063        @Override
064        protected ProxyFactoryFactory<Z, D, ResultSet, SQLException, ?, ? extends Exception> getProxyFactoryFactory(ResultSet object, Method method, Object... parameters) throws SQLException
065        {
066                if (getArrayMethodSet.contains(method))
067                {
068                        return new ArrayProxyFactoryFactory<Z, D, ResultSet>(this.getProxyFactory().locatorsUpdateCopy());
069                }
070                if (getBlobMethodSet.contains(method))
071                {
072                        return new BlobProxyFactoryFactory<Z, D, ResultSet>(this.getProxyFactory().locatorsUpdateCopy());
073                }
074                if (getClobMethodSet.contains(method))
075                {
076                        return new ClobProxyFactoryFactory<Z, D, ResultSet, Clob>(Clob.class, this.getProxyFactory().locatorsUpdateCopy());
077                }
078                if (getNClobMethodSet.contains(method))
079                {
080                        return new ClobProxyFactoryFactory<Z, D, ResultSet, NClob>(NClob.class, this.getProxyFactory().locatorsUpdateCopy());
081                }
082                if (getRefMethodSet.contains(method))
083                {
084                        return new RefProxyFactoryFactory<Z, D, ResultSet>(this.getProxyFactory().locatorsUpdateCopy());
085                }
086                if (getSQLXMLMethodSet.contains(method))
087                {
088                        return new SQLXMLProxyFactoryFactory<Z, D, ResultSet>(this.getProxyFactory().locatorsUpdateCopy());
089                }
090                
091                return super.getProxyFactoryFactory(object, method, parameters);
092        }
093
094        @Override
095        protected InvocationStrategy getInvocationStrategy(ResultSet resultSet, Method method, Object... parameters) throws SQLException
096        {
097                if (driverReadMethodSet.contains(method))
098                {
099                        return InvocationStrategies.INVOKE_ON_ANY;
100                }
101                
102                if (driverWriteMethodSet.contains(method) || method.equals(closeMethod))
103                {
104                        return InvocationStrategies.INVOKE_ON_EXISTING;
105                }
106                
107                if (transactionalWriteMethodSet.contains(method))
108                {
109                        return this.getProxyFactory().getTransactionContext().start(InvocationStrategies.TRANSACTION_INVOKE_ON_ALL, this.getProxyFactory().getParentProxy().getConnection());
110                }
111                
112                if (isGetMethod(method))
113                {
114                        return InvocationStrategies.INVOKE_ON_ANY;
115                }
116                
117                if (isUpdateMethod(method))
118                {
119                        return InvocationStrategies.INVOKE_ON_EXISTING;
120                }
121                
122                return super.getInvocationStrategy(resultSet, method, parameters);
123        }
124
125        @Override
126        protected <R> Invoker<Z, D, ResultSet, R, SQLException> getInvoker(ResultSet results, final Method method, final Object... parameters) throws SQLException
127        {
128                if (isUpdateMethod(method) && (parameters.length > 1))
129                {
130                        return this.getInvoker(method.getParameterTypes()[1], 1, results, method, parameters);
131                }
132                
133                return super.getInvoker(results, method, parameters);
134        }
135
136        @Override
137        protected <R> void postInvoke(Invoker<Z, D, ResultSet, R, SQLException> invoker, ResultSet results, Method method, Object... parameters)
138        {
139                if (method.equals(closeMethod))
140                {
141                        this.getProxyFactory().remove();
142                }
143                
144                if (driverWriteMethodSet.contains(method) || isUpdateMethod(method))
145                {
146                        this.getProxyFactory().addInvoker(invoker);
147                }
148        }
149        
150        private static boolean isGetMethod(Method method)
151        {
152                Class<?>[] types = method.getParameterTypes();
153                
154                return method.getName().startsWith("get") && (types != null) && (types.length > 0) && (types[0].equals(String.class) || types[0].equals(Integer.TYPE));
155        }
156        
157        private static boolean isUpdateMethod(Method method)
158        {
159                Class<?>[] types = method.getParameterTypes();
160                
161                return method.getName().startsWith("update") && (types != null) && (types.length > 0) && (types[0].equals(String.class) || types[0].equals(Integer.TYPE));
162        }
163}