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.sql.Connection;
021import java.sql.SQLException;
022import java.util.SortedMap;
023import java.util.concurrent.locks.Lock;
024
025import net.sf.hajdbc.Database;
026import net.sf.hajdbc.DatabaseCluster;
027import net.sf.hajdbc.ExceptionType;
028import net.sf.hajdbc.durability.Durability;
029import net.sf.hajdbc.invocation.InvocationStrategy;
030import net.sf.hajdbc.invocation.Invoker;
031import net.sf.hajdbc.tx.TransactionIdentifierFactory;
032
033/**
034 * @author Paul Ferraro
035 * @param <Z>
036 * @param <D>
037 */
038public class LocalTransactionContext<Z, D extends Database<Z>> implements TransactionContext<Z, D>
039{
040        final Durability<Z, D> durability;
041        private final Lock lock;
042        private final TransactionIdentifierFactory<? extends Object> transactionIdFactory;
043        volatile Object transactionId;
044        
045        /**
046         * @param cluster
047         */
048        public LocalTransactionContext(DatabaseCluster<Z, D> cluster)
049        {
050                this.lock = cluster.getLockManager().readLock(null);
051                this.durability = cluster.getDurability();
052                this.transactionIdFactory = cluster.getTransactionIdentifierFactory();
053        }
054        
055        /**
056         * {@inheritDoc}
057         * @see net.sf.hajdbc.sql.TransactionContext#start(net.sf.hajdbc.invocation.InvocationStrategy, java.sql.Connection)
058         */
059        @Override
060        public InvocationStrategy start(final InvocationStrategy strategy, final Connection connection) throws SQLException
061        {
062                if (this.transactionId != null) return strategy;
063                
064                if (connection.getAutoCommit())
065                {
066                        return new InvocationStrategy()
067                        {
068                                @Override
069                                public <ZZ, DD extends Database<ZZ>, T, R, E extends Exception> SortedMap<DD, R> invoke(ProxyFactory<ZZ, DD, T, E> proxy, Invoker<ZZ, DD, T, R, E> invoker) throws E
070                                {
071                                        LocalTransactionContext.this.lock();
072                                        
073                                        try
074                                        {
075                                                InvocationStrategy durabilityStrategy = LocalTransactionContext.this.durability.getInvocationStrategy(strategy, Durability.Phase.COMMIT, LocalTransactionContext.this.transactionId);
076                                                
077                                                return durabilityStrategy.invoke(proxy, invoker);
078                                        }
079                                        finally
080                                        {
081                                                LocalTransactionContext.this.unlock();
082                                        }
083                                }
084                        };
085                }
086                
087                return new InvocationStrategy()
088                {
089                        @Override
090                        public <ZZ, DD extends Database<ZZ>, T, R, E extends Exception> SortedMap<DD, R> invoke(ProxyFactory<ZZ, DD, T, E> proxy, Invoker<ZZ, DD, T, R, E> invoker) throws E
091                        {
092                                LocalTransactionContext.this.lock();
093                                
094                                try
095                                {
096                                        return strategy.invoke(proxy, invoker);
097                                }
098                                catch (Throwable e)
099                                {
100                                        throw proxy.getExceptionFactory().createException(e);
101                                } 
102                                finally 
103                                {
104                                        LocalTransactionContext.this.unlock();
105                                }
106                        }
107                };
108        }
109        
110        /**
111         * {@inheritDoc}
112         * @see net.sf.hajdbc.sql.TransactionContext#start(net.sf.hajdbc.invocation.Invoker, java.sql.Connection)
113         */
114        @Override
115        public <T, R> Invoker<Z, D, T, R, SQLException> start(final Invoker<Z, D, T, R, SQLException> invoker, Connection connection) throws SQLException
116        {
117                if ((this.transactionId == null) || !connection.getAutoCommit()) return invoker;
118
119                return new Invoker<Z, D, T, R, SQLException>()
120                {
121                        @Override
122                        public R invoke(D database, T object) throws SQLException
123                        {
124                                return LocalTransactionContext.this.durability.getInvoker(invoker, Durability.Phase.COMMIT, LocalTransactionContext.this.transactionId, ExceptionType.SQL.<SQLException>getExceptionFactory()).invoke(database, object);
125                        }
126                };
127        }
128
129        /**
130         * {@inheritDoc}
131         * @see net.sf.hajdbc.sql.TransactionContext#end(net.sf.hajdbc.invocation.InvocationStrategy, net.sf.hajdbc.durability.Durability.Phase)
132         */
133        @Override
134        public InvocationStrategy end(final InvocationStrategy strategy, final Durability.Phase phase)
135        {
136                if (this.transactionId == null) return strategy;
137
138                return new InvocationStrategy()
139                {
140                        @Override
141                        public <ZZ, DD extends Database<ZZ>, T, R, E extends Exception> SortedMap<DD, R> invoke(ProxyFactory<ZZ, DD, T, E> proxy, Invoker<ZZ, DD, T, R, E> invoker) throws E
142                        {
143                                InvocationStrategy durabilityStrategy = LocalTransactionContext.this.durability.getInvocationStrategy(strategy, phase, LocalTransactionContext.this.transactionId);
144                                
145                                try
146                                {
147                                        return durabilityStrategy.invoke(proxy, invoker);
148                                }
149                                finally
150                                {
151                                        LocalTransactionContext.this.unlock();
152                                }
153                        }
154                };
155        }
156
157        /**
158         * {@inheritDoc}
159         * @see net.sf.hajdbc.sql.TransactionContext#end(net.sf.hajdbc.invocation.Invoker, net.sf.hajdbc.durability.Durability.Phase)
160         */
161        @Override
162        public <T, R> Invoker<Z, D, T, R, SQLException> end(final Invoker<Z, D, T, R, SQLException> invoker, Durability.Phase phase)
163        {
164                if (this.transactionId == null) return invoker;
165
166                return this.durability.getInvoker(invoker, phase, this.transactionId, ExceptionType.SQL.<SQLException>getExceptionFactory());
167        }
168
169        /**
170         * @see net.sf.hajdbc.sql.TransactionContext#close()
171         */
172        @Override
173        public void close()
174        {
175                // Tsk, tsk... User neglected to commit/rollback transaction
176                if (this.transactionId != null)
177                {
178                        this.unlock();
179                }
180        }
181
182        void lock()
183        {
184                this.lock.lock();
185                this.transactionId = this.transactionIdFactory.createTransactionIdentifier();
186        }
187        
188        void unlock()
189        {
190                this.lock.unlock();
191                this.transactionId = null;
192        }
193}