/*
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
package com.mysema.rdfbean.spring;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.TransactionUsageException;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.SmartTransactionObject;
import com.mysema.rdfbean.model.RDFBeanTransaction;
import com.mysema.rdfbean.object.FlushMode;
import com.mysema.rdfbean.object.Session;
import com.mysema.rdfbean.object.SessionFactoryImpl;
import com.mysema.rdfbean.object.SimpleSessionContext;
/**
* RDFBeanTransactionManager is a PlatformTransactionManager implementation for
* RDFBean usage in Spring
*
* @author tiwe
* @version $Id$
*
*/
public class RDFBeanTransactionManager extends AbstractPlatformTransactionManager {
private static final long serialVersionUID = -4060513400839374983L;
private final transient SimpleSessionContext sessionContext;
private boolean clearSessionOnRollback = false;
/**
* Create a new RDFBeanTransactionManager instance.
*/
public RDFBeanTransactionManager(SessionFactoryImpl sessionFactory) {
this.sessionContext = new SimpleSessionContext(sessionFactory);
sessionFactory.setSessionContext(sessionContext);
setRollbackOnCommitFailure(false);
}
/**
* Return a transaction object for the current transaction state.
* <p>
* The returned object will usually be specific to the concrete transaction
* manager implementation, carrying corresponding transaction state in a
* modifiable fashion. This object will be passed into the other template
* methods (e.g. doBegin and doCommit), either directly or as part of a
* DefaultTransactionStatus instance.
* <p>
* The returned object should contain information about any existing
* transaction, that is, a transaction that has already started before the
* current <code>getTransaction</code> call on the transaction manager.
* Consequently, a <code>doGetTransaction</code> implementation will usually
* look for an existing transaction and store corresponding state in the
* returned transaction object.
*
* @return the current transaction object
* @throws org.springframework.transaction.CannotCreateTransactionException
* if transaction support is not available
* @throws TransactionException
* in case of lookup or system errors
* @see #doBegin
* @see #doCommit
* @see #doRollback
* @see DefaultTransactionStatus#getTransaction
*/
@Override
protected Object doGetTransaction() {
boolean closeAfterTx = sessionContext.getCurrentSession() == null;
Session session = sessionContext.getOrCreateSession();
return new TransactionObject(session, closeAfterTx);
}
/**
* Begin a new transaction with semantics according to the given transaction
* definition. Does not have to care about applying the propagation
* behavior, as this has already been handled by this abstract manager.
* <p>
* This method gets called when the transaction manager has decided to
* actually start a new transaction. Either there wasn't any transaction
* before, or the previous transaction has been suspended.
* <p>
* A special scenario is a nested transaction without savepoint: If
* <code>useSavepointForNestedTransaction()</code> returns "false", this
* method will be called to start a nested transaction when necessary. In
* such a context, there will be an active transaction: The implementation
* of this method has to detect this and start an appropriate nested
* transaction.
*
* @param transaction
* transaction object returned by <code>doGetTransaction</code>
* @param definition
* TransactionDefinition instance, describing propagation
* behavior, isolation level, read-only flag, timeout, and
* transaction name
* @throws TransactionException
* in case of creation or system errors
*/
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
try {
// session
Session s = ((TransactionObject) transaction).getSession();
s.beginTransaction(
definition.isReadOnly(),
determineTimeout(definition),
definition.getIsolationLevel());
s.setFlushMode(FlushMode.COMMIT);
} catch (RuntimeException oe) {
throw new TransactionSystemException("error beginning transaction", oe);
}
}
/**
* Perform an actual commit of the given transaction.
* <p>
* An implementation does not need to check the "new transaction" flag or
* the rollback-only flag; this will already have been handled before.
* Usually, a straight commit will be performed on the transaction object
* contained in the passed-in status.
*
* @param status
* the status representation of the transaction
* @throws TransactionException
* in case of commit or system errors
* @see DefaultTransactionStatus#getTransaction
*/
@Override
protected void doCommit(DefaultTransactionStatus status) {
TransactionObject txObj = (TransactionObject) status.getTransaction();
RDFBeanTransaction tx = txObj.getTransaction();
if (tx == null) {
throw new TransactionUsageException("no transaction active");
}
try {
txObj.getSession().flush();
tx.commit();
} catch (RuntimeException oe) {
throw new TransactionSystemException("error committing transaction", oe);
} finally {
if (txObj.getOriginalFlushMode() != null) {
txObj.getSession().setFlushMode(txObj.getOriginalFlushMode());
}
sessionContext.releaseSession();
txObj.close();
}
}
/**
* Perform an actual rollback of the given transaction.
* <p>
* An implementation does not need to check the "new transaction" flag; this
* will already have been handled before. Usually, a straight rollback will
* be performed on the transaction object contained in the passed-in status.
*
* @param status
* the status representation of the transaction
* @throws TransactionException
* in case of system errors
* @see DefaultTransactionStatus#getTransaction
*/
@Override
protected void doRollback(DefaultTransactionStatus status) {
TransactionObject txObj = (TransactionObject) status.getTransaction();
RDFBeanTransaction tx = txObj.getTransaction();
if (tx == null) {
throw new TransactionUsageException("no transaction active");
}
try {
tx.rollback();
} catch (RuntimeException oe) {
throw new TransactionSystemException("error rolling back transaction", oe);
} finally {
if (txObj.getOriginalFlushMode() != null) {
txObj.getSession().setFlushMode(txObj.getOriginalFlushMode());
}
sessionContext.releaseSession();
if (clearSessionOnRollback) {
txObj.getSession().clear();
}
txObj.close();
}
}
/**
* Check if the given transaction object indicates an existing transaction
* (that is, a transaction which has already started).
* <p>
* The result will be evaluated according to the specified propagation
* behavior for the new transaction. An existing transaction might get
* suspended (in case of PROPAGATION_REQUIRES_NEW), or the new transaction
* might participate in the existing one (in case of PROPAGATION_REQUIRED).
* <p>
* The default implementation returns <code>false</code>, assuming that
* participating in existing transactions is generally not supported.
* Subclasses are of course encouraged to provide such support.
*
* @param transaction
* transaction object returned by doGetTransaction
* @return if there is an existing transaction
* @throws TransactionException
* in case of system errors
* @see #doGetTransaction
*/
@Override
protected boolean isExistingTransaction(Object transaction) {
return ((TransactionObject) transaction).getTransaction() != null &&
((TransactionObject) transaction).getTransaction().isActive();
}
/**
* Set the given transaction rollback-only. Only called on rollback if the
* current transaction participates in an existing one.
* <p>
* The default implementation throws an IllegalTransactionStateException,
* assuming that participating in existing transactions is generally not
* supported. Subclasses are of course encouraged to provide such support.
*
* @param status
* the status representation of the transaction
* @throws TransactionException
* in case of system errors
*/
@Override
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
TransactionObject txObj = (TransactionObject) status.getTransaction();
try {
txObj.setRollbackOnly();
} catch (RuntimeException oe) {
throw new TransactionSystemException("error setting rollback-only", oe);
}
}
/**
* Set the clear-session-on-rollback flag. Because after a rollback the
* state of the objects in the session does not match the state of the
* database, it's usually prudent to clear the session after a rollback to
* prevent the application from continuing with stale objects.
*
* <p>
* Changing this flag will affect the current transaction, if there is one.
*
* @param clearSessionOnRB
* true if the session should be <code>clear()</code>'d on
* transaction rollback
*/
public void setClearSessionOnRollback(boolean clearSessionOnRB) {
this.clearSessionOnRollback = clearSessionOnRB;
}
/**
* Implement SmartTransactionObject so spring can do proper rollback-only
* handling.
*/
private static class TransactionObject implements SmartTransactionObject {
private final Session session;
private final boolean closeAfterTx;
private FlushMode flushMode;
public TransactionObject(Session session, boolean closeAfterTx) {
this.session = session;
this.closeAfterTx = closeAfterTx;
this.flushMode = session.getFlushMode();
}
public FlushMode getOriginalFlushMode() {
return flushMode;
}
public Session getSession() {
return session;
}
public RDFBeanTransaction getTransaction() {
return session.getTransaction();
}
public void setRollbackOnly() {
session.getTransaction().setRollbackOnly();
}
public boolean isRollbackOnly() {
return session.getTransaction().isRollbackOnly();
}
public void close() {
if (closeAfterTx) {
session.close();
}
}
@Override
public void flush() {
session.flush();
}
}
}