/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package org.hibernate.transaction;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.hibernate.HibernateException;
import org.hibernate.Transaction;
import org.hibernate.TransactionException;
import org.hibernate.jdbc.JDBCContext;
import org.hibernate.util.JTAHelper;
/**
* {@link Transaction} implementation based on transaction management through
* a JTA {@link UserTransaction}. Similar to {@link CMTTransaction}, except
* here we are actually managing the transactions through the Hibernate
* transaction mechanism.
*
* @author Gavin King
* @author Steve Ebersole
* @author Les Hazlewood
*/
public class JTATransaction implements Transaction {
private static final Logger log = LoggerFactory.getLogger( JTATransaction.class );
private final JDBCContext jdbcContext;
private final TransactionFactory.Context transactionContext;
private UserTransaction userTransaction;
private boolean newTransaction;
private boolean begun;
private boolean commitFailed;
private boolean commitSucceeded;
private boolean callback;
public JTATransaction(
UserTransaction userTransaction,
JDBCContext jdbcContext,
TransactionFactory.Context transactionContext) {
this.jdbcContext = jdbcContext;
this.transactionContext = transactionContext;
this.userTransaction = userTransaction;
}
/**
* {@inheritDoc}
*/
public void begin() throws HibernateException {
if ( begun ) {
return;
}
if ( commitFailed ) {
throw new TransactionException( "cannot re-start transaction after failed commit" );
}
log.debug( "begin" );
try {
newTransaction = userTransaction.getStatus() == Status.STATUS_NO_TRANSACTION;
if ( newTransaction ) {
userTransaction.begin();
log.debug( "Began a new JTA transaction" );
}
}
catch ( Exception e ) {
log.error( "JTA transaction begin failed", e );
throw new TransactionException( "JTA transaction begin failed", e );
}
/*if (newTransaction) {
// don't need a synchronization since we are committing
// or rolling back the transaction ourselves - assuming
// that we do no work in beforeTransactionCompletion()
synchronization = false;
}*/
boolean synchronization = jdbcContext.registerSynchronizationIfPossible();
if ( !newTransaction && !synchronization ) {
log.warn( "You should set hibernate.transaction.manager_lookup_class if cache is enabled" );
}
if ( !synchronization ) {
//if we could not register a synchronization,
//do the before/after completion callbacks
//ourself (but we need to let jdbcContext
//know that this is what we are going to
//do, so it doesn't keep trying to register
//synchronizations)
callback = jdbcContext.registerCallbackIfNecessary();
}
begun = true;
commitSucceeded = false;
jdbcContext.afterTransactionBegin( this );
}
/**
* {@inheritDoc}
*/
public void commit() throws HibernateException {
if ( !begun ) {
throw new TransactionException( "Transaction not successfully started" );
}
log.debug( "commit" );
boolean flush = !transactionContext.isFlushModeNever()
&& ( callback || !transactionContext.isFlushBeforeCompletionEnabled() );
if ( flush ) {
transactionContext.managedFlush(); //if an exception occurs during flush, user must call rollback()
}
if ( callback && newTransaction ) {
jdbcContext.beforeTransactionCompletion( this );
}
closeIfRequired();
if ( newTransaction ) {
try {
userTransaction.commit();
commitSucceeded = true;
log.debug( "Committed JTA UserTransaction" );
}
catch ( Exception e ) {
commitFailed = true; // so the transaction is already rolled back, by JTA spec
log.error( "JTA commit failed", e );
throw new TransactionException( "JTA commit failed: ", e );
}
finally {
afterCommitRollback();
}
}
else {
// this one only really needed for badly-behaved applications!
// (if the TransactionManager has a Sychronization registered,
// its a noop)
// (actually we do need it for downgrading locks)
afterCommitRollback();
}
}
/**
* {@inheritDoc}
*/
public void rollback() throws HibernateException {
if ( !begun && !commitFailed ) {
throw new TransactionException( "Transaction not successfully started" );
}
log.debug( "rollback" );
try {
closeIfRequired();
}
catch ( Exception e ) {
// swallow it, and continue to roll back JTA transaction
log.error( "could not close session during rollback", e );
}
try {
if ( newTransaction ) {
if ( !commitFailed ) {
userTransaction.rollback();
log.debug( "Rolled back JTA UserTransaction" );
}
}
else {
userTransaction.setRollbackOnly();
log.debug( "set JTA UserTransaction to rollback only" );
}
}
catch ( Exception e ) {
log.error( "JTA rollback failed", e );
throw new TransactionException( "JTA rollback failed", e );
}
finally {
afterCommitRollback();
}
}
private static final int NULL = Integer.MIN_VALUE;
private void afterCommitRollback() throws TransactionException {
begun = false;
// this method is a noop if there is a Synchronization!
if ( callback ) {
if ( !newTransaction ) {
log.warn( "You should set hibernate.transaction.manager_lookup_class if cache is enabled" );
}
int status = NULL;
try {
status = userTransaction.getStatus();
}
catch ( Exception e ) {
log.error( "Could not determine transaction status after commit", e );
throw new TransactionException( "Could not determine transaction status after commit", e );
}
finally {
jdbcContext.afterTransactionCompletion( status == Status.STATUS_COMMITTED, this );
}
}
}
/**
* {@inheritDoc}
*/
public boolean wasRolledBack() throws TransactionException {
final int status;
try {
status = userTransaction.getStatus();
}
catch ( SystemException se ) {
log.error( "Could not determine transaction status", se );
throw new TransactionException( "Could not determine transaction status", se );
}
if ( status == Status.STATUS_UNKNOWN ) {
throw new TransactionException( "Could not determine transaction status" );
}
else {
return JTAHelper.isRollback( status );
}
}
/**
* {@inheritDoc}
*/
public boolean wasCommitted() throws TransactionException {
final int status;
try {
status = userTransaction.getStatus();
}
catch ( SystemException se ) {
log.error( "Could not determine transaction status", se );
throw new TransactionException( "Could not determine transaction status: ", se );
}
if ( status == Status.STATUS_UNKNOWN ) {
throw new TransactionException( "Could not determine transaction status" );
}
else {
return status == Status.STATUS_COMMITTED;
}
}
/**
* {@inheritDoc}
*/
public boolean isActive() throws TransactionException {
if ( !begun || commitFailed || commitSucceeded ) {
return false;
}
final int status;
try {
status = userTransaction.getStatus();
}
catch ( SystemException se ) {
log.error( "Could not determine transaction status", se );
throw new TransactionException( "Could not determine transaction status: ", se );
}
if ( status == Status.STATUS_UNKNOWN ) {
throw new TransactionException( "Could not determine transaction status" );
}
else {
return status == Status.STATUS_ACTIVE;
}
}
/**
* {@inheritDoc}
*/
public void registerSynchronization(Synchronization sync) throws HibernateException {
if ( getTransactionManager() == null ) {
throw new IllegalStateException( "JTA TransactionManager not available" );
}
else {
try {
getTransactionManager().getTransaction().registerSynchronization( sync );
}
catch ( Exception e ) {
throw new TransactionException( "could not register synchronization", e );
}
}
}
/**
* Getter for property 'transactionManager'.
*
* @return Value for property 'transactionManager'.
*/
private TransactionManager getTransactionManager() {
return transactionContext.getFactory().getTransactionManager();
}
private void closeIfRequired() throws HibernateException {
boolean close = callback &&
transactionContext.shouldAutoClose() &&
!transactionContext.isClosed();
if ( close ) {
transactionContext.managedClose();
}
}
/**
* {@inheritDoc}
*/
public void setTimeout(int seconds) {
try {
userTransaction.setTransactionTimeout( seconds );
}
catch ( SystemException se ) {
throw new TransactionException( "could not set transaction timeout", se );
}
}
/**
* Getter for property 'userTransaction'.
*
* @return Value for property 'userTransaction'.
*/
protected UserTransaction getUserTransaction() {
return userTransaction;
}
}