Java tutorial
/** * 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 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; } }