001/* 
002 * JKNIV, whinstone one contract to access your database.
003 * 
004 * Copyright (C) 2017, the original author or authors.
005 *
006 * This library is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 2.1 of the License.
010 * 
011 * This library is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 * 
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with this library; if not, write to the Free Software Foundation, Inc., 
018 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
019 */
020package net.sf.jkniv.whinstone.jdbc.transaction;
021
022import java.sql.SQLException;
023
024import org.slf4j.Logger;
025import org.slf4j.LoggerFactory;
026
027import net.sf.jkniv.asserts.Assertable;
028import net.sf.jkniv.asserts.AssertsFactory;
029import net.sf.jkniv.whinstone.ConnectionAdapter;
030import net.sf.jkniv.whinstone.transaction.TransactionContext;
031import net.sf.jkniv.whinstone.transaction.TransactionException;
032import net.sf.jkniv.whinstone.transaction.TransactionScope;
033import net.sf.jkniv.whinstone.transaction.TransactionSessions;
034import net.sf.jkniv.whinstone.transaction.TransactionStatus;
035import net.sf.jkniv.whinstone.transaction.Transactional;
036
037public abstract class AbstractTransaction implements Transactional
038{
039    protected static final Logger   logger   = LoggerFactory.getLogger(AbstractTransaction.class);
040    private static final Assertable NOT_NULL = AssertsFactory.getNotNull();
041    private boolean                           wasAutoCommit;
042    private TransactionStatus                 status;
043    protected final TransactionScope          transactionScope;
044    protected final String                    contextName;
045    protected final ConnectionAdapter         connAdapter;
046    
047    /**
048     * Open new connection with default connection properties from driver version.
049     * @return new Connection
050     */
051    public abstract ConnectionAdapter open();
052    
053    public AbstractTransaction(String contextName, ConnectionAdapter connAdapter, TransactionScope transactionScope)
054    {
055        NOT_NULL.verify(contextName, connAdapter, transactionScope);
056        this.contextName = contextName;
057        this.connAdapter = connAdapter;
058        this.status = TransactionStatus.NO_TRANSACTION;
059        this.transactionScope = transactionScope;// FIXME tx implements different scopes transactions
060        try
061        {
062            this.wasAutoCommit = connAdapter.isAutoCommit();
063        }
064        catch (SQLException e)
065        {
066            throw new TransactionException("Could not create transaction for " + toString(), e);
067        }
068    }
069    
070    @Override
071    public final void begin() //throws NotSupportedException, SystemException
072    {
073        if (logger.isTraceEnabled())
074            logger.trace("Creating new transaction [{}] with scope [{}]", this.contextName, transactionScope);
075        TransactionContext transactionContext = getTransactionContext();
076        
077        if (transactionContext.isActive())
078            throw new TransactionException(
079                    "Transaction is active. Local transaction does not support nested transactions");
080        
081        this.status = TransactionStatus.PREPARING;
082        //ConnectionAdapter conn = transactionContext.getConnection();
083        try
084        {
085            //this.wasAutoCommit = conn.isAutoCommit();
086            if (wasAutoCommit)
087            {
088                connAdapter.autoCommitOff();
089                this.status = TransactionStatus.ACTIVE;
090                if (logger.isDebugEnabled())
091                    logger.debug("Transaction [{}] with scope [{}] ACTIVE", this.contextName, transactionScope);
092            }
093            else
094                this.status = TransactionStatus.ACTIVE;
095        }
096        catch (SQLException sqle)
097        {
098            //TransactionContext.setTransactionStatus(TransactionStatus.);
099            throw new TransactionException("Cannot begin transaction", sqle);
100        }
101        finally
102        {
103            
104        }
105    }
106    
107    @Override
108    public final void commit() //throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException
109    {
110        if (logger.isTraceEnabled())
111            logger.trace("Committing transaction [{}] with scope [{}]", this.contextName, transactionScope);
112        
113        TransactionContext transactionContext = getTransactionContext();
114        //transactionContext.setStatus(TransactionStatus.COMMITTING);
115        //ConnectionAdapter conn = transactionContext.getConnection();
116        try
117        {
118            if (!transactionContext.isActive())
119                throw new TransactionException("Does not have transaction beginning. Autocommit is true!");
120            
121            connAdapter.commit();
122            this.status = TransactionStatus.COMMITTED;
123            if (logger.isDebugEnabled())
124                logger.debug("Transaction [{}] with scope [{}] COMMITTED", this.contextName, transactionScope);
125            
126        }
127        catch (SQLException sqle)
128        {
129            this.status = TransactionStatus.MARKED_ROLLBACK;
130            throw new TransactionException("Cannot commit transaction", sqle);
131        }
132        finally
133        {
134            try
135            {
136                if (wasAutoCommit)
137                    connAdapter.autoCommitOn();
138            }
139            catch (SQLException sqle)
140            {
141                logger.warn("Cannot change connection to autocommit=true. Reason: " + sqle.getMessage());
142            }
143            TransactionSessions.close(this.contextName);
144        }
145    }
146    
147    @Override
148    public final TransactionStatus getStatus() //throws SystemException
149    {
150        return this.status;
151        //return getTransactionContext().getStatus();
152    }
153    
154    @Override
155    public final void rollback()
156    {
157        if (logger.isTraceEnabled())
158            logger.trace("Rolling back transaction [{}] with scope [{}]", this.contextName, transactionScope);
159        
160        TransactionContext transactionContext = getTransactionContext();
161        //ConnectionAdapter conn = transactionContext.getConnection();
162//        if (conn == null)
163//        {
164//            logger.warn("Try to rollback transaction at context [{}] without connection!", this.contextName);
165//            return;
166//        }
167        try
168        {
169            connAdapter.rollback();
170            this.status = TransactionStatus.ROLLEDBACK;
171            if (logger.isDebugEnabled())
172                logger.debug("Transaction [{}] with scope [{}] ROLLBACK", this.contextName, transactionScope);
173        }
174        catch (SQLException sqle)
175        {
176            this.status = TransactionStatus.MARKED_ROLLBACK;
177            logger.warn("Fail to try rollback transaction. Reason: " + sqle.getLocalizedMessage());
178            throw new TransactionException("Cannot rollback transaction", sqle);
179        }
180        finally
181        {
182            try
183            {
184                if (wasAutoCommit && !connAdapter.isAutoCommit())
185                    connAdapter.autoCommitOn();
186            }
187            catch (SQLException sqle)
188            {
189                logger.warn("Cannot back auto-commit connection to TRUE. Reason: " + sqle.getMessage());
190            }
191            TransactionSessions.close(this.contextName);
192        }
193    }
194    
195    private TransactionContext getTransactionContext()
196    {
197        TransactionContext transactionContext = TransactionSessions.get(this.contextName);
198        if (transactionContext == null)
199        {
200            //ConnectionAdapter conn = open();
201            transactionContext = TransactionSessions.set(this.contextName, this, connAdapter);
202        }
203        return transactionContext;
204    }
205    
206
207    @Override
208    public String toString()
209    {
210        return "AbstractTransaction [contextName=" + contextName + ", status=" + status + ", transactionScope="
211                + transactionScope + ", connAdapter=" + connAdapter + "]";
212    }
213    
214    
215    //    @Override
216    //    public final void setTransactionTimeout(int seconds) throws TransactionException
217    //    {
218    //    }
219    
220    /*
221    public Transaction getTransaction() //throws SystemException
222    {
223        
224        return null;
225    }
226    
227    public void resume(Transaction transaction)//        throws InvalidTransactionException, IllegalStateException, SystemException
228    {
229        
230    }
231    
232    public void setRollbackOnly() //throws IllegalStateException, SystemException
233    {
234        
235    }
236    
237    public void setTransactionTimeout(int timeout) //throws SystemException
238    {
239        
240    }
241    
242    public Transaction suspend() //throws SystemException
243    {
244        return null;
245    }
246    */
247    
248}