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.lang.reflect.Proxy;
022import java.sql.Clob;
023import java.sql.Connection;
024import java.sql.NClob;
025import java.sql.SQLException;
026import java.sql.Savepoint;
027import java.util.Arrays;
028import java.util.HashSet;
029import java.util.Set;
030
031import net.sf.hajdbc.Database;
032import net.sf.hajdbc.durability.Durability;
033import net.sf.hajdbc.invocation.InvocationStrategies;
034import net.sf.hajdbc.invocation.InvocationStrategy;
035import net.sf.hajdbc.invocation.Invoker;
036import net.sf.hajdbc.util.StaticRegistry;
037import net.sf.hajdbc.util.reflect.Methods;
038
039/**
040 * @author Paul Ferraro
041 * @param <D> 
042 * @param <P> 
043 */
044public class ConnectionInvocationHandler<Z, D extends Database<Z>, P> extends ChildInvocationHandler<Z, D, P, SQLException, Connection, SQLException, ConnectionProxyFactory<Z, D, P>>
045{
046        private static final Set<Method> driverReadMethodSet = Methods.findMethods(Connection.class, "createStruct", "getAutoCommit", "getCatalog", "getClientInfo", "getHoldability", "getNetworkTimeout", "getSchema", "getTransactionIsolation", "getTypeMap", "getWarnings", "isClosed", "isCloseOnCompletion", "isReadOnly", "nativeSQL");
047        private static final Set<Method> databaseReadMethodSet = Methods.findMethods(Connection.class, "isValid");
048        private static final Set<Method> driverWriterMethodSet = Methods.findMethods(Connection.class, "abort", "clearWarnings", "closeOnCompletion", "setClientInfo", "setHoldability", "setNetworkTimeout", "setSchema", "setTypeMap");
049        private static final Set<Method> createStatementMethodSet = Methods.findMethods(Connection.class, "createStatement");
050        private static final Set<Method> prepareStatementMethodSet = Methods.findMethods(Connection.class, "prepareStatement");
051        private static final Set<Method> prepareCallMethodSet = Methods.findMethods(Connection.class, "prepareCall");
052        private static final Set<Method> setSavepointMethodSet = Methods.findMethods(Connection.class, "setSavepoint");
053
054        private static final Method setAutoCommitMethod = Methods.getMethod(Connection.class, "setAutoCommit", Boolean.TYPE);
055        private static final Method commitMethod = Methods.getMethod(Connection.class, "commit");
056        private static final Method rollbackMethod = Methods.getMethod(Connection.class, "rollback");
057        private static final Method getMetaDataMethod = Methods.getMethod(Connection.class, "getMetaData");
058        private static final Method releaseSavepointMethod = Methods.getMethod(Connection.class, "releaseSavepoint", Savepoint.class);
059        private static final Method rollbackSavepointMethod = Methods.getMethod(Connection.class, "rollback", Savepoint.class);
060        private static final Method closeMethod = Methods.getMethod(Connection.class, "close");
061        private static final Method createArrayMethod = Methods.getMethod(Connection.class, "createArrayOf", String.class, Object[].class);
062        private static final Method createBlobMethod = Methods.getMethod(Connection.class, "createBlob");
063        private static final Method createClobMethod = Methods.getMethod(Connection.class, "createClob");
064        private static final Method createNClobMethod = Methods.getMethod(Connection.class, "createNClob");
065        private static final Method createSQLXMLMethod = Methods.getMethod(Connection.class, "createSQLXML");
066        
067        private static final Set<Method> endTransactionMethodSet = new HashSet<Method>(Arrays.asList(commitMethod, rollbackMethod, setAutoCommitMethod));
068        private static final Set<Method> createLocatorMethodSet = new HashSet<Method>(Arrays.asList(createBlobMethod, createClobMethod, createNClobMethod, createSQLXMLMethod));
069        
070        private static final StaticRegistry<Method, Durability.Phase> phaseRegistry = new DurabilityPhaseRegistry(Arrays.asList(commitMethod, setAutoCommitMethod), Arrays.asList(rollbackMethod));
071        
072        /**
073         * Constructs a new ConnectionInvocationHandler
074         * @param proxyFactory a factory for creating connection proxies
075         */
076        public ConnectionInvocationHandler(ConnectionProxyFactory<Z, D, P> proxyFactory)
077        {
078                super(Connection.class, proxyFactory, null);
079        }
080        
081        @Override
082        protected ProxyFactoryFactory<Z, D, Connection, SQLException, ?, ? extends Exception> getProxyFactoryFactory(Connection connection, Method method, Object... parameters) throws SQLException
083        {
084                if (createStatementMethodSet.contains(method))
085                {
086                        return new StatementProxyFactoryFactory<Z, D>(this.getProxyFactory().getTransactionContext());
087                }
088                if (prepareStatementMethodSet.contains(method))
089                {
090                        String sql = (String) parameters[0];
091                        return new PreparedStatementProxyFactoryFactory<Z, D>(this.getProxyFactory().getTransactionContext(), this.getProxyFactory().extractLocks(sql), this.getProxyFactory().isSelectForUpdate(sql));
092                }
093                if (prepareCallMethodSet.contains(method))
094                {
095                        String sql = (String) parameters[0];
096                        return new CallableStatementProxyFactoryFactory<Z, D>(this.getProxyFactory().getTransactionContext(), this.getProxyFactory().extractLocks(sql));
097                }
098                
099                if (setSavepointMethodSet.contains(method))
100                {
101                        return new SavepointProxyFactoryFactory<Z, D>();
102                }
103                
104                if (method.equals(getMetaDataMethod))
105                {
106                        return new DatabaseMetaDataProxyFactoryFactory<Z, D>();
107                }
108                
109                if (method.equals(createArrayMethod))
110                {
111                        return new ArrayProxyFactoryFactory<Z, D, Connection>(this.getProxyFactory().locatorsUpdateCopy());
112                }
113                if (method.equals(createBlobMethod))
114                {
115                        return new BlobProxyFactoryFactory<Z, D, Connection>(this.getProxyFactory().locatorsUpdateCopy());
116                }
117                if (method.equals(createClobMethod))
118                {
119                        return new ClobProxyFactoryFactory<Z, D, Connection, Clob>(Clob.class, this.getProxyFactory().locatorsUpdateCopy());
120                }
121                if (method.equals(createNClobMethod))
122                {
123                        return new ClobProxyFactoryFactory<Z, D, Connection, NClob>(NClob.class, this.getProxyFactory().locatorsUpdateCopy());
124                }
125                if (method.equals(createSQLXMLMethod))
126                {
127                        return new SQLXMLProxyFactoryFactory<Z, D, Connection>(this.getProxyFactory().locatorsUpdateCopy());
128                }
129                
130                return super.getProxyFactoryFactory(connection, method, parameters);
131        }
132
133        /**
134         * {@inheritDoc}
135         */
136        @Override
137        protected InvocationStrategy getInvocationStrategy(Connection connection, Method method, Object... parameters) throws SQLException
138        {
139                if (driverReadMethodSet.contains(method))
140                {
141                        return InvocationStrategies.INVOKE_ON_ANY;
142                }
143                
144                if (databaseReadMethodSet.contains(method) || method.equals(getMetaDataMethod))
145                {
146                        return InvocationStrategies.INVOKE_ON_NEXT;
147                }
148                
149                if (driverWriterMethodSet.contains(method) || method.equals(closeMethod) || createStatementMethodSet.contains(method))
150                {
151                        return InvocationStrategies.INVOKE_ON_EXISTING;
152                }
153                
154                if (prepareStatementMethodSet.contains(method) || prepareCallMethodSet.contains(method) || createLocatorMethodSet.contains(method))
155                {
156                        return InvocationStrategies.INVOKE_ON_ALL;
157                }
158                
159                if (endTransactionMethodSet.contains(method))
160                {
161                        return this.getProxyFactory().getTransactionContext().end(InvocationStrategies.END_TRANSACTION_INVOKE_ON_ALL, phaseRegistry.get(method));
162                }
163                
164                if (method.equals(rollbackSavepointMethod) || method.equals(releaseSavepointMethod))
165                {
166                        return InvocationStrategies.END_TRANSACTION_INVOKE_ON_ALL;
167                }
168                
169                if (setSavepointMethodSet.contains(method))
170                {
171                        return InvocationStrategies.TRANSACTION_INVOKE_ON_ALL;
172                }
173                
174                return super.getInvocationStrategy(connection, method, parameters);
175        }
176
177        /**
178         * {@inheritDoc}
179         */
180        @Override
181        protected <R> Invoker<Z, D, Connection, R, SQLException> getInvoker(Connection connection, Method method, Object... parameters) throws SQLException
182        {
183                if (method.equals(releaseSavepointMethod) || method.equals(rollbackSavepointMethod))
184                {
185                        return this.getInvoker(Savepoint.class, 0, connection, method, parameters);
186                }
187                
188                if (prepareStatementMethodSet.contains(method) || prepareCallMethodSet.contains(method))
189                {
190                        parameters[0] = this.getProxyFactory().evaluate((String) parameters[0]);
191                }
192
193                Invoker<Z, D, Connection, R, SQLException> invoker = super.getInvoker(connection, method, parameters);
194                
195                if (endTransactionMethodSet.contains(method))
196                {
197                        return this.getProxyFactory().getTransactionContext().end(invoker, phaseRegistry.get(method));
198                }
199                
200                return invoker;
201        }
202
203        @Override
204        protected <R> void postInvoke(Invoker<Z, D, Connection, R, SQLException> invoker, Connection proxy, Method method, Object... parameters)
205        {
206                if (driverWriterMethodSet.contains(method) || method.equals(setAutoCommitMethod))
207                {
208                        this.getProxyFactory().record(invoker);
209                }
210                else if (method.equals(closeMethod))
211                {
212                        this.getProxyFactory().getTransactionContext().close();
213                        this.getProxyFactory().remove();
214                }
215                else if (method.equals(releaseSavepointMethod))
216                {
217                        SavepointInvocationHandler<Z, D> handler = (SavepointInvocationHandler<Z, D>) Proxy.getInvocationHandler(parameters[0]);
218                        this.getProxyFactory().removeChild(handler.getProxyFactory());
219                }
220        }
221}