transactionInfoThreadLocal = new ThreadLocal<>();
+
+ private final TransactionManager tm = TransactionManagerServices.getTransactionManager();
+
+ public TransactionInfo topTransaction() {
+ return transactionInfoThreadLocal.get();
+ }
+
+ public int currentLevel() {
+ TransactionInfo tt = topTransaction();
+ return tt == null ? 0 : tt.level;
+ }
+
+ public TransactionAttributeType currentType() {
+ TransactionInfo tt = topTransaction();
+ return tt == null ? null : tt.currentTransactionAttributeType;
+ }
+
+ public Transactional.TxType currentTxType() {
+ TransactionInfo tt = topTransaction();
+ return tt == null ? null : tt.currentTxType;
+ }
+
+ public void commitTransaction() throws HeuristicRollbackException, RollbackException, InvalidTransactionException, HeuristicMixedException, SystemException {
+ popTransaction(true, false);
+ }
+ public void rollbackTransaction() throws HeuristicRollbackException, RollbackException, InvalidTransactionException, HeuristicMixedException, SystemException {
+ popTransaction(true, true);
+ }
+
+ public void popTransaction() throws HeuristicRollbackException, RollbackException, InvalidTransactionException, HeuristicMixedException, SystemException {
+ popTransaction(false, false);
+ }
+
+ public boolean isUserTransaction() {
+ final TransactionInfo transactionInfo = transactionInfoThreadLocal.get();
+ return transactionInfo != null ? transactionInfo.userTransaction : false;
+ }
+
+
+ public void popTransaction(boolean expectNewTra, boolean rollback) throws HeuristicRollbackException, RollbackException, HeuristicMixedException, SystemException, InvalidTransactionException {
+ TransactionInfo transactionInfo = transactionInfoThreadLocal.get();
+ transactionInfoThreadLocal.set(transactionInfo.previous);
+ try {
+ if (expectNewTra && !transactionInfo.newTra)
+ throw new IllegalStateException("expected new tra-transaction-frame on stack");
+ if (transactionInfo.newTra || transactionInfo.suspended != null) {
+ if (rollback)
+ tm.rollback();
+ else
+ tm.commit();
+ for (EntityManagerInfo ei: transactionInfo.entityManagers) {
+ ei.em.close();
+ }
+ }
+ }
+ finally {
+ if (transactionInfo.suspended != null) {
+ tm.resume(transactionInfo.suspended);
+ }
+ }
+ }
+
+ public void pushUserTransaction() throws SystemException, NotSupportedException {
+ final TransactionInfo previousTransactionInfo = topTransaction();
+ TransactionInfo transactionInfo = new TransactionInfo(previousTransactionInfo);
+ transactionInfo.currentTransactionAttributeType = null;
+ if (traActive()) {
+ transactionInfo.suspended = tm.suspend();
+ }
+ tm.begin();
+ transactionInfo.newTra = true;
+ transactionInfo.setUserTransaction();
+ transactionInfoThreadLocal.set(transactionInfo);
+
+ }
+
+ public void pushTransaction(Transactional.TxType attributeType) throws SystemException, NotSupportedException {
+ final TransactionInfo previousTransactionInfo = topTransaction();
+ TransactionInfo transactionInfo = new TransactionInfo(previousTransactionInfo);
+ transactionInfo.currentTxType = attributeType;
+ switch (attributeType) {
+ case MANDATORY:
+ if (!traActive())
+ throw new TransactionRequiredException("Mandatory Transaction");
+ break;
+ case REQUIRED:
+ if (!traActive()) {
+ tm.begin();
+ transactionInfo.newTra = true;
+ }
+ break;
+ case REQUIRES_NEW:
+ if (traActive()) {
+ transactionInfo.suspended = tm.suspend();
+ }
+ tm.begin();
+ transactionInfo.newTra = true;
+ break;
+ case SUPPORTS:
+ break;
+ case NOT_SUPPORTED:
+ if (traActive()) {
+ transactionInfo.suspended = tm.suspend();
+ }
+ break;
+ case NEVER:
+ if (traActive())
+ throw new TransactionRequiredException("Transaction is not allowed");
+ break;
+ }
+ transactionInfoThreadLocal.set(transactionInfo);
+ }
+
+ public void pushTransaction(TransactionAttributeType attributeType) throws SystemException, NotSupportedException {
+
+ final TransactionInfo previousTransactionInfo = topTransaction();
+ TransactionInfo transactionInfo = new TransactionInfo(previousTransactionInfo);
+ transactionInfo.currentTransactionAttributeType = attributeType;
+ switch (attributeType) {
+ case MANDATORY:
+ if (!traActive())
+ throw new TransactionRequiredException("Mandatory Transaction");
+ break;
+ case REQUIRED:
+ if (!traActive()) {
+ tm.begin();
+ transactionInfo.newTra = true;
+ }
+ break;
+ case REQUIRES_NEW:
+ if (traActive()) {
+ transactionInfo.suspended = tm.suspend();
+ }
+ tm.begin();
+ transactionInfo.newTra = true;
+ break;
+ case SUPPORTS:
+ break;
+ case NOT_SUPPORTED:
+ if (traActive()) {
+ transactionInfo.suspended = tm.suspend();
+ }
+ break;
+ case NEVER:
+ if (traActive())
+ throw new TransactionRequiredException("Transaction is not allowed");
+ break;
+ }
+ transactionInfoThreadLocal.set(transactionInfo);
+ }
+
+ private boolean traActive() {
+ try {
+ return tm.getStatus() != Status.STATUS_NO_TRANSACTION;
+ } catch (SystemException e) {
+ throw new RuntimeException("simulated ejb", e);
+ }
+ }
+
+ public EntityManager getEntityManager(SqlPersistenceFactory sqlPersistenceFactory, boolean expectTransaction) {
+ if (expectTransaction && !traActive()) {
+ throw new TransactionRequiredException("ejb simulation");
+ }
+ String name = sqlPersistenceFactory.getPersistenceUnitName();
+ TransactionInfo info = topTransaction();
+ while (info != null) {
+ if (info.newTra || info.suspended != null)
+ break;
+ info = info.previous;
+ }
+ if (info != null) {
+ for (EntityManagerInfo ei: info.entityManagers) {
+ if (ei.persistenceUnit.equals(name)) {
+ return ei.em;
+ }
+ }
+ EntityManager result = sqlPersistenceFactory.getEmf().createEntityManager();
+ if (traActive()) {
+ result.joinTransaction();
+ }
+ return result;
+ } else {
+ assert !traActive();
+ return sqlPersistenceFactory.getEmf().createEntityManager();
+ }
+ }
+}
diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TUserTransaction.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TUserTransaction.java
new file mode 100644
index 00000000..5ac77b89
--- /dev/null
+++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TUserTransaction.java
@@ -0,0 +1,222 @@
+package bitronix.tm.integration.cdi;
+
+import bitronix.tm.TransactionManagerServices;
+
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.InvalidTransactionException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.UserTransaction;
+
+/**
+ * @author aschoerk
+ */
+public class TUserTransaction implements UserTransaction {
+
+ TransactionManager tm;
+
+ TFrameStack ts;
+
+ public TUserTransaction() {
+ this.tm = TransactionManagerServices.getTransactionManager();
+ this.ts = new TFrameStack();
+ }
+
+ /**
+ * Create a new transaction and associate it with the current thread.
+ *
+ * @exception NotSupportedException Thrown if the thread is already
+ * associated with a transaction and the Transaction Manager
+ * implementation does not support nested transactions.
+ *
+ * @exception SystemException Thrown if the transaction manager
+ * encounters an unexpected error condition.
+ *
+ */
+ @Override
+ public void begin() throws NotSupportedException, SystemException {
+ if (ts.isUserTransaction())
+ throw new IllegalStateException("Already UserTransaction running");
+ ts.pushUserTransaction();
+ }
+
+ /**
+ * Complete the transaction associated with the current thread. When this
+ * method completes, the thread is no longer associated with a transaction.
+ *
+ * @exception RollbackException Thrown to indicate that
+ * the transaction has been rolled back rather than committed.
+ *
+ * @exception HeuristicMixedException Thrown to indicate that a heuristic
+ * decision was made and that some relevant updates have been committed
+ * while others have been rolled back.
+ *
+ * @exception HeuristicRollbackException Thrown to indicate that a
+ * heuristic decision was made and that all relevant updates have been
+ * rolled back.
+ *
+ * @exception SecurityException Thrown to indicate that the thread is
+ * not allowed to commit the transaction.
+ *
+ * @exception IllegalStateException Thrown if the current thread is
+ * not associated with a transaction.
+ *
+ * @exception SystemException Thrown if the transaction manager
+ * encounters an unexpected error condition.
+ *
+ */
+ @Override
+ public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException {
+ try {
+ if (!ts.isUserTransaction())
+ throw new IllegalStateException("No UserTransaction");
+ ts.commitTransaction();
+ } catch (InvalidTransactionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Obtain the status of the transaction associated with the current thread.
+ *
+ * @return The transaction status. If no transaction is associated with
+ * the current thread, this method returns the Status.NoTransaction
+ * value.
+ *
+ * @exception SystemException Thrown if the transaction manager
+ * encounters an unexpected error condition.
+ *
+ */
+ @Override
+ public int getStatus() throws SystemException {
+ return tm.getStatus();
+ }
+
+ /**
+ * Get the transaction object that represents the transaction
+ * context of the calling thread.
+ *
+ * @return the Transaction
object representing the
+ * transaction associated with the calling thread.
+ *
+ * @exception SystemException Thrown if the transaction manager
+ * encounters an unexpected error condition.
+ *
+ */
+ public Transaction getTransaction() throws SystemException {
+ return tm.getTransaction();
+ }
+
+ /**
+ * Resume the transaction context association of the calling thread
+ * with the transaction represented by the supplied Transaction object.
+ * When this method returns, the calling thread is associated with the
+ * transaction context specified.
+ *
+ * @param tobj The Transaction
object that represents the
+ * transaction to be resumed.
+ *
+ * @exception InvalidTransactionException Thrown if the parameter
+ * transaction object contains an invalid transaction.
+ *
+ * @exception IllegalStateException Thrown if the thread is already
+ * associated with another transaction.
+ *
+ * @exception SystemException Thrown if the transaction manager
+ * encounters an unexpected error condition.
+ */
+ public void resume(Transaction tobj) throws InvalidTransactionException, IllegalStateException, SystemException {
+ if (!ts.isUserTransaction())
+ throw new IllegalStateException("No UserTransaction");
+ tm.resume(tobj);
+ }
+
+ /**
+ * Roll back the transaction associated with the current thread. When this
+ * method completes, the thread is no longer associated with a
+ * transaction.
+ *
+ * @exception SecurityException Thrown to indicate that the thread is
+ * not allowed to roll back the transaction.
+ *
+ * @exception IllegalStateException Thrown if the current thread is
+ * not associated with a transaction.
+ *
+ * @exception SystemException Thrown if the transaction manager
+ * encounters an unexpected error condition.
+ *
+ */
+ @Override
+ public void rollback() throws IllegalStateException, SecurityException, SystemException {
+ try {
+ if (!ts.isUserTransaction())
+ throw new IllegalStateException("No UserTransaction");
+ ts.rollbackTransaction();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Modify the transaction associated with the current thread such that
+ * the only possible outcome of the transaction is to roll back the
+ * transaction.
+ *
+ * @exception IllegalStateException Thrown if the current thread is
+ * not associated with a transaction.
+ *
+ * @exception SystemException Thrown if the transaction manager
+ * encounters an unexpected error condition.
+ *
+ */
+ @Override
+ public void setRollbackOnly() throws IllegalStateException, SystemException {
+ if (!ts.isUserTransaction())
+ throw new IllegalStateException("No UserTransaction");
+ tm.setRollbackOnly();
+ }
+
+ /**
+ * Modify the timeout value that is associated with transactions started
+ * by the current thread with the begin method.
+ *
+ * If an application has not called this method, the transaction
+ * service uses some default value for the transaction timeout.
+ *
+ * @param seconds The value of the timeout in seconds. If the value is zero,
+ * the transaction service restores the default value. If the value
+ * is negative a SystemException is thrown.
+ *
+ * @exception SystemException Thrown if the transaction manager
+ * encounters an unexpected error condition.
+ *
+ */
+ @Override
+ public void setTransactionTimeout(int seconds) throws SystemException {
+ tm.setTransactionTimeout(seconds);
+ }
+
+ /**
+ * Suspend the transaction currently associated with the calling
+ * thread and return a Transaction object that represents the
+ * transaction context being suspended. If the calling thread is
+ * not associated with a transaction, the method returns a null
+ * object reference. When this method returns, the calling thread
+ * is not associated with a transaction.
+ *
+ * @return Transaction object representing the suspended transaction.
+ *
+ * @exception SystemException Thrown if the transaction manager
+ * encounters an unexpected error condition.
+ *
+ */
+ public Transaction suspend() throws SystemException {
+ if (!ts.isUserTransaction())
+ throw new IllegalStateException("No UserTransaction");
+ return tm.suspend();
+ }
+}
diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionInfo.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionInfo.java
new file mode 100644
index 00000000..12c912ca
--- /dev/null
+++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionInfo.java
@@ -0,0 +1,43 @@
+package bitronix.tm.integration.cdi;
+
+import javax.ejb.TransactionAttributeType;
+import javax.persistence.EntityManager;
+import javax.transaction.Transaction;
+import javax.transaction.Transactional;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author aschoerk
+ */
+public class TransactionInfo {
+
+ static class TAttribute {
+ TransactionAttributeType ejbAttribue;
+ Transactional.TxType txType;
+ boolean userTransaction;
+ }
+
+ public TransactionInfo(TransactionInfo previous) {
+ this.previous = previous;
+ this.level = previous != null ? previous.level + 1 : 0;
+ }
+
+ List entityManagers = new ArrayList<>();
+ Transaction suspended; // fetching of entitymanagers: only new ones
+ boolean newTra = false; // if true: tra has been begin, entitymanagers joined, pop means: need to commit!
+ TransactionAttributeType currentTransactionAttributeType;
+ Transactional.TxType currentTxType;
+ TAttribute tAttribute;
+ TransactionInfo previous;
+ boolean userTransaction;
+ int level;
+
+ public void setUserTransaction() {
+ this.userTransaction = true;
+ }
+
+ public boolean isUserTransaction() {
+ return userTransaction;
+ }
+}
diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionalCdiExtension.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionalCdiExtension.java
new file mode 100644
index 00000000..63f30071
--- /dev/null
+++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionalCdiExtension.java
@@ -0,0 +1,131 @@
+package bitronix.tm.integration.cdi;
+
+import org.apache.deltaspike.core.util.metadata.builder.AnnotatedTypeBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+import javax.ejb.MessageDriven;
+import javax.ejb.Singleton;
+import javax.ejb.Stateful;
+import javax.ejb.Stateless;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.*;
+import javax.enterprise.util.AnnotationLiteral;
+import javax.transaction.Transactional;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class TransactionalCdiExtension implements Extension {
+
+ private static final Logger log = LoggerFactory.getLogger(TransactionalCdiExtension.class);
+
+ AnnotationLiteral EJBTRALITERAL = new AnnotationLiteral () {
+
+ private static final long serialVersionUID = -6529647818427562781L;
+ };
+
+ AnnotationLiteral CDITRALITERAL = new AnnotationLiteral () {
+
+ private static final long serialVersionUID = 6942136472219373737L;
+ };
+
+ void processAnnotatedType(@Observes ProcessAnnotatedType pat) {
+ final AnnotatedType annotatedType = pat.getAnnotatedType();
+ boolean interceptForEjbTransactions = false;
+ boolean interceptForCdiTransactions = false;
+ if (annotatedType.isAnnotationPresent(Stateless.class)
+ || annotatedType.isAnnotationPresent(Stateful.class)
+ || annotatedType.isAnnotationPresent(Singleton.class)
+ || annotatedType.isAnnotationPresent(MessageDriven.class)
+ ) {
+ interceptForEjbTransactions = true;
+ }
+ for (AnnotatedMethod m: annotatedType.getMethods()) {
+ if (m.isAnnotationPresent(Transactional.class)) {
+ interceptForCdiTransactions = true;
+ }
+ }
+ if (annotatedType.isAnnotationPresent(Transactional.class)) {
+ interceptForCdiTransactions = true;
+ }
+ if (interceptForCdiTransactions && interceptForEjbTransactions) {
+ log.warn("Transactional-Annotation for Ejb ignored {}", annotatedType.getJavaClass().getName());
+ interceptForCdiTransactions = false;
+ }
+ if (interceptForEjbTransactions || interceptForCdiTransactions) {
+ final boolean finalInterceptForCdiTransactions = interceptForCdiTransactions;
+ pat.setAnnotatedType(new AnnotatedType() {
+ @Override
+ public Class getJavaClass() {
+ return annotatedType.getJavaClass();
+ }
+
+ @Override
+ public Set> getConstructors() {
+ return annotatedType.getConstructors();
+ }
+
+ @Override
+ public Set> getMethods() {
+ return annotatedType.getMethods();
+ }
+
+ @Override
+ public Set> getFields() {
+ return annotatedType.getFields();
+ }
+
+ @Override
+ public Type getBaseType() {
+ return annotatedType.getBaseType();
+ }
+
+ @Override
+ public Set getTypeClosure() {
+ return annotatedType.getTypeClosure();
+ }
+
+ @Override
+ public T getAnnotation(Class annotationType) {
+ if (finalInterceptForCdiTransactions) {
+ if (annotationType.equals(CdiTransactional.class))
+ return (T) CDITRALITERAL;
+ } else {
+ if (annotationType.equals(EjbTransactional.class))
+ return (T) EJBTRALITERAL;
+ }
+ return annotatedType.getAnnotation(annotationType);
+ }
+
+ @Override
+ public Set getAnnotations() {
+ Set result = new HashSet<>(annotatedType.getAnnotations());
+ if (finalInterceptForCdiTransactions) {
+ result.add(CDITRALITERAL);
+ } else {
+ result.add(EJBTRALITERAL);
+
+ }
+ return Collections.unmodifiableSet(result);
+ }
+
+ @Override
+ public boolean isAnnotationPresent(Class extends Annotation> annotationType) {
+ if (finalInterceptForCdiTransactions) {
+ if (annotationType.equals(CdiTransactional.class))
+ return true;
+ } else {
+ if (annotationType.equals(EjbTransactional.class))
+ return true;
+ }
+ return annotatedType.isAnnotationPresent(annotationType);
+ }
+ });
+ }
+
+ }
+}
diff --git a/btm-cdi/src/main/resources/META-INF/beans.xml b/btm-cdi/src/main/resources/META-INF/beans.xml
new file mode 100644
index 00000000..b7fdba87
--- /dev/null
+++ b/btm-cdi/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/cdiintercepted/CDITransactionalJPABean.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/cdiintercepted/CDITransactionalJPABean.java
new file mode 100644
index 00000000..f924619d
--- /dev/null
+++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/cdiintercepted/CDITransactionalJPABean.java
@@ -0,0 +1,89 @@
+package bitronix.tm.integration.cdi.cdiintercepted;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+
+import java.util.Iterator;
+
+import javax.annotation.Resource;
+import javax.ejb.Stateless;
+import javax.ejb.TransactionAttribute;
+import javax.ejb.TransactionAttributeType;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.sql.DataSource;
+import javax.transaction.TransactionManager;
+import javax.transaction.Transactional;
+import javax.transaction.xa.XAResource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import bitronix.tm.integration.cdi.entities.TestEntity1;
+import bitronix.tm.mock.events.EventRecorder;
+import bitronix.tm.mock.events.XAResourceCommitEvent;
+import bitronix.tm.mock.events.XAResourceEndEvent;
+import bitronix.tm.mock.events.XAResourcePrepareEvent;
+import bitronix.tm.mock.events.XAResourceStartEvent;
+
+@Transactional
+public class CDITransactionalJPABean {
+
+ private static final Logger log = LoggerFactory.getLogger(CDITransactionalJPABean.class);
+
+ @Resource(name = "h2DataSource")
+ private DataSource dataSource;
+
+ @Inject
+ TransactionManager tm;
+
+ @Inject
+ EntityManager em;
+
+
+ @Transactional(value = Transactional.TxType.REQUIRES_NEW)
+ public void insertTestEntityInNewTra() throws Exception {
+ em.persist(new TestEntity1());
+ }
+
+ @Transactional(value = Transactional.TxType.REQUIRES_NEW)
+ public void insertTestEntityInNewTraAndSetRollbackOnly() throws Exception {
+ em.persist(new TestEntity1());
+ tm.setRollbackOnly();
+ }
+
+ @Transactional(value = Transactional.TxType.REQUIRED)
+ public void insertTestEntityInRequired() throws Exception {
+ em.persist(new TestEntity1());
+ }
+
+ public long countTestEntity() throws Exception {
+ Long result = em.createQuery("select count(e) from TestEntity1 e", Long.class).getSingleResult();
+ return result;
+ }
+
+ public void verifyEvents(int count) {
+ if (log.isDebugEnabled()) {
+ log.debug(EventRecorder.dumpToString());
+ }
+
+ Iterator> it = EventRecorder.iterateEvents();
+
+ for (int i = 0; i < count; i++) {
+ assertEquals(XAResource.TMNOFLAGS, ((XAResourceStartEvent) it.next()).getFlag());
+ }
+ for (int i = 0; i < count; i++) {
+ assertEquals(XAResource.TMSUCCESS, ((XAResourceEndEvent) it.next()).getFlag());
+ }
+ if (count > 1) {
+ for (int i = 0; i < count; i++) {
+ assertEquals(XAResource.XA_OK, ((XAResourcePrepareEvent) it.next()).getReturnCode());
+ }
+ }
+ for (int i = 0; i < count; i++) {
+ assertEquals(count == 1, ((XAResourceCommitEvent) it.next()).isOnePhase());
+ }
+
+ assertFalse(it.hasNext());
+ }
+}
diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/cdiintercepted/H2CdiTransactionalTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/cdiintercepted/H2CdiTransactionalTest.java
new file mode 100644
index 00000000..68a70d95
--- /dev/null
+++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/cdiintercepted/H2CdiTransactionalTest.java
@@ -0,0 +1,138 @@
+package bitronix.tm.integration.cdi.cdiintercepted;
+
+import javax.inject.Inject;
+import javax.sql.DataSource;
+import javax.transaction.RollbackException;
+import javax.transaction.UserTransaction;
+
+import org.jglue.cdiunit.AdditionalClasses;
+import org.jglue.cdiunit.AdditionalPackages;
+import org.jglue.cdiunit.CdiRunner;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import bitronix.tm.integration.cdi.CdiTInterceptor;
+import bitronix.tm.integration.cdi.EjbTInterceptor;
+import bitronix.tm.integration.cdi.PlatformTransactionManager;
+import bitronix.tm.integration.cdi.TransactionalCdiExtension;
+
+/**
+ * @author aschoerk
+ */
+@RunWith(CdiRunner.class)
+@AdditionalClasses({
+ H2PersistenceFactory.class,
+ CdiTInterceptor.class, TransactionalCdiExtension.class})
+@AdditionalPackages(PlatformTransactionManager.class)
+public class H2CdiTransactionalTest {
+
+ static Logger log = LoggerFactory.getLogger("testlogger");
+
+ @BeforeClass
+ public static void loginit() {
+ log.info("log");
+ }
+
+ @Inject
+ PlatformTransactionManager platformTransactionManager;
+
+ @Inject
+ UserTransaction tm;
+
+ @Inject
+ DataSource dataSource;
+
+ @Before
+ public void initContext() throws Exception {
+ }
+
+ @Inject
+ CDITransactionalJPABean jpaBean;
+
+ @Test
+ public void testTraMethod() throws Exception {
+
+ jpaBean.insertTestEntityInNewTra();
+ Assert.assertEquals(1L, jpaBean.countTestEntity());
+ tm.begin();
+ jpaBean.insertTestEntityInRequired();
+ tm.rollback();
+ Assert.assertEquals(1L, jpaBean.countTestEntity());
+ tm.begin();
+ jpaBean.insertTestEntityInRequired();
+ jpaBean.insertTestEntityInNewTra();
+ tm.rollback();
+ Assert.assertEquals(2L, jpaBean.countTestEntity());
+ tm.begin();
+ jpaBean.insertTestEntityInRequired();
+ jpaBean.insertTestEntityInNewTra();
+ insertTestEntityInNewTraAndRollback();
+ tm.commit();
+ Assert.assertEquals(4L, jpaBean.countTestEntity());
+ tm.begin();
+ jpaBean.insertTestEntityInRequired();
+ insertTestEntityInNewTraAndRollback();
+ tm.commit();
+ Assert.assertEquals(5L, jpaBean.countTestEntity());
+ tm.begin();
+ insertTestEntityInNewTraAndRollback();
+ jpaBean.insertTestEntityInRequired();
+ tm.commit();
+ Assert.assertEquals(6L, jpaBean.countTestEntity());
+ tm.begin();
+ insertTestEntityInNewTraAndRollback();
+ jpaBean.insertTestEntityInRequired();
+ jpaBean.insertTestEntityInNewTra();
+ tm.rollback();
+ Assert.assertEquals(7L, jpaBean.countTestEntity());
+
+ tm.begin();
+ jpaBean.insertTestEntityInRequired();
+ jpaBean.insertTestEntityInNewTra();
+ insertTestEntityInNewTraAndSetRollbackOnly();
+ tm.commit();
+ Assert.assertEquals(9L, jpaBean.countTestEntity());
+ tm.begin();
+ jpaBean.insertTestEntityInRequired();
+ insertTestEntityInNewTraAndSetRollbackOnly();
+ tm.commit();
+ Assert.assertEquals(10L, jpaBean.countTestEntity());
+ tm.begin();
+ insertTestEntityInNewTraAndSetRollbackOnly();
+ jpaBean.insertTestEntityInRequired();
+ tm.commit();
+ Assert.assertEquals(11L, jpaBean.countTestEntity());
+ tm.begin();
+ insertTestEntityInNewTraAndSetRollbackOnly();
+ jpaBean.insertTestEntityInRequired();
+ jpaBean.insertTestEntityInNewTra();
+ tm.rollback();
+ Assert.assertEquals(12L, jpaBean.countTestEntity());
+
+ }
+
+ private void insertTestEntityInNewTraAndSetRollbackOnly() throws Exception {
+ try {
+ jpaBean.insertTestEntityInNewTraAndSetRollbackOnly();
+ Assert.fail("Expected Rollbackexception during commit of new tra");
+ } catch (RollbackException ex){
+
+ }
+ }
+
+ private void insertTestEntityInNewTraAndRollback() throws Exception {
+ try {
+ jpaBean.insertTestEntityInNewTraAndSetRollbackOnly();
+ Assert.fail("expected rollbackexception");
+ }
+ catch (RollbackException rbe) {
+
+ }
+ }
+
+}
diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/cdiintercepted/H2PersistenceFactory.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/cdiintercepted/H2PersistenceFactory.java
new file mode 100644
index 00000000..01208922
--- /dev/null
+++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/cdiintercepted/H2PersistenceFactory.java
@@ -0,0 +1,62 @@
+package bitronix.tm.integration.cdi.cdiintercepted;
+
+import java.util.Properties;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Produces;
+import javax.persistence.EntityManager;
+import javax.sql.DataSource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import bitronix.tm.integration.cdi.SqlPersistenceFactory;
+import bitronix.tm.resource.jdbc.PoolingDataSource;
+
+/**
+ * @author aschoerk
+ */
+@ApplicationScoped
+public class H2PersistenceFactory extends SqlPersistenceFactory {
+
+ Logger log = LoggerFactory.getLogger("H2PersistenceFactory");
+
+ public H2PersistenceFactory() {
+ }
+
+
+ @Override
+ public String getPersistenceUnitName() {
+ return "btm-cdi-test-h2-pu";
+ }
+
+ @Produces
+ public EntityManager newEm() {
+ return produceEntityManager();
+ }
+
+
+ @Produces
+ @ApplicationScoped
+ protected DataSource createDataSource() {
+ if (ds != null)
+ return ds;
+ log.info("creating datasource");
+ PoolingDataSource res = new PoolingDataSource();
+ res.setClassName("org.h2.jdbcx.JdbcDataSource");
+ Properties driverProperties = res.getDriverProperties();
+ driverProperties.setProperty("URL", "jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_DELAY=0");
+ driverProperties.setProperty("user","sa");
+ driverProperties.setProperty("password","");
+ res.setUniqueName("jdbc/btm-cdi-test-h2");
+ res.setMinPoolSize(1);
+ res.setMaxPoolSize(10);
+ res.setAllowLocalTransactions(true); // to allow autocommitmode
+ res.init();
+ log.info("created datasource");
+ return res;
+ }
+
+
+
+}
diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/EJBTransactionalJPABean.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/EJBTransactionalJPABean.java
new file mode 100644
index 00000000..212c6988
--- /dev/null
+++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/EJBTransactionalJPABean.java
@@ -0,0 +1,86 @@
+package bitronix.tm.integration.cdi.ejbintercepted;
+
+import bitronix.tm.integration.cdi.entities.TestEntity1;
+import bitronix.tm.mock.events.EventRecorder;
+import bitronix.tm.mock.events.XAResourceCommitEvent;
+import bitronix.tm.mock.events.XAResourceEndEvent;
+import bitronix.tm.mock.events.XAResourcePrepareEvent;
+import bitronix.tm.mock.events.XAResourceStartEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Resource;
+import javax.ejb.Stateless;
+import javax.ejb.TransactionAttribute;
+import javax.ejb.TransactionAttributeType;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.sql.DataSource;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAResource;
+import java.util.Iterator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+
+@Stateless
+public class EJBTransactionalJPABean {
+
+ private static final Logger log = LoggerFactory.getLogger(EJBTransactionalJPABean.class);
+
+ @Resource(name = "h2DataSource")
+ private DataSource dataSource;
+
+ @Inject
+ TransactionManager tm;
+
+ @Inject
+ EntityManager em;
+
+
+ @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
+ public void insertTestEntityInNewTra() throws Exception {
+ em.persist(new TestEntity1());
+ }
+
+ @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
+ public void insertTestEntityInNewTraAndSetRollbackOnly() throws Exception {
+ em.persist(new TestEntity1());
+ tm.setRollbackOnly();
+ }
+
+ @TransactionAttribute(value = TransactionAttributeType.REQUIRED)
+ public void insertTestEntityInRequired() throws Exception {
+ em.persist(new TestEntity1());
+ }
+
+ public long countTestEntity() throws Exception {
+ Long result = em.createQuery("select count(e) from TestEntity1 e", Long.class).getSingleResult();
+ return result;
+ }
+
+ public void verifyEvents(int count) {
+ if (log.isDebugEnabled()) {
+ log.debug(EventRecorder.dumpToString());
+ }
+
+ Iterator> it = EventRecorder.iterateEvents();
+
+ for (int i = 0; i < count; i++) {
+ assertEquals(XAResource.TMNOFLAGS, ((XAResourceStartEvent) it.next()).getFlag());
+ }
+ for (int i = 0; i < count; i++) {
+ assertEquals(XAResource.TMSUCCESS, ((XAResourceEndEvent) it.next()).getFlag());
+ }
+ if (count > 1) {
+ for (int i = 0; i < count; i++) {
+ assertEquals(XAResource.XA_OK, ((XAResourcePrepareEvent) it.next()).getReturnCode());
+ }
+ }
+ for (int i = 0; i < count; i++) {
+ assertEquals(count == 1, ((XAResourceCommitEvent) it.next()).isOnePhase());
+ }
+
+ assertFalse(it.hasNext());
+ }
+}
diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/H2EjbTransactionalTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/H2EjbTransactionalTest.java
new file mode 100644
index 00000000..76f39cda
--- /dev/null
+++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/H2EjbTransactionalTest.java
@@ -0,0 +1,137 @@
+package bitronix.tm.integration.cdi.ejbintercepted;
+
+import org.jglue.cdiunit.AdditionalClasses;
+import org.jglue.cdiunit.AdditionalPackages;
+import org.jglue.cdiunit.CdiRunner;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javax.sql.DataSource;
+import javax.transaction.RollbackException;
+import javax.transaction.UserTransaction;
+
+import bitronix.tm.integration.cdi.EjbTInterceptor;
+import bitronix.tm.integration.cdi.PlatformTransactionManager;
+import bitronix.tm.integration.cdi.TransactionalCdiExtension;
+
+/**
+ * @author aschoerk
+ */
+@RunWith(CdiRunner.class)
+@AdditionalClasses({
+ H2PersistenceFactory.class,
+ EjbTInterceptor.class, TransactionalCdiExtension.class})
+@AdditionalPackages(PlatformTransactionManager.class)
+public class H2EjbTransactionalTest {
+
+ static Logger log = LoggerFactory.getLogger("testlogger");
+
+ @BeforeClass
+ public static void loginit() {
+ log.info("log");
+ }
+
+ @Inject
+ PlatformTransactionManager platformTransactionManager;
+
+ @Inject
+ UserTransaction tm;
+
+ @Inject
+ DataSource dataSource;
+
+ @Before
+ public void initContext() throws Exception {
+ }
+
+ @Inject
+ EJBTransactionalJPABean jpaBean;
+
+ @Test
+ public void testTraMethod() throws Exception {
+
+ jpaBean.insertTestEntityInNewTra();
+ Assert.assertEquals(1L, jpaBean.countTestEntity());
+ tm.begin();
+ jpaBean.insertTestEntityInRequired();
+ tm.rollback();
+ Assert.assertEquals(1L, jpaBean.countTestEntity());
+ tm.begin();
+ jpaBean.insertTestEntityInRequired();
+ jpaBean.insertTestEntityInNewTra();
+ tm.rollback();
+ Assert.assertEquals(2L, jpaBean.countTestEntity());
+ tm.begin();
+ jpaBean.insertTestEntityInRequired();
+ jpaBean.insertTestEntityInNewTra();
+ insertTestEntityInNewTraAndRollback();
+ tm.commit();
+ Assert.assertEquals(4L, jpaBean.countTestEntity());
+ tm.begin();
+ jpaBean.insertTestEntityInRequired();
+ insertTestEntityInNewTraAndRollback();
+ tm.commit();
+ Assert.assertEquals(5L, jpaBean.countTestEntity());
+ tm.begin();
+ insertTestEntityInNewTraAndRollback();
+ jpaBean.insertTestEntityInRequired();
+ tm.commit();
+ Assert.assertEquals(6L, jpaBean.countTestEntity());
+ tm.begin();
+ insertTestEntityInNewTraAndRollback();
+ jpaBean.insertTestEntityInRequired();
+ jpaBean.insertTestEntityInNewTra();
+ tm.rollback();
+ Assert.assertEquals(7L, jpaBean.countTestEntity());
+
+ tm.begin();
+ jpaBean.insertTestEntityInRequired();
+ jpaBean.insertTestEntityInNewTra();
+ insertTestEntityInNewTraAndSetRollbackOnly();
+ tm.commit();
+ Assert.assertEquals(9L, jpaBean.countTestEntity());
+ tm.begin();
+ jpaBean.insertTestEntityInRequired();
+ insertTestEntityInNewTraAndSetRollbackOnly();
+ tm.commit();
+ Assert.assertEquals(10L, jpaBean.countTestEntity());
+ tm.begin();
+ insertTestEntityInNewTraAndSetRollbackOnly();
+ jpaBean.insertTestEntityInRequired();
+ tm.commit();
+ Assert.assertEquals(11L, jpaBean.countTestEntity());
+ tm.begin();
+ insertTestEntityInNewTraAndSetRollbackOnly();
+ jpaBean.insertTestEntityInRequired();
+ jpaBean.insertTestEntityInNewTra();
+ tm.rollback();
+ Assert.assertEquals(12L, jpaBean.countTestEntity());
+
+ }
+
+ private void insertTestEntityInNewTraAndSetRollbackOnly() throws Exception {
+ try {
+ jpaBean.insertTestEntityInNewTraAndSetRollbackOnly();
+ Assert.fail("Expected Rollbackexception during commit of new tra");
+ } catch (RollbackException ex){
+
+ }
+ }
+
+ private void insertTestEntityInNewTraAndRollback() throws Exception {
+ try {
+ jpaBean.insertTestEntityInNewTraAndSetRollbackOnly();
+ Assert.fail("expected rollbackexception");
+ }
+ catch (RollbackException rbe) {
+
+ }
+ }
+
+}
diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/H2PersistenceFactory.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/H2PersistenceFactory.java
new file mode 100644
index 00000000..2dc8e4c9
--- /dev/null
+++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/H2PersistenceFactory.java
@@ -0,0 +1,64 @@
+package bitronix.tm.integration.cdi.ejbintercepted;
+
+import bitronix.tm.integration.cdi.SqlPersistenceFactory;
+import bitronix.tm.resource.jdbc.PoolingDataSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Produces;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+import javax.sql.DataSource;
+import javax.transaction.TransactionManager;
+import java.util.Properties;
+
+/**
+ * @author aschoerk
+ */
+@ApplicationScoped
+public class H2PersistenceFactory extends SqlPersistenceFactory {
+
+ Logger log = LoggerFactory.getLogger("H2PersistenceFactory");
+
+ public H2PersistenceFactory() {
+ }
+
+
+ @Override
+ public String getPersistenceUnitName() {
+ return "btm-cdi-test-h2-pu";
+ }
+
+ @Produces
+ public EntityManager newEm() {
+ return produceEntityManager();
+ }
+
+
+ @Produces
+ @ApplicationScoped
+ protected DataSource createDataSource() {
+ if (ds != null)
+ return ds;
+ log.info("creating datasource");
+ PoolingDataSource res = new PoolingDataSource();
+ res.setClassName("org.h2.jdbcx.JdbcDataSource");
+ Properties driverProperties = res.getDriverProperties();
+ driverProperties.setProperty("URL", "jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_DELAY=0");
+ driverProperties.setProperty("user","sa");
+ driverProperties.setProperty("password","");
+ res.setUniqueName("jdbc/btm-cdi-test-h2");
+ res.setMinPoolSize(1);
+ res.setMaxPoolSize(10);
+ res.setAllowLocalTransactions(true); // to allow autocommitmode
+ res.init();
+ log.info("created datasource");
+ return res;
+ }
+
+
+
+}
diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/entities/TestEntity1.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/entities/TestEntity1.java
new file mode 100644
index 00000000..d2b4a0c2
--- /dev/null
+++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/entities/TestEntity1.java
@@ -0,0 +1,50 @@
+package bitronix.tm.integration.cdi.entities;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+/**
+ * @author aschoerk
+ */
+@Entity
+@Table(name = "test_entity_1")
+public class TestEntity1 {
+ @Id
+ @GeneratedValue(strategy = GenerationType.TABLE)
+ private Long id;
+
+ private String stringAttribute;
+
+ private int intAttribute;
+
+ public TestEntity1() {
+
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(long idP) {
+ this.id = idP;
+ }
+
+ public String getStringAttribute() {
+ return stringAttribute;
+ }
+
+ public void setStringAttribute(String stringAttributeP) {
+ this.stringAttribute = stringAttributeP;
+ }
+
+ public int getIntAttribute() {
+ return intAttribute;
+ }
+
+ public void setIntAttribute(int intAttributeP) {
+ this.intAttribute = intAttributeP;
+ }
+}
diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/DataSource1.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/DataSource1.java
new file mode 100644
index 00000000..d623ef0f
--- /dev/null
+++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/DataSource1.java
@@ -0,0 +1,22 @@
+package bitronix.tm.integration.cdi.nonintercepted;
+
+import javax.enterprise.inject.Alternative;
+
+import bitronix.tm.integration.cdi.PoolingDataSourceFactoryBean;
+
+/**
+ * @author aschoerk
+ */
+@Alternative
+public class DataSource1 extends PoolingDataSourceFactoryBean {
+
+ private static final long serialVersionUID = 6581338365140914540L;
+
+ public DataSource1() {
+ super();
+ setClassName("bitronix.tm.mock.resource.jdbc.MockitoXADataSource");
+ setUniqueName("btm-cdi-test-ds1");
+ setMinPoolSize(1);
+ setMaxPoolSize(3);
+ }
+}
diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/DataSource2.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/DataSource2.java
new file mode 100644
index 00000000..2bb2f17a
--- /dev/null
+++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/DataSource2.java
@@ -0,0 +1,28 @@
+package bitronix.tm.integration.cdi.nonintercepted;
+
+import javax.enterprise.inject.Alternative;
+import java.util.Properties;
+
+import bitronix.tm.integration.cdi.PoolingDataSourceFactoryBean;
+
+/**
+ * @author aschoerk
+ */
+@Alternative
+public class DataSource2 extends PoolingDataSourceFactoryBean {
+
+ private static final long serialVersionUID = 6581338365140914540L;
+
+ public DataSource2() {
+ super();
+ setClassName("bitronix.tm.mock.resource.jdbc.MockitoXADataSource");
+ setUniqueName("btm-cdi-test-ds2");
+ setMinPoolSize(1);
+ setMaxPoolSize(2);
+ setAutomaticEnlistingEnabled(true);
+ setUseTmJoin(false);
+ Properties driverProperties = new Properties();
+ driverProperties.setProperty("loginTimeout", "5");
+ setDriverProperties(driverProperties);
+ }
+}
diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/JPABean.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/JPABean.java
new file mode 100644
index 00000000..ca0a250c
--- /dev/null
+++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/JPABean.java
@@ -0,0 +1,155 @@
+package bitronix.tm.integration.cdi.nonintercepted;
+
+import bitronix.tm.integration.cdi.entities.TestEntity1;
+import bitronix.tm.mock.events.EventRecorder;
+import bitronix.tm.mock.events.XAResourceCommitEvent;
+import bitronix.tm.mock.events.XAResourceEndEvent;
+import bitronix.tm.mock.events.XAResourcePrepareEvent;
+import bitronix.tm.mock.events.XAResourceStartEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAResource;
+import java.util.Iterator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+
+class JPABean {
+
+ private static final Logger log = LoggerFactory.getLogger(JPABean.class);
+
+ @Inject
+ TransactionManager tm;
+
+ @Inject
+ EntityManagerFactory entityManagerFactory;
+
+ class CloseableEm implements AutoCloseable {
+ private final EntityManager em;
+
+ public CloseableEm(EntityManager em) {
+ this.em = em;
+ }
+
+ public EntityManager getEm() {
+ return em;
+ }
+
+ @Override
+ public void close() throws Exception {
+ em.close();
+ }
+ }
+
+ CloseableEm getEm() {
+ EntityManager result = entityManagerFactory.createEntityManager();
+ result.joinTransaction();
+ return new CloseableEm(result);
+ }
+
+ public void insertTestEntityInNewTra() throws Exception {
+ Transaction suspendedTransaction = tm.suspend();
+
+ tm.begin();
+ try (CloseableEm em = getEm()){
+ em.getEm().persist(new TestEntity1());
+ } finally {
+ tm.commit();
+ if (suspendedTransaction != null)
+ tm.resume(suspendedTransaction);
+ }
+ }
+
+ public void insertTestEntityInNewTraAndRollback() throws Exception {
+ Transaction suspendedTransaction = tm.suspend();
+
+ tm.begin();
+ try (CloseableEm em = getEm()){
+ em.getEm().persist(new TestEntity1());
+ } finally {
+ tm.rollback();
+ if (suspendedTransaction != null)
+ tm.resume(suspendedTransaction);
+ }
+ }
+
+ public void insertTestEntityInNewTraAndSetRollbackOnly() throws Exception {
+ Transaction suspendedTransaction = tm.suspend();
+
+ tm.begin();
+ try (CloseableEm em = getEm()){
+ em.getEm().persist(new TestEntity1());
+ tm.setRollbackOnly();
+ } finally {
+ try {
+ tm.commit();
+
+ } catch (RollbackException ex) {
+
+ }
+
+ if (suspendedTransaction != null)
+ tm.resume(suspendedTransaction);
+ }
+ }
+
+ public void insertTestEntityInRequired() throws Exception {
+ boolean encloseInTra = tm.getStatus() == Status.STATUS_NO_TRANSACTION ? true : false;
+ if (encloseInTra) {
+ tm.begin();
+ }
+ try (CloseableEm em = getEm()){
+ em.getEm().persist(new TestEntity1());
+ } finally {
+ if (encloseInTra)
+ tm.commit();
+ }
+ }
+
+ public long countTestEntity() throws Exception {
+ boolean encloseInTra = tm.getStatus() == Status.STATUS_NO_TRANSACTION ? true : false;
+ if (encloseInTra) {
+ tm.begin();
+ }
+ try (CloseableEm em = getEm()) {
+ Long result = em.getEm().createQuery("select count(e) from TestEntity1 e", Long.class).getSingleResult();
+ return result;
+ } finally {
+ if (encloseInTra)
+ tm.commit();
+ }
+ }
+
+ public void verifyEvents(int count) {
+ if (log.isDebugEnabled()) {
+ log.debug(EventRecorder.dumpToString());
+ }
+
+ Iterator> it = EventRecorder.iterateEvents();
+
+ for (int i = 0; i < count; i++) {
+ assertEquals(XAResource.TMNOFLAGS, ((XAResourceStartEvent) it.next()).getFlag());
+ }
+ for (int i = 0; i < count; i++) {
+ assertEquals(XAResource.TMSUCCESS, ((XAResourceEndEvent) it.next()).getFlag());
+ }
+ if (count > 1) {
+ for (int i = 0; i < count; i++) {
+ assertEquals(XAResource.XA_OK, ((XAResourcePrepareEvent) it.next()).getReturnCode());
+ }
+ }
+ for (int i = 0; i < count; i++) {
+ assertEquals(count == 1, ((XAResourceCommitEvent) it.next()).isOnePhase());
+ }
+
+ assertFalse(it.hasNext());
+ }
+}
diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/PlatformTransactionManagerTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/PlatformTransactionManagerTest.java
new file mode 100644
index 00000000..30589603
--- /dev/null
+++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/PlatformTransactionManagerTest.java
@@ -0,0 +1,45 @@
+package bitronix.tm.integration.cdi.nonintercepted;
+
+import javax.inject.Inject;
+
+import org.jglue.cdiunit.ActivatedAlternatives;
+import org.jglue.cdiunit.AdditionalClasses;
+import org.jglue.cdiunit.CdiRunner;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import bitronix.tm.integration.cdi.PlatformTransactionManager;
+import bitronix.tm.mock.events.EventRecorder;
+
+@RunWith(CdiRunner.class)
+@AdditionalClasses({PlatformTransactionManager.class})
+@ActivatedAlternatives({DataSource1.class})
+public class PlatformTransactionManagerTest {
+
+ private static final Logger log = LoggerFactory.getLogger(PlatformTransactionManagerTest.class);
+
+ @Inject
+ private TransactionalBean bean;
+
+ @Before @After
+ public void clearEvents() {
+ EventRecorder.clear();
+ }
+
+ @After
+ public void logEvents() {
+ if (log.isDebugEnabled()) {
+ log.debug(EventRecorder.dumpToString());
+ }
+ }
+
+ @Test
+ public void testTransactionalMethod() throws Exception {
+ bean.doSomethingTransactional(1);
+ bean.verifyEvents(1);
+ }
+}
diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/PoolingDataSourceFactoryBeanTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/PoolingDataSourceFactoryBeanTest.java
new file mode 100644
index 00000000..30872042
--- /dev/null
+++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/PoolingDataSourceFactoryBeanTest.java
@@ -0,0 +1,49 @@
+package bitronix.tm.integration.cdi.nonintercepted;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+import java.sql.Connection;
+
+import javax.inject.Inject;
+
+import org.jglue.cdiunit.ActivatedAlternatives;
+import org.jglue.cdiunit.CdiRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import junit.framework.Assert;
+
+@RunWith(CdiRunner.class)
+@ActivatedAlternatives({DataSource2.class})
+public class PoolingDataSourceFactoryBeanTest {
+
+ @Inject
+ private DataSource2 dataSource2;
+
+ @Test
+ public void validateProperties() {
+ Assert.assertEquals("btm-cdi-test-ds2", dataSource2.getUniqueName());
+ Assert.assertEquals("bitronix.tm.mock.resource.jdbc.MockitoXADataSource", dataSource2.getClassName());
+ Assert.assertEquals(1, dataSource2.getMinPoolSize());
+ Assert.assertEquals(2, dataSource2.getMaxPoolSize());
+ Assert.assertEquals(true, dataSource2.getAutomaticEnlistingEnabled());
+ Assert.assertEquals(false, dataSource2.getUseTmJoin());
+ Assert.assertEquals(60, dataSource2.getMaxIdleTime()); // default value not overridden in bean configuration
+ Assert.assertEquals("5", dataSource2.getDriverProperties().get("loginTimeout"));
+ }
+
+ @Test
+ public void validateConnection() throws Exception {
+ Connection connection = null;
+ try {
+ connection = dataSource2.getObject().getConnection();
+ assertNotNull(connection);
+ }
+ finally {
+ if (connection != null) {
+ connection.close();
+ }
+ }
+ }
+}
diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/Resources.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/Resources.java
new file mode 100644
index 00000000..834a6899
--- /dev/null
+++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/Resources.java
@@ -0,0 +1,88 @@
+package bitronix.tm.integration.cdi.nonintercepted;
+
+import bitronix.tm.resource.jdbc.PoolingDataSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.PreDestroy;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Produces;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+import javax.sql.DataSource;
+import javax.transaction.TransactionManager;
+import java.util.Properties;
+
+/**
+ * @author aschoerk
+ */
+@ApplicationScoped
+class Resources {
+
+ Logger log = LoggerFactory.getLogger("ResourcesLogger");
+
+ public Resources() {
+ }
+
+ @PreDestroy
+ public void preDestroyResources() {
+ if (ds != null) {
+ ds.close();
+ ds = null;
+ }
+ }
+
+ @Inject
+ TransactionManager tm;
+
+ private PoolingDataSource ds;
+
+ private EntityManagerFactory emf = null;
+
+ protected void createEntityManagerFactory() {
+ if (emf == null) {
+ if (ds == null)
+ createDataSource();
+ emf = Persistence.createEntityManagerFactory("btm-cdi-test-h2-pu");
+ }
+ }
+
+ DataSource getDs() {
+ if (ds == null)
+ createDataSource();
+ return ds;
+ }
+
+ @Produces
+ EntityManagerFactory produceEntityManagerFactory() {
+ createEntityManagerFactory();
+ return emf;
+ }
+
+ @Produces
+ @ApplicationScoped
+ DataSource createDataSource() {
+ if (ds != null)
+ return ds;
+ log.info("creating datasource");
+ PoolingDataSource res = new PoolingDataSource();
+ res.setClassName("org.h2.jdbcx.JdbcDataSource");
+ Properties driverProperties = res.getDriverProperties();
+ driverProperties.setProperty("URL", "jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_DELAY=0");
+ driverProperties.setProperty("user","sa");
+ driverProperties.setProperty("password","");
+ res.setUniqueName("jdbc/btm-cdi-test-h2");
+ res.setMinPoolSize(1);
+ res.setMaxPoolSize(3);
+ res.setAllowLocalTransactions(true);
+ res.init();
+ log.info("created datasource");
+ ds = res;
+ return res;
+ }
+
+
+
+}
diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/TransactionalBean.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/TransactionalBean.java
new file mode 100644
index 00000000..f1105a93
--- /dev/null
+++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/TransactionalBean.java
@@ -0,0 +1,77 @@
+package bitronix.tm.integration.cdi.nonintercepted;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+
+import java.sql.Connection;
+import java.util.Iterator;
+
+import javax.inject.Inject;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAResource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import bitronix.tm.integration.cdi.PoolingDataSourceFactoryBean;
+import bitronix.tm.mock.events.EventRecorder;
+import bitronix.tm.mock.events.XAResourceCommitEvent;
+import bitronix.tm.mock.events.XAResourceEndEvent;
+import bitronix.tm.mock.events.XAResourcePrepareEvent;
+import bitronix.tm.mock.events.XAResourceStartEvent;
+
+public class TransactionalBean {
+
+ private static final Logger log = LoggerFactory.getLogger(TransactionalBean.class);
+
+ @Inject
+ private PoolingDataSourceFactoryBean dataSource;
+
+ @Inject
+ TransactionManager transactionManager;
+
+ public void doSomethingTransactional(int count) throws Exception {
+ transactionManager.begin();
+ log.info("From transactional method, claiming {} connection(s)", count);
+
+ Connection[] connections = new Connection[count];
+ try {
+ for (int i = 0; i < count; i++) {
+ connections[i] = dataSource.getObject().getConnection();
+ connections[i].createStatement();
+ }
+ } finally {
+ for (int i = 0; i < count; i++) {
+ if (connections[i] != null) {
+ connections[i].close();
+ }
+ }
+ }
+ transactionManager.commit();
+ }
+
+ public void verifyEvents(int count) {
+ if (log.isDebugEnabled()) {
+ log.debug(EventRecorder.dumpToString());
+ }
+
+ Iterator> it = EventRecorder.iterateEvents();
+
+ for (int i = 0; i < count; i++) {
+ assertEquals(XAResource.TMNOFLAGS, ((XAResourceStartEvent) it.next()).getFlag());
+ }
+ for (int i = 0; i < count; i++) {
+ assertEquals(XAResource.TMSUCCESS, ((XAResourceEndEvent) it.next()).getFlag());
+ }
+ if (count > 1) {
+ for (int i = 0; i < count; i++) {
+ assertEquals(XAResource.XA_OK, ((XAResourcePrepareEvent) it.next()).getReturnCode());
+ }
+ }
+ for (int i = 0; i < count; i++) {
+ assertEquals(count == 1, ((XAResourceCommitEvent) it.next()).isOnePhase());
+ }
+
+ assertFalse(it.hasNext());
+ }
+}
diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/TransactionalTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/TransactionalTest.java
new file mode 100644
index 00000000..f02ecac7
--- /dev/null
+++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/TransactionalTest.java
@@ -0,0 +1,132 @@
+package bitronix.tm.integration.cdi.nonintercepted;
+
+import org.jglue.cdiunit.AdditionalClasses;
+import org.jglue.cdiunit.CdiRunner;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javax.transaction.TransactionManager;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+import bitronix.tm.integration.cdi.PlatformTransactionManager;
+import bitronix.tm.integration.cdi.TransactionalCdiExtension;
+
+/**
+ * @author aschoerk
+ */
+@RunWith(CdiRunner.class)
+@AdditionalClasses({PlatformTransactionManager.class, Resources.class, TransactionalCdiExtension.class})
+public class TransactionalTest {
+
+ static Logger log = LoggerFactory.getLogger("testlogger");
+
+ @BeforeClass
+ public static void loginit() {
+ log.info("log");
+ }
+
+
+ @Inject
+ TransactionManager tm;
+
+ @Inject
+ Resources resources;
+
+ @Before
+ public void initContext() throws Exception {
+ }
+
+ @Test
+ public void test() throws Exception {
+ tm.begin();
+
+ try (Connection connection = resources.getDs().getConnection()) {
+ try (Statement stmt = connection.createStatement()) {
+ stmt.execute("create table t (a varchar(200), b integer)");
+ stmt.execute("insert into t (a, b) values ('a', 1 )");
+ stmt.execute("insert into t (a, b) values ('b', 2 )");
+ stmt.execute("select * from t");
+ try (ResultSet result = stmt.getResultSet()) {
+ assert (result.next());
+ assert (result.next());
+ assert (!result.next());
+ }
+ }
+ }
+ tm.commit();
+ }
+
+ @Inject
+ JPABean JPABean;
+
+ @Test
+ public void testTraMethod() throws Exception {
+
+ JPABean.insertTestEntityInNewTra();
+ Assert.assertEquals(1L, JPABean.countTestEntity());
+ tm.begin();
+ JPABean.insertTestEntityInRequired();
+ tm.rollback();
+ Assert.assertEquals(1L, JPABean.countTestEntity());
+ tm.begin();
+ JPABean.insertTestEntityInRequired();
+ JPABean.insertTestEntityInNewTra();
+ tm.rollback();
+ Assert.assertEquals(2L, JPABean.countTestEntity());
+ tm.begin();
+ JPABean.insertTestEntityInRequired();
+ JPABean.insertTestEntityInNewTra();
+ JPABean.insertTestEntityInNewTraAndRollback();
+ tm.commit();
+ Assert.assertEquals(4L, JPABean.countTestEntity());
+ tm.begin();
+ JPABean.insertTestEntityInRequired();
+ JPABean.insertTestEntityInNewTraAndRollback();
+ tm.commit();
+ Assert.assertEquals(5L, JPABean.countTestEntity());
+ tm.begin();
+ JPABean.insertTestEntityInNewTraAndRollback();
+ JPABean.insertTestEntityInRequired();
+ tm.commit();
+ Assert.assertEquals(6L, JPABean.countTestEntity());
+ tm.begin();
+ JPABean.insertTestEntityInNewTraAndRollback();
+ JPABean.insertTestEntityInRequired();
+ JPABean.insertTestEntityInNewTra();
+ tm.rollback();
+ Assert.assertEquals(7L, JPABean.countTestEntity());
+
+ tm.begin();
+ JPABean.insertTestEntityInRequired();
+ JPABean.insertTestEntityInNewTra();
+ JPABean.insertTestEntityInNewTraAndSetRollbackOnly();
+ tm.commit();
+ Assert.assertEquals(9L, JPABean.countTestEntity());
+ tm.begin();
+ JPABean.insertTestEntityInRequired();
+ JPABean.insertTestEntityInNewTraAndSetRollbackOnly();
+ tm.commit();
+ Assert.assertEquals(10L, JPABean.countTestEntity());
+ tm.begin();
+ JPABean.insertTestEntityInNewTraAndSetRollbackOnly();
+ JPABean.insertTestEntityInRequired();
+ tm.commit();
+ Assert.assertEquals(11L, JPABean.countTestEntity());
+ tm.begin();
+ JPABean.insertTestEntityInNewTraAndSetRollbackOnly();
+ JPABean.insertTestEntityInRequired();
+ JPABean.insertTestEntityInNewTra();
+ tm.rollback();
+ Assert.assertEquals(12L, JPABean.countTestEntity());
+
+ }
+
+}
diff --git a/btm-cdi/src/test/resources/META-INF/beans.xml b/btm-cdi/src/test/resources/META-INF/beans.xml
new file mode 100644
index 00000000..b7fdba87
--- /dev/null
+++ b/btm-cdi/src/test/resources/META-INF/beans.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/btm-cdi/src/test/resources/META-INF/persistence.xml b/btm-cdi/src/test/resources/META-INF/persistence.xml
new file mode 100644
index 00000000..793d1d1a
--- /dev/null
+++ b/btm-cdi/src/test/resources/META-INF/persistence.xml
@@ -0,0 +1,21 @@
+
+
+
+ jdbc/btm-cdi-test-h2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/btm-cdi/src/test/resources/bitronix-default-config.properties b/btm-cdi/src/test/resources/bitronix-default-config.properties
new file mode 100644
index 00000000..9ad95e92
--- /dev/null
+++ b/btm-cdi/src/test/resources/bitronix-default-config.properties
@@ -0,0 +1,6 @@
+bitronix.tm.serverId=btm-cdi-test
+# for testing don't use logs
+bitronix.tm.journal=null
+bitronix.tm.timer.gracefulShutdownInterval=0
+# bitronix.tm.journal.disk.logPart1Filename=target/btm1.tlog
+# bitronix.tm.journal.disk.logPart2Filename=target/btm2.tlog
diff --git a/btm-cdi/src/test/resources/jndi.properties b/btm-cdi/src/test/resources/jndi.properties
new file mode 100644
index 00000000..5d3c598d
--- /dev/null
+++ b/btm-cdi/src/test/resources/jndi.properties
@@ -0,0 +1 @@
+java.naming.factory.initial=bitronix.tm.jndi.BitronixInitialContextFactory
\ No newline at end of file
diff --git a/btm-cdi/src/test/resources/log4j.xml b/btm-cdi/src/test/resources/log4j.xml
new file mode 100644
index 00000000..77d51869
--- /dev/null
+++ b/btm-cdi/src/test/resources/log4j.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/btm-cdi/src/test/resources/test-context.xml b/btm-cdi/src/test/resources/test-context.xml
new file mode 100644
index 00000000..7f939027
--- /dev/null
+++ b/btm-cdi/src/test/resources/test-context.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5
+
+
+
+
+
+
+
+
+
+
diff --git a/btm-spring/pom.xml b/btm-spring/pom.xml
index f73adf74..09f3237e 100644
--- a/btm-spring/pom.xml
+++ b/btm-spring/pom.xml
@@ -9,6 +9,12 @@
btm-spring
Bitronix Transaction Manager :: Spring Integration
+
+ 1.0.1.Final
+ 4.2.7.Final
+ 4.3.1.Final
+ 1.7.21
+
org.codehaus.btm
@@ -62,21 +68,53 @@
spring-context
test
+
+ org.springframework
+ spring-orm
+ test
+
org.springframework
spring-test
test
+
org.slf4j
- jcl-over-slf4j
+ slf4j-api
+
+
+ org.slf4j
+ slf4j-log4j12
test
+ ${slf4j.version}
ch.qos.logback
logback-classic
test
+
+ com.h2database
+ h2
+ test
+ 1.4.196
+
+
+ org.hibernate
+ hibernate-entitymanager
+ ${version.org.hibernate}
+
+
+ org.hibernate
+ hibernate-validator
+ ${version.org.hibernate.validator}
+
+
+ org.hibernate.javax.persistence
+ hibernate-jpa-2.0-api
+ ${version.org.hibernate.api}
+
diff --git a/btm-spring/src/test/java/bitronix/tm/integration/spring/JPATest.java b/btm-spring/src/test/java/bitronix/tm/integration/spring/JPATest.java
new file mode 100644
index 00000000..7395677d
--- /dev/null
+++ b/btm-spring/src/test/java/bitronix/tm/integration/spring/JPATest.java
@@ -0,0 +1,32 @@
+package bitronix.tm.integration.spring;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.sql.DataSource;
+
+/**
+ * @author aschoerk
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("classpath:test-context.xml")
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
+public class JPATest {
+
+ @Resource(name = "h2DataSource")
+ private DataSource dataSource;
+
+ @Inject
+ TransactionalJPABean transactionalJPABean;
+
+ @Test
+ public void test() {
+ transactionalJPABean.insertTestEntityInNewTra();
+ transactionalJPABean.insertTestEntityInRequired();
+ }
+}
diff --git a/btm-spring/src/test/java/bitronix/tm/integration/spring/LifecycleTest.java b/btm-spring/src/test/java/bitronix/tm/integration/spring/LifecycleTest.java
index 31577046..7d289e93 100644
--- a/btm-spring/src/test/java/bitronix/tm/integration/spring/LifecycleTest.java
+++ b/btm-spring/src/test/java/bitronix/tm/integration/spring/LifecycleTest.java
@@ -46,7 +46,7 @@ public void testLifecycle() throws SQLException {
// make sure that a refresh closes all connections
int totalPoolSize = getTotalPoolSize(applicationContext);
assertEquals(3, totalPoolSize);
-
+
applicationContext.refresh();
verifyConnectionsClosed(totalPoolSize);
@@ -66,7 +66,8 @@ public void testLifecycle() throws SQLException {
private int getTotalPoolSize(ApplicationContext applicationContext) {
int totalPoolSize = 0;
for (PoolingDataSource ds : applicationContext.getBeansOfType(PoolingDataSource.class).values()) {
- totalPoolSize += ds.getTotalPoolSize();
+ if (!ds.getAllowLocalTransactions())
+ totalPoolSize += ds.getTotalPoolSize();
}
return totalPoolSize;
}
diff --git a/btm-spring/src/test/java/bitronix/tm/integration/spring/TestEntity1.java b/btm-spring/src/test/java/bitronix/tm/integration/spring/TestEntity1.java
new file mode 100644
index 00000000..9d04800d
--- /dev/null
+++ b/btm-spring/src/test/java/bitronix/tm/integration/spring/TestEntity1.java
@@ -0,0 +1,50 @@
+package bitronix.tm.integration.spring;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+/**
+ * @author aschoerk
+ */
+@Entity
+@Table(name = "test_entity_1")
+public class TestEntity1 {
+ @Id
+ @GeneratedValue(strategy = GenerationType.TABLE)
+ private Long id;
+
+ private String stringAttribute;
+
+ private int intAttribute;
+
+ public TestEntity1() {
+
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(long idP) {
+ this.id = idP;
+ }
+
+ public String getStringAttribute() {
+ return stringAttribute;
+ }
+
+ public void setStringAttribute(String stringAttributeP) {
+ this.stringAttribute = stringAttributeP;
+ }
+
+ public int getIntAttribute() {
+ return intAttribute;
+ }
+
+ public void setIntAttribute(int intAttributeP) {
+ this.intAttribute = intAttributeP;
+ }
+}
diff --git a/btm-spring/src/test/java/bitronix/tm/integration/spring/TransactionalBean.java b/btm-spring/src/test/java/bitronix/tm/integration/spring/TransactionalBean.java
index 232b619b..bb6c845f 100644
--- a/btm-spring/src/test/java/bitronix/tm/integration/spring/TransactionalBean.java
+++ b/btm-spring/src/test/java/bitronix/tm/integration/spring/TransactionalBean.java
@@ -7,6 +7,7 @@
import java.sql.SQLException;
import java.util.Iterator;
+import javax.annotation.Resource;
import javax.inject.Inject;
import javax.sql.DataSource;
import javax.transaction.xa.XAResource;
@@ -25,7 +26,7 @@ public class TransactionalBean {
private static final Logger log = LoggerFactory.getLogger(TransactionalBean.class);
- @Inject
+ @Resource(name = "dataSource1")
private DataSource dataSource;
@Transactional
diff --git a/btm-spring/src/test/java/bitronix/tm/integration/spring/TransactionalJPABean.java b/btm-spring/src/test/java/bitronix/tm/integration/spring/TransactionalJPABean.java
new file mode 100644
index 00000000..75c4bbe3
--- /dev/null
+++ b/btm-spring/src/test/java/bitronix/tm/integration/spring/TransactionalJPABean.java
@@ -0,0 +1,98 @@
+package bitronix.tm.integration.spring;
+
+import bitronix.tm.mock.events.EventRecorder;
+import bitronix.tm.mock.events.XAResourceCommitEvent;
+import bitronix.tm.mock.events.XAResourceEndEvent;
+import bitronix.tm.mock.events.XAResourcePrepareEvent;
+import bitronix.tm.mock.events.XAResourceStartEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.sql.DataSource;
+import javax.transaction.xa.XAResource;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Iterator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+
+public class TransactionalJPABean {
+
+ private static final Logger log = LoggerFactory.getLogger(TransactionalJPABean.class);
+
+ @Resource(name = "h2DataSource")
+ private DataSource dataSource;
+
+
+ @PersistenceContext(name = "btm-cdi-test-h2-pu")
+ private EntityManager em;
+
+ @Inject
+ LocalContainerEntityManagerFactoryBean entityManagerFactoryBean;
+
+ EntityManager getEm() {
+ return em;
+ }
+
+ @Transactional
+ public void doSomethingTransactional(int count) throws SQLException {
+ log.info("From transactional method, claiming {} connection(s)", count);
+
+ Connection[] connections = new Connection[count];
+ try {
+ for (int i = 0; i < count; i++) {
+ connections[i] = dataSource.getConnection();
+ connections[i].createStatement();
+ }
+ } finally {
+ for (int i = 0; i < count; i++) {
+ if (connections[i] != null) {
+ connections[i].close();
+ }
+ }
+ }
+ }
+
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public void insertTestEntityInNewTra() {
+ getEm().persist(new TestEntity1());
+ }
+
+ @Transactional(propagation = Propagation.REQUIRED)
+ public void insertTestEntityInRequired() {
+ getEm().persist(new TestEntity1());
+ }
+
+ public void verifyEvents(int count) {
+ if (log.isDebugEnabled()) {
+ log.debug(EventRecorder.dumpToString());
+ }
+
+ Iterator> it = EventRecorder.iterateEvents();
+
+ for (int i = 0; i < count; i++) {
+ assertEquals(XAResource.TMNOFLAGS, ((XAResourceStartEvent) it.next()).getFlag());
+ }
+ for (int i = 0; i < count; i++) {
+ assertEquals(XAResource.TMSUCCESS, ((XAResourceEndEvent) it.next()).getFlag());
+ }
+ if (count > 1) {
+ for (int i = 0; i < count; i++) {
+ assertEquals(XAResource.XA_OK, ((XAResourcePrepareEvent) it.next()).getReturnCode());
+ }
+ }
+ for (int i = 0; i < count; i++) {
+ assertEquals(count == 1, ((XAResourceCommitEvent) it.next()).isOnePhase());
+ }
+
+ assertFalse(it.hasNext());
+ }
+}
diff --git a/btm-spring/src/test/resources/META-INF/persistence.xml b/btm-spring/src/test/resources/META-INF/persistence.xml
new file mode 100644
index 00000000..811dbe2d
--- /dev/null
+++ b/btm-spring/src/test/resources/META-INF/persistence.xml
@@ -0,0 +1,22 @@
+
+
+
+ jdbc/btm-spring-test-h2
+ bitronix.tm.integration.spring.TestEntity1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/btm-spring/src/test/resources/create-schema.sql b/btm-spring/src/test/resources/create-schema.sql
new file mode 100644
index 00000000..36a6b02e
--- /dev/null
+++ b/btm-spring/src/test/resources/create-schema.sql
@@ -0,0 +1,5 @@
+create table a (a varchar 200);
+create table b (a varchar 200);
+
+create table hibernate_sequences (sequence_name varchar(200), sequence_next_hi_value bigint);
+insert into hibernate_sequences (sequence_name, sequence_next_hi_value) values ('test_entity_1', 1);
diff --git a/btm-spring/src/test/resources/import.sql b/btm-spring/src/test/resources/import.sql
new file mode 100644
index 00000000..36a6b02e
--- /dev/null
+++ b/btm-spring/src/test/resources/import.sql
@@ -0,0 +1,5 @@
+create table a (a varchar 200);
+create table b (a varchar 200);
+
+create table hibernate_sequences (sequence_name varchar(200), sequence_next_hi_value bigint);
+insert into hibernate_sequences (sequence_name, sequence_next_hi_value) values ('test_entity_1', 1);
diff --git a/btm-spring/src/test/resources/jndi.properties b/btm-spring/src/test/resources/jndi.properties
new file mode 100644
index 00000000..7896d013
--- /dev/null
+++ b/btm-spring/src/test/resources/jndi.properties
@@ -0,0 +1,2 @@
+# java.naming.factory.initial=bitronix.tm.jndi.BitronixInitialContextFactory
+java.naming.factory.initial=org.jglue.cdiunit.internal.naming.CdiUnitContextFactory
diff --git a/btm-spring/src/test/resources/test-context.xml b/btm-spring/src/test/resources/test-context.xml
index fde7fe45..6ca37841 100644
--- a/btm-spring/src/test/resources/test-context.xml
+++ b/btm-spring/src/test/resources/test-context.xml
@@ -31,9 +31,46 @@
+
+
+
+
+
+
+
+
+
+
+ jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_DELAY=0
+ sa
+
+
+
+
+
+
+
+
+
+
+
+
+ create-drop
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
index 56106589..91478a06 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,6 +72,7 @@
btm-jetty9-lifecycle
btm-tomcat55-lifecycle
btm-spring
+ btm-cdi
btm-docs
@@ -180,6 +181,11 @@
spring-tx
${spring.version}
+
+ org.springframework
+ spring-orm
+ ${spring.version}
+
org.springframework
spring-test