001/* 002 * HA-JDBC: High-Availability JDBC 003 * Copyright (c) 2004-2008 Paul Ferraro 004 * 005 * This library is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Lesser General Public License as published by the 007 * Free Software Foundation; either version 2.1 of the License, or (at your 008 * option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, but WITHOUT 011 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 012 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 013 * for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License 016 * along with this library; if not, write to the Free Software Foundation, 017 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018 * 019 * Contact: ferraro@users.sourceforge.net 020 */ 021package net.sf.hajdbc.sql.xa; 022 023import java.lang.reflect.Method; 024import java.util.Map; 025import java.util.Set; 026import java.util.concurrent.ConcurrentHashMap; 027import java.util.concurrent.ConcurrentMap; 028import java.util.concurrent.locks.Lock; 029 030import javax.sql.XAConnection; 031import javax.sql.XADataSource; 032import javax.transaction.xa.XAResource; 033import javax.transaction.xa.Xid; 034 035import net.sf.hajdbc.Database; 036import net.sf.hajdbc.LockManager; 037import net.sf.hajdbc.sql.AbstractChildInvocationHandler; 038import net.sf.hajdbc.sql.DatabaseWriteInvocationStrategy; 039import net.sf.hajdbc.sql.DriverReadInvocationStrategy; 040import net.sf.hajdbc.sql.InvocationStrategy; 041import net.sf.hajdbc.sql.Invoker; 042import net.sf.hajdbc.sql.SQLProxy; 043import net.sf.hajdbc.util.reflect.Methods; 044 045/** 046 * @author Paul Ferraro 047 * 048 */ 049@SuppressWarnings("nls") 050public class XAResourceInvocationHandler extends AbstractChildInvocationHandler<XADataSource, XAConnection, XAResource> 051{ 052 private static final Set<Method> driverReadMethodSet = Methods.findMethods(XAResource.class, "getTransactionTimeout", "isSameRM"); 053 private static final Set<Method> databaseWriteMethodSet = Methods.findMethods(XAResource.class, "setTransactionTimeout"); 054 private static final Set<Method> intraTransactionMethodSet = Methods.findMethods(XAResource.class, "end", "prepare", "recover"); 055 private static final Method startMethod = Methods.getMethod(XAResource.class, "start", Xid.class, Integer.TYPE); 056 private static final Set<Method> endTransactionMethodSet = Methods.findMethods(XAResource.class, "commit", "rollback", "forget"); 057 058 // Xids are global - so store in static variable 059 private static ConcurrentMap<Xid, Lock> lockMap = new ConcurrentHashMap<Xid, Lock>(); 060 061 /** 062 * @param connection 063 * @param proxy 064 * @param invoker 065 * @param objectMap 066 * @throws Exception 067 */ 068 protected XAResourceInvocationHandler(XAConnection connection, SQLProxy<XADataSource, XAConnection> proxy, Invoker<XADataSource, XAConnection, XAResource> invoker, Map<Database<XADataSource>, XAResource> objectMap) throws Exception 069 { 070 super(connection, proxy, invoker, XAResource.class, objectMap); 071 } 072 073 /** 074 * @see net.sf.hajdbc.sql.AbstractInvocationHandler#getInvocationStrategy(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) 075 */ 076 @Override 077 protected InvocationStrategy<XADataSource, XAResource, ?> getInvocationStrategy(XAResource resource, Method method, Object[] parameters) throws Exception 078 { 079 if (driverReadMethodSet.contains(method)) 080 { 081 return new DriverReadInvocationStrategy<XADataSource, XAResource, Object>(); 082 } 083 084 if (databaseWriteMethodSet.contains(method)) 085 { 086 return new DatabaseWriteInvocationStrategy<XADataSource, XAResource, Object>(this.cluster.getNonTransactionalExecutor()); 087 } 088 089 if (method.equals(startMethod) || intraTransactionMethodSet.contains(method) || endTransactionMethodSet.contains(method)) 090 { 091 final InvocationStrategy<XADataSource, XAResource, Object> strategy = new DatabaseWriteInvocationStrategy<XADataSource, XAResource, Object>(this.cluster.getTransactionalExecutor()); 092 093 if (method.equals(startMethod)) 094 { 095 Xid xid = (Xid) parameters[0]; 096 097 final Lock lock = this.cluster.getLockManager().readLock(LockManager.GLOBAL); 098 099 // Lock may already exist if we're resuming a suspended transaction 100 Lock existingLock = lockMap.putIfAbsent(xid, lock); 101 102 if (existingLock == null) 103 { 104 return new InvocationStrategy<XADataSource, XAResource, Object>() 105 { 106 @Override 107 public Object invoke(SQLProxy<XADataSource, XAResource> proxy, Invoker<XADataSource, XAResource, Object> invoker) throws Exception 108 { 109 lock.lock(); 110 111 return strategy.invoke(proxy, invoker); 112 } 113 }; 114 } 115 } 116 117 if (endTransactionMethodSet.contains(method)) 118 { 119 final Lock lock = lockMap.remove(parameters[0]); 120 121 return new InvocationStrategy<XADataSource, XAResource, Object>() 122 { 123 @Override 124 public Object invoke(SQLProxy<XADataSource, XAResource> proxy, Invoker<XADataSource, XAResource, Object> invoker) throws Exception 125 { 126 try 127 { 128 return strategy.invoke(proxy, invoker); 129 } 130 finally 131 { 132 if (lock != null) 133 { 134 lock.unlock(); 135 } 136 } 137 } 138 }; 139 } 140 141 return strategy; 142 } 143 144 return super.getInvocationStrategy(resource, method, parameters); 145 } 146 147 /** 148 * @see net.sf.hajdbc.sql.AbstractChildInvocationHandler#close(java.lang.Object, java.lang.Object) 149 */ 150 @Override 151 protected void close(XAConnection connection, XAResource resource) 152 { 153 // Do nothing 154 } 155 156 /** 157 * @see net.sf.hajdbc.sql.AbstractInvocationHandler#isRecordable(java.lang.reflect.Method) 158 */ 159 @Override 160 protected boolean isRecordable(Method method) 161 { 162 return databaseWriteMethodSet.contains(method); 163 } 164}