package io.ebeaninternal.server.transaction;
import io.ebean.config.ExternalTransactionManager;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.SpiTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.TransactionSynchronizationRegistry;
import javax.transaction.UserTransaction;
Hook into external JTA transaction manager.
/**
* Hook into external JTA transaction manager.
*/
public class JtaTransactionManager implements ExternalTransactionManager {
private static final Logger logger = LoggerFactory.getLogger(JtaTransactionManager.class);
private static final String EBEAN_TXN_RESOURCE = "EBEAN_TXN_RESOURCE";
The Ebean transaction manager.
/**
* The Ebean transaction manager.
*/
private TransactionManager transactionManager;
private TransactionScopeManager scope;
Instantiates a new spring aware transaction scope manager.
/**
* Instantiates a new spring aware transaction scope manager.
*/
public JtaTransactionManager() {
}
Initialise this with the Ebean internal transaction manager.
/**
* Initialise this with the Ebean internal transaction manager.
*/
@Override
public void setTransactionManager(Object txnMgr) {
// RB: At this stage not exposing TransactionManager to
// the public API and hence the Object type and casting here
this.transactionManager = (TransactionManager) txnMgr;
this.scope = transactionManager.scope();
}
Return the current dataSource taking into account multi-tenancy.
/**
* Return the current dataSource taking into account multi-tenancy.
*/
private DataSource dataSource() {
return transactionManager.getDataSource();
}
private TransactionSynchronizationRegistry getSyncRegistry() {
try {
InitialContext ctx = new InitialContext();
return (TransactionSynchronizationRegistry) ctx.lookup("java:comp/TransactionSynchronizationRegistry");
} catch (NamingException e) {
throw new PersistenceException(e);
}
}
private UserTransaction getUserTransaction() {
try {
InitialContext ctx = new InitialContext();
return (UserTransaction) ctx.lookup("java:comp/UserTransaction");
} catch (NamingException e) {
// assuming CMT
return new DummyUserTransaction();
}
}
Looks for a current Spring managed transaction and wraps/returns that as a Ebean transaction.
Returns null if there is no current spring transaction (lazy loading outside a spring txn etc).
/**
* Looks for a current Spring managed transaction and wraps/returns that as a Ebean transaction.
* <p>
* Returns null if there is no current spring transaction (lazy loading outside a spring txn etc).
* </p>
*/
@Override
public Object getCurrentTransaction() {
TransactionSynchronizationRegistry syncRegistry = getSyncRegistry();
SpiTransaction t = (SpiTransaction) syncRegistry.getResource(EBEAN_TXN_RESOURCE);
if (t != null) {
// we have already seen this transaction
return t;
}
// check current Ebean transaction
SpiTransaction currentEbeanTransaction = scope.getInScope();
if (currentEbeanTransaction != null) {
// NOT expecting this so log WARNING
String msg = "JTA Transaction - no current txn BUT using current Ebean one " + currentEbeanTransaction.getId();
logger.warn(msg);
return currentEbeanTransaction;
}
UserTransaction ut = getUserTransaction();
if (ut == null) {
// no current JTA transaction
if (logger.isDebugEnabled()) {
logger.debug("JTA Transaction - no current txn");
}
return null;
}
// This is a transaction that Ebean has not seen before.
// "wrap" it in a Ebean specific JtaTransaction
String txnId = String.valueOf(System.currentTimeMillis());
JtaTransaction newTrans = new JtaTransaction(txnId, true, ut, dataSource(), transactionManager);
// create and register transaction listener
JtaTxnListener txnListener = createJtaTxnListener(newTrans);
syncRegistry.putResource(EBEAN_TXN_RESOURCE, newTrans);
syncRegistry.registerInterposedSynchronization(txnListener);
// also put in Ebean ThreadLocal
scope.set(newTrans);
return newTrans;
}
Create a listener to register with JTA to enable Ebean to be
notified when transactions commit and rollback.
This is used by Ebean to notify it's appropriate listeners and maintain it's server
cache etc.
/**
* Create a listener to register with JTA to enable Ebean to be
* notified when transactions commit and rollback.
* <p>
* This is used by Ebean to notify it's appropriate listeners and maintain it's server
* cache etc.
* </p>
*/
private JtaTxnListener createJtaTxnListener(SpiTransaction t) {
return new JtaTxnListener(transactionManager, t);
}
private static class DummyUserTransaction implements UserTransaction {
@Override
public void begin() {
}
@Override
public void commit() throws SecurityException, IllegalStateException {
}
@Override
public int getStatus() {
return 0;
}
@Override
public void rollback() throws IllegalStateException, SecurityException {
}
@Override
public void setRollbackOnly() throws IllegalStateException {
}
@Override
public void setTransactionTimeout(int seconds) {
}
}
A JTA Transaction Synchronization that we register to get notified when a
managed transaction has been committed or rolled back.
When Ebean is notified (of the commit/rollback) it can then manage its
cache, notify BeanPersistListeners etc.
/**
* A JTA Transaction Synchronization that we register to get notified when a
* managed transaction has been committed or rolled back.
* <p>
* When Ebean is notified (of the commit/rollback) it can then manage its
* cache, notify BeanPersistListeners etc.
* </p>
*/
private static class JtaTxnListener implements Synchronization {
private final TransactionManager transactionManager;
private final SpiTransaction transaction;
private JtaTxnListener(TransactionManager transactionManager, SpiTransaction t) {
this.transactionManager = transactionManager;
this.transaction = t;
}
@Override
public void beforeCompletion() {
transaction.preCommit();
}
@Override
public void afterCompletion(int status) {
switch (status) {
case Status.STATUS_COMMITTED:
if (logger.isDebugEnabled()) {
logger.debug("Jta Txn [" + transaction.getId() + "] committed");
}
transaction.postCommit();
// Remove this transaction object as it is completed
transactionManager.scope().clearExternal();
break;
case Status.STATUS_ROLLEDBACK:
if (logger.isDebugEnabled()) {
logger.debug("Jta Txn [" + transaction.getId() + "] rollback");
}
transaction.postRollback(null);
// Remove this transaction object as it is completed
transactionManager.scope().clearExternal();
break;
default:
if (logger.isDebugEnabled()) {
logger.debug("Jta Txn [" + transaction.getId() + "] status:" + status);
}
}
// No matter the completion status of the transaction, we release the connection we got from the pool.
JdbcClose.close(transaction.getInternalConnection());
}
}
}