I have an application that integrates jBPM, Spring, Hibernate/JPA, JPA2, and Richfaces4. It's mostly working ok, except sometimes I am/was getting NullPointer exceptions from the DroolsSpringTransactionManager. I'm using drools-spring 5.3.1.Final. This class is apparently a (broken?) wrapper class around (in my case), the org.springframework.orm.jpa.JpaTransactionManager.
Looking at the source code, I observed several things. There is an obvious bug in the begin() method in that if the call to getStatus() returns STATUS_NO_TRANSACTION, which it will if the wrapped tm (ptm) is null, it immediately dereferences the null ptm. (this.ptm.getTransaction(td)). I'm not too woried about this one as it seems rather unlikely.
What's happening for me (in the getStatus() method) is that it's making the call "transaction = this.ptm.getTransaction(td);" which is occasionally throwing an "IllegalStateException: Transaction already active," wrapped in an outer RuntimeException and dropping down to the finally block. There, the still null transaction is committed, throwing an NPE that masks the original exception. To mitigate this, I added a catch RuntimeException and if the cause is an IllegalStateException, I return STATUS_UNKNOWN. In the finally block, I also ensure that I don't try to commit a null transaction.
Clearly this code is fragile, and https://issues.jboss.org/browse/JBRULES-2791 is probably correct, but can anyone suggest why I'm sometimes getting the "IllegalStateException: Transaction already active" exceptions in the first place. I am using 4 separate persistence units (and transaction managers), two for basic components of my application, one for the jBPM process tables, and one for the LocalTaskManager Task tables.
Copying the code from: https://github.com/droolsjbpm/droolsjbpm-integration/blob/master/drools-container/drools-spring/src/main/java/org/drools/container/spring/beans/persistence/DroolsSpringTransactionManager.java, my mitigating changes are in red (typed, not copied):
public class DroolsSpringTransactionManager
implements
TransactionManager {
Logger logger = LoggerFactory.getLogger( getClass() );
private AbstractPlatformTransactionManager ptm;
TransactionDefinition td = new DefaultTransactionDefinition();
TransactionStatus currentTransaction = null;
public DroolsSpringTransactionManager(AbstractPlatformTransactionManager ptm) {
this.ptm = ptm;
}
public boolean begin() {
try {
if ( getStatus() == TransactionManager.STATUS_NO_TRANSACTION ) {
// If there is no transaction then start one, we will commit within the same Command
// it seems in spring calling getTransaction is enough to begin a new transaction
currentTransaction = this.ptm.getTransaction( td );
return true;
} else {
return false;
}
} catch ( Exception e ) {
logger.warn( "Unable to begin transaction",
e );
throw new RuntimeException( "Unable to begin transaction",
e );
}
}
public void commit(boolean transactionOwner) {
if ( transactionOwner ) {
try {
// if we didn't begin this transaction, then do nothing
this.ptm.commit( currentTransaction );
currentTransaction = null;
} catch ( Exception e ) {
logger.warn( "Unable to commit transaction",
e );
throw new RuntimeException( "Unable to commit transaction",
e );
}
}
}
public void rollback(boolean transactionOwner) {
try {
if ( transactionOwner ) {
this.ptm.rollback( currentTransaction );
currentTransaction = null;
}
} catch ( Exception e ) {
logger.warn( "Unable to rollback transaction",
e );
throw new RuntimeException( "Unable to rollback transaction",
e );
}
}
/**
* Borrowed from Seam
*/
public int getStatus() {
if ( ptm == null ) {
return TransactionManager.STATUS_NO_TRANSACTION;
}
logger.debug( "Current TX name (According to TransactionSynchronizationManager) : " + TransactionSynchronizationManager.getCurrentTransactionName() );
if ( TransactionSynchronizationManager.isActualTransactionActive() ) {
TransactionStatus transaction = null;
try {
if ( currentTransaction == null ) {
transaction = ptm.getTransaction( td );
if ( transaction.isNewTransaction() ) {
return TransactionManager.STATUS_COMMITTED;
}
} else {
transaction = currentTransaction;
}
logger.debug( "Current TX: " + transaction );
// If SynchronizationManager thinks it has an active transaction but
// our transaction is a new one
// then we must be in the middle of committing
if ( transaction.isCompleted() ) {
if ( transaction.isRollbackOnly() ) {
return TransactionManager.STATUS_ROLLEDBACK;
}
return TransactionManager.STATUS_COMMITTED;
} else {
// Using the commented-out code in means that if rollback with this manager,
// I always have to catch and check the exception
// because ROLLEDBACK can mean both "rolled back" and "rollback only".
// if ( transaction.isRollbackOnly() ) {
// return TransactionManager.STATUS_ROLLEDBACK;
// }
return TransactionManager.STATUS_ACTIVE;
}
{color:#f00}
} catch (RuntimeException e) {
if ( e.getCause() instanceof IllegalStateException ) {
logger.debug( "IllegalStateException in getStatus()", e );
return TransactionManager.STATUS_UNKNOWN;
} else {
logger.debug( "Unexpected Exception in getStatus()", e );
throw e;
}
{color}
} finally {
if ( currentTransaction == null ) {
{color:#f00}
if (transaction != null) {
{color}
ptm.commit( transaction );
{color:#f00}
}
{color}
}
}
}
return TransactionManager.STATUS_NO_TRANSACTION;
}
public void registerTransactionSynchronization(TransactionSynchronization ts) {
TransactionSynchronizationManager.registerSynchronization( new SpringTransactionSynchronizationAdapter( ts ) );
}
}