/** * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except * in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.synapse.commons.transaction; import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.naming.Context; import javax.sql.DataSource; import javax.sql.XAConnection; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import javax.transaction.xa.XAResource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class TranscationManger { protected static final Log log = LogFactory.getLog(TranscationManger.class); private static class ConnectionMapper{ private final Connection realConn; private final String key; private ConnectionMapper(final Connection conn) { super(); this.realConn = conn; this.key = conn.toString(); } private Connection getConnection() { return realConn; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((key == null) ? 0 : key.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ConnectionMapper other = (ConnectionMapper) obj; if (key == null) { if (other.key != null) return false; } else if (!key.equals(other.key)) return false; return true; } } private static ConcurrentHashMap<Long, ConnectionMapper> connections = new ConcurrentHashMap<Long, ConnectionMapper>(); private static final String TRANSCATION_MANGER_LOOKUP_STR = "java:comp/TransactionManager"; /** * This is used to keep the enlisted XADatasource objects */ private static ThreadLocal<Map<Long,XAResource>> enlistedXADataSources = new ThreadLocal<Map<Long,XAResource>>() { protected Map<Long,XAResource> initialValue() { return new HashMap<Long,XAResource>(); } }; private static ThreadLocal<Map<Long,TransactionManager>> txManagers = new ThreadLocal<Map<Long,TransactionManager>>() { protected Map<Long,TransactionManager> initialValue() { return new HashMap<Long,TransactionManager>(); } }; private static ThreadLocal<Map<Long,Transaction>> transactions = new ThreadLocal<Map<Long,Transaction>>() { protected Map<Long,Transaction> initialValue() { return new HashMap<Long,Transaction>(); } }; public static void lookUp(Context txContext) throws Exception { long key = Thread.currentThread().getId(); Map<Long,TransactionManager> txMgrMap = txManagers.get(); if(txMgrMap.containsKey(key)){ }else{ TransactionManager transactionManager = (TransactionManager) txContext .lookup(TRANSCATION_MANGER_LOOKUP_STR); txMgrMap.put(key, transactionManager); if(log.isDebugEnabled()){ StringBuilder logMsg = new StringBuilder(); logMsg .append(" Transaction Mgr Hashcode : " + transactionManager.hashCode()) .append("\n") .append(" Transaction Mgr : " + transactionManager); log.debug(logMsg.toString()); } } } private static boolean isXAResourceEnlisted(XAResource resource) throws Exception { long key = Thread.currentThread().getId(); return enlistedXADataSources.get().containsKey(key) && enlistedXADataSources.get().containsValue(resource); } public static boolean checkConnectionAlreadyUse(Connection conn) throws SQLException { boolean isUsed = false; if (connections.containsValue(new ConnectionMapper(conn))) { isUsed = true; log.debug(" Connection toString : " + conn.toString()); } return isUsed; } public static void removeConnectionUsed(long key) { boolean contains = false; try { if (connections.containsKey(key)) { contains = true; Connection conn = connections.get(key).getConnection(); if (conn != null) { log.debug(" Connection close for Thread Id : " + key); conn.close(); } } } catch (Exception ex) { log.error(" Ignore this error " + ex); } finally { if (contains) { connections.remove(key); } } } public static Connection addConnection(final DataSource ds) throws Exception{ long key = Thread.currentThread().getId(); Connection conn = getConnection(); if(conn != null){ log.debug(" Connection can get from map : "+ key); return conn; } int count = 0; do{ conn = ds.getConnection(); Connection actual = ((javax.sql.PooledConnection)conn).getConnection(); if(conn == null || actual == null){ continue; } if(!TranscationManger.checkConnectionAlreadyUse(conn) && !actual.isClosed()){ if(!connections.containsKey(key)){ connections.putIfAbsent(key, new ConnectionMapper(conn)); log.debug(" Connection added to map in attempt : "+ count + " Thread : "+key); } break; }else{ conn.close(); conn = null; Thread.sleep(500l); continue; } } while(++count < 5); if(conn == null && count >= 5){ throw new Exception (" Not enough Connections in the pool, Cache size : "+ connections.size()); } return conn; } public static Connection getConnection(){ long key = Thread.currentThread().getId(); ConnectionMapper connMapper = connections.get(key); Connection conn = connMapper != null ? connMapper.getConnection() : null; return conn; } public static boolean isThreadHasEnlistment() { // check there is an enlistment for current thread long key = Thread.currentThread().getId(); boolean hasEnlistment = enlistedXADataSources.get().containsKey(key) ? enlistedXADataSources .get().get(key) != null : false; return hasEnlistment; } public static void bindConnection(final Connection conn) throws Exception { long key = Thread.currentThread().getId(); try { if (conn instanceof XAConnection) { Transaction tx = transactions.get().get(key); XAResource xaRes = ((XAConnection) conn).getXAResource(); if (!isXAResourceEnlisted(xaRes)) { tx.enlistResource(xaRes); addToEnlistedXADataSources(xaRes, key); log.debug(" DS enlisted in thread " + key + " XA Resource : "+ xaRes.hashCode()); } } } catch (Exception ex) { StringBuilder logMsg = new StringBuilder(); Connection actual = ((javax.sql.PooledConnection)conn).getConnection(); logMsg .append(" Thread Id : "+key) .append(" BIND ERROR , Transaction Manager status : " + txManagers.get().get(key).getStatus()) .append("\n") .append(" BIND ERROR , Transaction status : " + transactions.get().get(key).getStatus()) .append("\n") .append(" JDBC Connection status : " + actual.isClosed()) .append("\n") .append(" BIND ERROR : " + ex); log.error(logMsg.toString()); rollbackTransaction(true,key); throw ex; } } public static void delistResource(int flag, long key) throws Exception { Map<Long,XAResource> enlistedResources = enlistedXADataSources.get(); XAResource resource = null; try { if (enlistedResources != null && !enlistedResources.isEmpty()) { Transaction tx = transactions.get().get(key); resource = enlistedResources.get(key); if (tx != null && resource != null) { tx.delistResource(resource, flag); } } } catch (Exception ex) { throw new Exception("Error occurred while delisting datasource " + "connection: " + ex.getMessage(), ex); }finally{ removeConnectionUsed(key); removeTransaction(key); enlistedResources.remove(key); } } public static void removeTransaction(long key){ transactions.get().remove(key); } private static void addToEnlistedXADataSources(final XAResource resource, long key) throws Exception { if(resource != null){ enlistedXADataSources.get().put(key,resource); } } public static void rollbackTransaction(boolean insideSynapse, long key) throws Exception { int xaResourceStatus = XAResource.TMFAIL; try { if (log.isDebugEnabled()) { log.debug("rollbackTransaction()"); } if (insideSynapse && transactions.get() == null) { log.warn(" ROLLBACK Thread Local null "); return; } if (insideSynapse && transactions.get().get(key) == null) { log.warn(" ROLLBACK Some How TX null "); return; } if (transactions.get().get(key) != null && javax.transaction.Status.STATUS_ACTIVE == transactions .get().get(key).getStatus()) { txManagers.get().get(key).rollback(); xaResourceStatus = XAResource.TMFAIL; } } catch (Exception ex) { log.error(" ROLLBACK ERROR : " + txManagers.get().get(key).getStatus()); throw ex; }finally{ // delist delistResource(xaResourceStatus,key); } } public static void endTransaction(boolean insideSynapse, long key) throws Exception { int xaResourceStatus = XAResource.TMNOFLAGS; try { if(insideSynapse && transactions.get() == null){ log.warn(" END Thread Local null "); return; } if(insideSynapse && transactions.get().get(key) == null){ log.warn(" END Some How TX null "); return; } if (transactions.get().get(key) != null && javax.transaction.Status.STATUS_ACTIVE == transactions .get().get(key).getStatus()) { txManagers.get().get(key).commit(); xaResourceStatus = XAResource.TMSUCCESS; } } catch (Exception ex) { xaResourceStatus = XAResource.TMFAIL; log.error(" END ERROR : " + txManagers.get().get(key).getStatus()); throw ex; }finally{ // delist delistResource(xaResourceStatus, key); } } public static void beginTransaction() throws Exception { long key = Thread.currentThread().getId(); try { if (log.isDebugEnabled()) { log.debug("beginTransaction()"); } TransactionManager txMgr = txManagers.get().get(key); txMgr.begin(); Transaction tx = txMgr.getTransaction(); transactions.get().put(key, tx); log.debug(" BEGIN : " + transactions.get().get(key).getStatus()); } catch (Exception ex) { log.debug(" BEGIN ERROR : " + txManagers.get().get(key).getStatus()); throw ex; } } public static TransactionManager getTransactionManager() throws Exception { long key = Thread.currentThread().getId(); try { if (log.isDebugEnabled()) { log.debug("getTransactionManager Called"); } TransactionManager txMgr = txManagers.get().get(key); return txMgr; } catch (Exception ex) { log.error(" BEGIN ERROR : " + txManagers.get().get(key).getStatus()); throw ex; } } public static Transaction getTransaction() throws Exception { long key = Thread.currentThread().getId(); try { if (log.isDebugEnabled()) { log.debug("getTransaction Called"); } TransactionManager txMgr = txManagers.get().get(key); txMgr.begin(); Transaction tx = txMgr.getTransaction(); transactions.get().put(key, tx); return tx; } catch (Exception ex) { log.error(" BEGIN ERROR : " + txManagers.get().get(key).getStatus()); throw ex; } } public static int getStatus() throws Exception{ long key = Thread.currentThread().getId(); int status = javax.transaction.Status.STATUS_UNKNOWN; if(transactions.get().get(key) == null){ if(enlistedXADataSources.get().containsKey(key) && enlistedXADataSources.get().get(key) != null){ log.warn(" END Some How TX null "); }else{ status = javax.transaction.Status.STATUS_NO_TRANSACTION; } }else{ status = transactions.get().get(key).getStatus(); } return status; } }