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