001/*
002 * HA-JDBC: High-Availability JDBC
003 * Copyright (C) 2013  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.SQLException;
023import java.sql.Wrapper;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030import java.util.SortedMap;
031
032import net.sf.hajdbc.Database;
033import net.sf.hajdbc.DatabaseCluster;
034import net.sf.hajdbc.ExceptionFactory;
035import net.sf.hajdbc.Messages;
036import net.sf.hajdbc.invocation.AllResultsCollector;
037import net.sf.hajdbc.invocation.InvocationStrategies;
038import net.sf.hajdbc.invocation.InvocationStrategy;
039import net.sf.hajdbc.invocation.Invoker;
040import net.sf.hajdbc.invocation.SimpleInvoker;
041import net.sf.hajdbc.logging.Level;
042import net.sf.hajdbc.logging.Logger;
043import net.sf.hajdbc.logging.LoggerFactory;
044import net.sf.hajdbc.sql.serial.SerialLocatorFactories;
045import net.sf.hajdbc.sql.serial.SerialLocatorFactory;
046import net.sf.hajdbc.util.reflect.Methods;
047
048/**
049 * 
050 * @author Paul Ferraro
051 */
052public class AbstractInvocationHandler<Z, D extends Database<Z>, T, E extends Exception, F extends ProxyFactory<Z, D, T, E>> implements InvocationHandler<Z, D, T, E, F>
053{
054        private static final Method equalsMethod = Methods.getMethod(Object.class, "equals", Object.class);
055        private static final Method hashCodeMethod = Methods.getMethod(Object.class, "hashCode");
056        private static final Method toStringMethod = Methods.getMethod(Object.class, "toString");
057        private static final Set<Method> wrapperMethods = Methods.findMethods(Wrapper.class, "isWrapperFor", "unwrap");
058        
059        protected final Logger logger = LoggerFactory.getLogger(this.getClass());
060        private final Class<T> proxyClass;
061        private final F proxyFactory;
062        
063        protected AbstractInvocationHandler(Class<T> targetClass, F proxyFactory)
064        {
065                this.proxyClass = targetClass;
066                this.proxyFactory = proxyFactory;
067        }
068        
069        @Override
070        public F getProxyFactory()
071        {
072                return this.proxyFactory;
073        }
074        
075        @Override
076        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
077        {
078                DatabaseCluster<Z, D> cluster = this.proxyFactory.getDatabaseCluster();
079                
080                if (!cluster.isActive())
081                {
082                        throw new SQLException(Messages.CLUSTER_NOT_ACTIVE.getMessage(cluster));
083                }
084                
085                return this.invokeOnProxy(this.proxyClass.cast(proxy), method, args);
086        }
087
088        private <R> R invokeOnProxy(T proxy, Method method, Object... parameters) throws E
089        {
090                InvocationStrategy strategy = this.getInvocationStrategy(proxy, method, parameters);
091
092                Invoker<Z, D, T, R, E> invoker = this.getInvoker(proxy, method, parameters);
093
094                this.logger.log(Level.TRACE, "Invoking {0} using {1}", method, strategy);
095                SortedMap<D, R> results = strategy.invoke(this.proxyFactory, invoker);
096
097                this.postInvoke(invoker, proxy, method, parameters);
098                
099                @SuppressWarnings("unchecked")
100                ProxyFactoryFactory<Z, D, T, E, R, ? extends Exception> factory = (ProxyFactoryFactory<Z, D, T, E, R, ? extends Exception>) this.getProxyFactoryFactory(proxy, method, parameters);
101                InvocationResultFactory<Z, D, R> resultFactory = (factory != null) ? new ProxyInvocationResultFactory<Z, D, T, R, E>(factory, proxy, this.getProxyFactory(), invoker) : new SimpleInvocationResultFactory<Z, D, R>();
102                
103                return this.createResult(resultFactory, results);
104        }
105        
106        /**
107         * @throws E 
108         */
109        protected ProxyFactoryFactory<Z, D, T, E, ?, ? extends Exception> getProxyFactoryFactory(T object, Method method, Object... parameters) throws E
110        {
111                return null;
112        }
113        
114        /**
115         * Returns the appropriate {@link InvocationStrategy} for the specified method.
116         * This implementation detects {@link java.sql.Wrapper} methods; and {@link Object#equals}, {@link Object#hashCode()}, and {@link Object#toString()}.
117         * Default invocation strategy is {@link AllResultsCollector}. 
118         * @param object the proxied object
119         * @param method the method to invoke
120         * @param parameters the method invocation parameters
121         * @return an invocation strategy
122         * @throws E
123         */
124        protected InvocationStrategy getInvocationStrategy(T object, Method method, Object... parameters) throws E
125        {
126                if (equalsMethod.equals(method) || hashCodeMethod.equals(method) || toStringMethod.equals(method) || wrapperMethods.contains(method))
127                {
128                        return InvocationStrategies.INVOKE_ON_ANY;
129                }
130
131                return InvocationStrategies.INVOKE_ON_ALL;
132        }
133        
134        /**
135         * Return the appropriate invoker for the specified method.
136         * @param proxy
137         * @param method
138         * @param parameters
139         * @return an invoker
140         * @throws Exception
141         */
142        protected <R> Invoker<Z, D, T, R, E> getInvoker(T proxy, Method method, Object... parameters) throws E
143        {
144                return this.getInvoker(method, parameters);
145        }
146
147        /**
148         * @throws E  
149         */
150        private <R> Invoker<Z, D, T, R, E> getInvoker(Method method, Object... parameters) throws E
151        {
152                return new SimpleInvoker<Z, D, T, R, E>(method, parameters, this.proxyFactory.getExceptionFactory());
153        }
154        
155        protected <R, X> Invoker<Z, D, T, R, E> getInvoker(Class<X> parameterClass, final int parameterIndex, T proxy, final Method method, final Object... parameters) throws E
156        {
157                if (parameterClass.equals(method.getParameterTypes()[parameterIndex]) && !parameterClass.isPrimitive())
158                {
159                        X parameter = parameterClass.cast(parameters[parameterIndex]);
160                        
161                        if (parameter != null)
162                        {
163                                final ExceptionFactory<E> exceptionFactory = this.getProxyFactory().getExceptionFactory();
164                                
165                                // Handle proxy parameter
166                                if (Proxy.isProxyClass(parameter.getClass()) && (Proxy.getInvocationHandler(parameter) instanceof InvocationHandler))
167                                {
168                                        final InvocationHandler<Z, D, X, E, ProxyFactory<Z, D, X, E>> handler = (InvocationHandler<Z, D, X, E, ProxyFactory<Z, D, X, E>>) Proxy.getInvocationHandler(parameter);
169                                        
170                                        return new Invoker<Z, D, T, R, E>()
171                                        {
172                                                @Override
173                                                public R invoke(D database, T object) throws E
174                                                {
175                                                        List<Object> parameterList = new ArrayList<Object>(Arrays.asList(parameters));
176                                                        
177                                                        parameterList.set(parameterIndex, handler.getProxyFactory().get(database));
178                                                        
179                                                        return Methods.<R, E>invoke(method, exceptionFactory, object, parameterList.toArray());
180                                                }
181                                        };
182                                }
183                                
184                                SerialLocatorFactory<X> factory = SerialLocatorFactories.find(parameterClass);
185                                if (factory != null)
186                                {
187                                        try
188                                        {
189                                                // Create a serial form of the parameter, so it can be used by each database
190                                                parameters[parameterIndex] = factory.createSerial(parameter);
191                                        }
192                                        catch (SQLException e)
193                                        {
194                                                throw exceptionFactory.createException(e);
195                                        }
196                                }
197                        }
198                }
199                
200                return this.getInvoker(method, parameters);
201        }
202        
203        private <R> R createResult(InvocationResultFactory<Z, D, R> factory, SortedMap<D, R> resultMap) throws E
204        {
205                DatabaseCluster<Z, D> cluster = this.proxyFactory.getDatabaseCluster();
206                
207                if (resultMap.isEmpty())
208                {
209                        throw this.proxyFactory.getExceptionFactory().createException(Messages.NO_ACTIVE_DATABASES.getMessage(cluster));
210                }
211                
212                Iterator<Map.Entry<D, R>> results = resultMap.entrySet().iterator();
213                R primaryResult = results.next().getValue();
214                
215                while (results.hasNext())
216                {
217                        Map.Entry<D, R> entry = results.next();
218                        R result = entry.getValue();
219                        
220                        if (factory.differs(primaryResult, result))
221                        {
222                                results.remove();
223                                D database = entry.getKey();
224                                
225                                if (cluster.deactivate(database, cluster.getStateManager()))
226                                {
227                                        this.logger.log(Level.ERROR, Messages.DATABASE_INCONSISTENT.getMessage(), database, cluster, primaryResult, result);
228                                }
229                        }
230                }
231                
232                return (primaryResult != null) ? factory.createResult(resultMap) : null;
233        }
234
235        protected <R> void postInvoke(Invoker<Z, D, T, R, E> invoker, T proxy, Method method, Object... parameters)
236        {
237                // Do nothing
238        }
239}