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}