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.transaction;
021
022import java.util.HashMap;
023import java.util.Map;
024
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028import net.sf.jkniv.whinstone.ConnectionAdapter;
029
030/**
031 * Represent a set of transactions for thread, where one thread can use many TransactionContexts.
032 * 
033 * Responsible to release (close) the connection with data source.
034 * 
035 * @author Alisson Gomes
036 * @since 0.6.0
037 */
038public class TransactionSessions
039{
040    private static final Logger                                       LOG       = LoggerFactory
041            .getLogger(TransactionSessions.class);
042    private static final ThreadLocal<Map<String, TransactionContext>> RESOURCES = new ThreadLocal<Map<String, TransactionContext>>(); //"Transactional resources"
043    
044    public static boolean isEmpty(String contextName)
045    {
046        return (get(contextName) == null);
047    }
048    
049    public static TransactionContext get(String contextName)
050    {
051        Map<String, TransactionContext> context = RESOURCES.get();
052        if (context == null)
053            return null;
054        
055        //throw new TransactionException("There is no current transaction session named ["+name+"]");
056        return context.get(contextName);
057    }
058    
059    public static TransactionContext set(String contextName, Transactional tx, ConnectionAdapter conn)
060    {
061        if (tx == null)
062            throw new TransactionException("Cannot set a null connection to transaction context");
063        
064        Map<String, TransactionContext> transactionContext = RESOURCES.get();
065        if (transactionContext == null)
066        {
067            transactionContext = new HashMap<String, TransactionContext>();
068            RESOURCES.set(transactionContext);
069        }
070        if (transactionContext.isEmpty() || !transactionContext.containsKey(contextName))
071        {
072            transactionContext.put(contextName, new TransactionContext(contextName, tx, conn));
073        }
074        else if (transactionContext.containsKey(contextName))
075            throw new TransactionException("Already exists a connection bound to context name [" + contextName + "] for this thread. jkniv-whinstone-jdbc doesn't support multiple or nested transactions in same Thread!");
076        
077        return transactionContext.get(contextName);
078    }
079    
080    public static void close(String contextName)
081    {
082//  ACTIVE | MARKED_ROLLBACK | PREPARED | COMMITED | ROLLBACK | UNKNOW | NO_TRANSACTION | PREPARING | COMMITING | ROLLING_BACK
083// release when: MARKED_ROLLBACK | ROLLING_BACK | ROLLBACK | PREPARED | COMMITED | COMMITING
084        TransactionContext transactionContext = get(contextName);
085        TransactionStatus txStatus =transactionContext.getTransactional().getStatus(); 
086        if (txStatus == TransactionStatus.COMMITTED ||  txStatus == TransactionStatus.ROLLEDBACK)
087            releaseResource(contextName);
088        
089        else if (txStatus == TransactionStatus.MARKED_ROLLBACK || txStatus == TransactionStatus.PREPARING)
090        {
091            LOG.warn("Be careful transaction was finished with status [{}]", txStatus);
092            releaseResource(contextName);
093        }
094        else// ACTIVE? PREPARED? UNKNOWN? NO_TRANSACTION? ? COMMITTING ? ROLLING_BACK?
095        {
096            LOG.error("Transaction was closed with status [{}]", txStatus);
097            releaseResource(contextName);
098            // FIXME transaction design, what's happens with status: ACTIVE? PREPARED? UNKNOWN? NO_TRANSACTION? ? COMMITTING ? ROLLING_BACK?
099            //throw new TransactionException(" exists a connection bind to context name [" + name + "] for this thread!");
100        }
101        //setTransactionStatus(TransactionStatus.NO_TRANSACTION);
102    }
103    
104    private static void releaseResource(String contextName)
105    {
106        try
107        {
108            TransactionContext txContext = get(contextName);
109            txContext.getConnection().close();
110            Map<String, TransactionContext> contexts = RESOURCES.get();
111            if (contexts.size() == 1) //
112            {
113                contexts.clear();
114                RESOURCES.remove();
115            }
116            else
117            {
118                contexts.remove(contextName);
119            }
120        }
121        finally
122        {
123            RESOURCES.remove();
124        }
125    }
126}