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}