/*
* JBoss, Home of Professional Open Source.
* Copyright 2013, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package com.arjuna.ats.jta.cdi.transactional;
import static java.security.AccessController.doPrivileged;
import com.arjuna.ats.jta.common.jtaPropertyManager;
import com.arjuna.ats.jta.logging.jtaLogger;
import javax.enterprise.context.ContextNotActiveException;
import javax.enterprise.inject.Intercepted;
import javax.enterprise.inject.spi.Bean;
import javax.inject.Inject;
import javax.interceptor.InvocationContext;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.Status;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.Transactional;
import org.jboss.tm.usertx.UserTransactionOperationsProvider;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.security.PrivilegedAction;
/**
* @author paul.robinson@redhat.com 02/05/2013
*/
public abstract class TransactionalInterceptorBase implements Serializable {
private static final long serialVersionUID = 1L;
@Inject
transient javax.enterprise.inject.spi.BeanManager beanManager;
@Inject
@Intercepted
private Bean<?> interceptedBean;
private static TransactionManager transactionManager;
private final boolean userTransactionAvailable;
protected TransactionalInterceptorBase(boolean userTransactionAvailable) {
this.userTransactionAvailable = userTransactionAvailable;
}
public Object intercept(InvocationContext ic) throws Exception {
final TransactionManager tm = getTransactionManager();
final Transaction tx = tm.getTransaction();
boolean previousUserTransactionAvailability = setUserTransactionAvailable(userTransactionAvailable);
try {
return doIntercept(tm, tx, ic);
} finally {
resetUserTransactionAvailability(previousUserTransactionAvailability);
}
}
protected abstract Object doIntercept(TransactionManager tm, Transaction tx, InvocationContext ic) throws Exception;
private Transactional getTransactional(InvocationContext ic) {
Transactional transactional = ic.getMethod().getAnnotation(Transactional.class);
if (transactional != null) {
return transactional;
}
Class<?> targetClass = ic.getTarget().getClass();
transactional = targetClass.getAnnotation(Transactional.class);
if (transactional != null) {
return transactional;
}
// see if the target is a stereotype
for (Annotation annotation : interceptedBean.getBeanClass().getAnnotations()) {
if (beanManager.isStereotype(annotation.annotationType())) {
for (Annotation stereotyped : beanManager.getStereotypeDefinition(annotation.annotationType())) {
if (stereotyped.annotationType().equals(Transactional.class)) {
return (Transactional) stereotyped;
}
}
}
}
throw new RuntimeException(jtaLogger.i18NLogger.get_expected_transactional_annotation());
}
protected Object invokeInOurTx(InvocationContext ic, TransactionManager tm) throws Exception {
tm.begin();
Transaction tx = tm.getTransaction();
try {
return ic.proceed();
} catch (Exception e) {
handleException(ic, e, tx);
} finally {
endTransaction(tm, tx);
}
throw new RuntimeException("UNREACHABLE");
}
protected Object invokeInCallerTx(InvocationContext ic, Transaction tx) throws Exception {
try {
return ic.proceed();
} catch (Exception e) {
handleException(ic, e, tx);
}
throw new RuntimeException("UNREACHABLE");
}
protected Object invokeInNoTx(InvocationContext ic) throws Exception {
return ic.proceed();
}
protected void handleException(InvocationContext ic, Exception e, Transaction tx) throws Exception {
Transactional transactional = getTransactional(ic);
for (Class dontRollbackOnClass : transactional.dontRollbackOn()) {
if (dontRollbackOnClass.isAssignableFrom(e.getClass())) {
throw e;
}
}
for (Class rollbackOnClass : transactional.rollbackOn()) {
if (rollbackOnClass.isAssignableFrom(e.getClass())) {
tx.setRollbackOnly();
throw e;
}
}
if (e instanceof RuntimeException) {
tx.setRollbackOnly();
throw e;
}
throw e;
}
protected void endTransaction(TransactionManager tm, Transaction tx) throws Exception {
if (tx != tm.getTransaction()) {
throw new RuntimeException(jtaLogger.i18NLogger.get_wrong_tx_on_thread());
}
if (tx.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
tm.rollback();
} else {
tm.commit();
}
}
protected boolean setUserTransactionAvailable(boolean available) {
UserTransactionOperationsProvider userTransactionProvider =
jtaPropertyManager.getJTAEnvironmentBean().getUserTransactionOperationsProvider();
boolean previousUserTransactionAvailability = userTransactionProvider.getAvailability();
setAvailability(userTransactionProvider, available);
return previousUserTransactionAvailability;
}
protected void resetUserTransactionAvailability(boolean previousUserTransactionAvailability) {
UserTransactionOperationsProvider userTransactionProvider =
jtaPropertyManager.getJTAEnvironmentBean().getUserTransactionOperationsProvider();
setAvailability(userTransactionProvider, previousUserTransactionAvailability);
}
protected TransactionManager getTransactionManager() {
if (transactionManager == null) {
try {
InitialContext initialContext = new InitialContext();
transactionManager = (TransactionManager) initialContext.lookup(jtaPropertyManager.getJTAEnvironmentBean().getTransactionManagerJNDIContext());
} catch (NamingException e) {
throw new ContextNotActiveException(jtaLogger.i18NLogger.get_could_not_lookup_tm(), e);
}
}
return transactionManager;
}
private void setAvailability(UserTransactionOperationsProvider userTransactionProvider, boolean available) {
if (System.getSecurityManager() == null) {
userTransactionProvider.setAvailability(available);
} else {
doPrivileged((PrivilegedAction<Object>) () -> {
userTransactionProvider.setAvailability(available);
return null;
});
}
}
}