AtomikosTransInterceptor.java
/*
* Copyright (c) Steven P. Goldsmith. All rights reserved.
*
* Created by Steven P. Goldsmith on December 24, 2011
* sgoldsmith@com.codeferm
*/
package com.codeferm.dbaccess.transaction;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.codeferm.dbaccess.DbAccess;
import java.lang.reflect.Method;
import javax.transaction.UserTransaction;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Intercept methods annotated with
* {@link com.codeferm.dbaccess.transaction.Transaction} and commit on success
* or rollback if {@link java.lang.Exception} is thrown. The first method
* parameter must be an instance of {@link com.codeferm.dbaccess.DbAccess} in
* order for this class to close the connection prior to commit or rollback
* operation as required by Atomikos.
* <p>
* <p>
* Currently this class supports Geronimo JTA for
* {@link javax.transaction.UserTransaction} and Atomikos
* {@code UserTransactionImp} for the implementation. Other JTA implementations
* could be leveraged as well.
* <p>
* <p>
* Limitations
* <p>
* Behind the scenes, method interception is implemented by generating bytecode
* at runtime. Guice dynamically creates a subclass that applies interceptors by
* overriding methods. If you are on a platform that doesn't support bytecode
* generation (such as Android), you should use Guice without AOP support.
* <p>
* This approach imposes limits on what classes and methods can be intercepted:
* <p>
* <p>
* Classes must be public or package-private. Classes must be non-final Methods
* must be public, package-private or protected Methods must be non-final
* Instances must be created by Guice by an @Inject-annotated or no-argument
* constructor
* <p>
* <p>
* It is not possible to use method interception on instances that aren't
* constructed by Guice.
*
* @see com.codeferm.dbaccess.transaction.Transaction
* @see com.codeferm.dbaccess.transaction.AtomikosTransModule
* @see com.codeferm.dbaccess.transaction.TransactionFactory
*
* @author sgoldsmith
* @version 1.0.0
* @since 1.0.0
*/
public final class AtomikosTransInterceptor implements MethodInterceptor {
/**
* Logger.
*/
//CHECKSTYLE:OFF ConstantName - Logger is static final, not a constant
private static final Logger log = LoggerFactory.getLogger(//NOPMD
AtomikosTransInterceptor.class);
//CHECKSTYLE:ON ConstantName
/**
* Invoke method wrapped in a transaction.
*
* @param invocation Method invocation.
* @return Invoked Object.
* @throws Throwable Possible exception.
*/
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
final Method method = invocation.getMethod();
final Transaction annotation = method.getAnnotation(Transaction.class);
Object object = null;
// Make sure interceptor was called for a @Transaction method
if (annotation == null) {
// Not a Transaction annotated method, so proceed without change
object = invocation.proceed();
} else {
// First parameter must be DbAccess object
if (invocation.getArguments().length == 0
|| !(invocation.getArguments()[0] instanceof DbAccess)) {
throw new IllegalArgumentException(String.format(
"First parameter must be a DbAccess instance, not %s",
method.getName()));
}
// Get DbAccess object
final DbAccess dbAccess = (DbAccess) invocation.getArguments()[0];
final UserTransaction userTransaction = new UserTransactionImp();
// Begin transaction
userTransaction.begin();
try {
// Proceed with the original method's invocation
object = invocation.proceed();
// Close connection
dbAccess.cleanUp();
// Commit if successful
userTransaction.commit();
if (log.isDebugEnabled()) {
log.debug(String.format(
"Committed transaction for method %s",
invocation.getMethod().getName()));
}
} catch (Exception e) {
// Rollback on Exception
if (log.isDebugEnabled()) {
log.debug(String.format(
"Rollback transaction for method %s",
invocation.getMethod().getName()));
}
// Close connection
dbAccess.cleanUp();
// Rollback on error
userTransaction.rollback();
throw e;
}
}
return object;
}
}